diff --git a/.devcontainer/backend/devcontainer.json b/.devcontainer/backend/devcontainer.json index b6cd62a..e1c1edc 100644 --- a/.devcontainer/backend/devcontainer.json +++ b/.devcontainer/backend/devcontainer.json @@ -14,7 +14,7 @@ }, "customizations": { "vscode": { - "extensions": ["charliermarsh.ruff", "ms-python.python", "wholroyd.jinja"], + "extensions": ["astral-sh.ty", "charliermarsh.ruff", "ms-python.python", "wholroyd.jinja"], "settings": { "[python][notebook]": { "editor.codeActionsOnSave": { diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 162e29f..1ca703a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,6 +20,7 @@ "dbaeumer.vscode-eslint", "expo.vscode-expo-tools", // Backend + "astral-sh.ty", "charliermarsh.ruff", "ms-python.python", "wholroyd.jinja", diff --git a/.env.example b/.env.example index b371bd6..277b256 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,12 @@ TUNNEL_TOKEN=your_token # Host directory where database and user upload backups are stored BACKUP_DIR=./backups -# Remote backup config (for use of backend/scripts/backup/backup_rclone.sh script) -BACKUP_REMOTE_HOST=user@host -BACKUP_REMOTE_PATH=/path/to/remote/backup +# Remote rsync backup config (for use of backend/scripts/backup/rsync_backup.sh script) +BACKUP_RSYNC_REMOTE_HOST=user@host +BACKUP_RSYNC_REMOTE_PATH=/path/to/remote/backup + +# Remote rclone backup config (for use of backend/scripts/backup/rclone_backup.sh script) +BACKUP_RCLONE_REMOTE=myremote:/path/to/remote/backup +BACKUP_RCLONE_MULTI_THREAD_STREAMS=16 +BACKUP_RCLONE_TIMEOUT=5m +BACKUP_RCLONE_USE_COOKIES=false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0d8647c..73bc3bd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,9 +1,9 @@ --- -name: Bug report about: Create a report to help us improve -title: 'bug: ' -labels: bug assignees: '' +labels: bug +name: Bug report +title: 'bug: ' --- ## Bug description diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index eea6c5b..4d1d87f 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,9 +1,9 @@ --- -name: Feature request about: Suggest an idea for this project -title: 'feature request: ' -labels: feature request assignees: '' +labels: feature request +name: Feature request +title: 'feature request: ' --- ## Problem statement diff --git a/.github/ISSUE_TEMPLATE/internal-ticket.md b/.github/ISSUE_TEMPLATE/internal-ticket.md index e687334..bb764f7 100644 --- a/.github/ISSUE_TEMPLATE/internal-ticket.md +++ b/.github/ISSUE_TEMPLATE/internal-ticket.md @@ -1,9 +1,9 @@ --- -name: Internal ticket about: For internal development -title: '' -labels: '' assignees: '' +labels: '' +name: Internal ticket +title: '' --- ## Problem diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 308b556..aea22d4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,7 +19,7 @@ Please provide a brief description of the changes in this pull request. ## Checklist - [ ] I've read the [contributing guidelines](../CONTRIBUTING.md) -- [ ] Code follows style guidelines and passes quality checks (ruff, pyright) +- [ ] Code follows style guidelines and passes quality checks (ruff, ty) - [ ] Unit tests added/updated and passing locally - [ ] Documentation updated (if applicable) - [ ] Database migrations created (if applicable) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed4ace6..643f314 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,106 +5,108 @@ repos: ### Global hooks - - repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update - rev: v0.8.0 +- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update + rev: v0.9.0 hooks: - - id: pre-commit-update # Autoupdate pre-commit hooks + - id: pre-commit-update # Autoupdate pre-commit hooks - - repo: https://github.com/gitleaks/gitleaks - rev: v8.28.0 +- repo: https://github.com/gitleaks/gitleaks + rev: v8.30.0 hooks: - - id: gitleaks + - id: gitleaks - - repo: https://github.com/executablebooks/mdformat +- repo: https://github.com/executablebooks/mdformat rev: 1.0.0 hooks: - - id: mdformat # Format Markdown files. + - id: mdformat # Format Markdown files. additional_dependencies: - - mdformat-gfm # Support GitHub Flavored Markdown. - - mdformat-footnote - - mdformat-frontmatter - - mdformat-ruff # Support Python code blocks linted with Ruff. - - mdformat-tables # Support GitHub style tables. + - mdformat-gfm>=1.0.0 # Support GitHub Flavored Markdown. + - mdformat-front-matters + - mdformat-ruff # Support Python code blocks linted with Ruff. - - repo: https://github.com/pre-commit/pre-commit-hooks +- repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: check-added-large-files - - id: check-case-conflict # Check for files with names that differ only in case. - - id: check-executables-have-shebangs - - id: check-shebang-scripts-are-executable - - id: check-toml - - id: check-yaml + - id: check-added-large-files + - id: check-case-conflict # Check for files with names that differ only in case. + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-toml + - id: check-yaml exclude: ^docs/mkdocs.yml$ # Exclude mkdocs.yml because it uses an obscure tag to allow for mermaid formatting - - id: detect-private-key - - id: end-of-file-fixer # Ensure files end with a newline. - - id: mixed-line-ending - - id: no-commit-to-branch # Prevent commits to main and master branches. - - id: trailing-whitespace + - id: detect-private-key + - id: end-of-file-fixer # Ensure files end with a newline. + - id: mixed-line-ending + - id: no-commit-to-branch # Prevent commits to main and master branches. + - id: trailing-whitespace args: ["--markdown-linebreak-ext", "md"] # Preserve Markdown hard line breaks. + exclude: ^.*/build/.*\.html$ # Exclude generated HTML files because they often have intentional trailing whitespace for formatting. - - repo: https://github.com/commitizen-tools/commitizen - rev: v4.9.1 +- repo: https://github.com/commitizen-tools/commitizen + rev: v4.13.7 hooks: - - id: commitizen + - id: commitizen stages: [commit-msg] - - repo: https://github.com/simonvanlierde/check-json5 +- repo: https://github.com/simonvanlierde/check-json5 rev: v1.1.0 hooks: - - id: check-json5 + - id: check-json5 files: ^ (?!(backend/frontend-app|frontend-web)/data/) - ### Backend hooks - - repo: https://github.com/RobertCraigie/pyright-python # Lint backend code with Pyright. - rev: v1.1.406 - hooks: - - id: pyright - files: ^backend/(app|scripts|tests)/ - entry: pyright --project backend - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.0 + ### Backend hooks +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.0 hooks: - - id: ruff-check # Lint code + - id: ruff-check # Lint code files: ^backend/(app|scripts|tests)/ args: ["--fix", "--config", "backend/pyproject.toml", "--ignore", "FIX002"] # Allow TODO comments in commits. - - id: ruff-format # Format code + - id: ruff-format # Format code files: ^backend/(app|scripts|tests)/ args: ["--config", "backend/pyproject.toml"] - - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.9.3 +- repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.10.2 hooks: - - id: uv-lock # Update the uv lockfile for the backend. + - id: uv-lock # Update the uv lockfile for the backend. files: ^backend/(uv\.lock|pyproject\.toml|uv\.toml)$ entry: uv lock --project backend - - repo: local - hooks: # Check if Alembic migrations are up-to-date. Uses uv to ensure the right environment when executed through VS Code Git extension. - - id: backend-alembic-autogen-check +- repo: local + hooks: + # I use uv for local hooks to ensure the right environment when executed through VS Code Git extension. + # Check if Alembic migrations are up-to-date. + - id: backend-alembic-autogen-check name: check alembic migrations entry: bash -c 'cd backend && uv run alembic-autogen-check' language: system files: ^(backend/(app|alembic)/|alembic\.ini$) pass_filenames: false stages: [pre-commit] + # Run Ty for static type checking. + - id: ty + name: type check with Ty + files: ^backend/(app|scripts|tests)/ + entry: bash -c 'cd backend && uv run ty check' + language: system + types: [python] - ### Frontend hooks - - repo: local + ### Frontend hooks +- repo: local hooks: - - id: frontend-web-lint - name: lint frontend-web code - entry: bash -c 'cd frontend-web && npm run lint' - language: - system - # Match frontend JavaScript and TypeScript files for linting. - files: ^frontend-web\/.*\.(jsx?|tsx?|c(js|ts)|m(js|ts)|d\\.(ts|cts|mts)|jsonc?)$ + - id: frontend-web-format + name: format frontend-web code + entry: bash -c 'cd frontend-web && npm run format' + language: system + # Match frontend JavaScript and TypeScript files for formatting. + files: + ^frontend-web\/.*\.(jsx?|tsx?|c(js|ts)|m(js|ts)|d\\.(ts|cts|mts)|jsonc?)$ pass_filenames: false - - id: frontend-app-lint - name: lint frontend-app code - entry: bash -c 'cd frontend-app && npm run lint' - language: - system - # Match frontend JavaScript and TypeScript files for linting. - files: ^frontend-app\/.*\.(jsx?|tsx?|c(js|ts)|m(js|ts)|d\\.(ts|cts|mts)|jsonc?)$ + - id: frontend-app-format + name: format frontend-app code + entry: bash -c 'cd frontend-app && npm run format' + language: system + # Match frontend JavaScript and TypeScript files for formatting. + files: + ^frontend-app\/.*\.(jsx?|tsx?|c(js|ts)|m(js|ts)|d\\.(ts|cts|mts)|jsonc?)$ pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3148c9..164be12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,7 @@ Thank you for your interest in contributing to the Reverse Engineering Lab proje - [Backend Code Style](#backend-code-style) - [Backend Testing](#backend-testing) - [Database Migrations](#database-migrations) + - [Email templates](#email-templates) - [Frontend Development](#frontend-development) - [Frontend Code Style](#frontend-code-style) - [Frontend Testing](#frontend-testing) @@ -172,10 +173,7 @@ It is still recommended to use VS Code as your IDE, as we have provided some rec The API is now available at . - You can log in with the superuser details specified in the `.env` file. This gives you access to: - - - Interactive API documentation at - - Admin panel for database management at + You can log in with the superuser details specified in the `.env` file. This gives you access to the interactive API documentation at #### Documentation Setup @@ -286,14 +284,14 @@ We use several tools to ensure code quality: 1. [Ruff](https://docs.astral.sh/ruff/) for linting and code style enforcement (see [`pyproject.toml`](backend/pyproject.toml) for rules): ```bash - uv run ruff check . - uv run ruff format . + uv run ruff check + uv run ruff format ``` -1. [Pyright](https://github.com/microsoft/pyright) for static type checking: +1. [Ty](https://docs.astral.sh/ty/) for static type checking: ```bash - uv run pyright + uv run ty check ``` #### Backend Testing @@ -339,6 +337,34 @@ When making changes to the database schema: uv run alembic upgrade head ``` +#### Email templates + +This project uses [MJML](https://mjml.io/) to write email templates and [Jinja2](https://jinja.palletsprojects.com/en/latest/) for variable substitution at runtime. + +- **Location** + + - Source MJML templates: `backend/app/templates/emails/src/` + - Reusable components: `backend/app/templates/emails/src/components/` + - Compiled HTML output: `backend/app/templates/emails/build/` (This directory is **auto-generated**—do not edit files here.) + +- **Editing Guidelines** + + - Use **MJML** for structure and the `{{include:component_name}}` directive to reuse components. + - Use **Jinja2-style variables** in templates, e.g., `{{ username }}`, `{{ verification_link }}`. + - Keep components small and shared styles in `src/components/styles.mjml`. + - **Do not modify** files in `build/`. + +- **Compiling Templates** + Run the compilation script from the repository root: + + ```bash + cd backend + python scripts/compile_email_templates.py + ``` + +- **Interactive Preview** + For visual development, use MJML online tools or the [MJML VS Code extension](https://marketplace.visualstudio.com/items?itemName=mjmlio.vscode-mjml). + ### Frontend Development Set up your environment as described in the [Getting Started](#getting-started) section. diff --git a/backend/.dockerignore b/backend/.dockerignore index 4c510a3..08aa94a 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -96,3 +96,6 @@ MANIFEST # Local logs ./logs + +# Include built email templates +!app/templates/emails/build/ diff --git a/backend/.env.example b/backend/.env.example index ab1001c..bec577f 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -28,6 +28,12 @@ EMAIL_PASSWORD='your-email-password' # 🔀 Password for EMAIL_FROM='Your Name ' # 🔀 Email address from which the emails are sent. Can be different from the SMTP server username. EMAIL_REPLY_TO='your.replyto.alias.@example.com' # 🔀 Email address to which replies are sent. Can be different from the SMTP server username. +# Redis settings for caching (disposable email domains, sessions, etc.) +REDIS_HOST='localhost' # Redis server host (use 'cache' in Docker) +REDIS_PORT='6379' # Redis server port +REDIS_DB='0' # Redis database number (0-15) +REDIS_PASSWORD='' # 🔀 Redis password (leave empty if no password) + # Superuser details SUPERUSER_EMAIL='your-email@example.com' # 🔀 SUPERUSER_PASSWORD='example_password' # 🔀 diff --git a/backend/.gitignore b/backend/.gitignore index 38f0b26..fb372f9 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -80,3 +80,6 @@ backups/* # VS Code settings !.vscode/settings.json !.vscode/extensions.json + +# Include built email templates +!app/templates/emails/build/ diff --git a/backend/.vscode/extensions.json b/backend/.vscode/extensions.json index 5ab2c28..6bba2b4 100644 --- a/backend/.vscode/extensions.json +++ b/backend/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["charliermarsh.ruff", "ms-python.python", "wholroyd.jinja"] + "recommendations": ["astral-sh.ty", "charliermarsh.ruff", "ms-python.python", "wholroyd.jinja"] } diff --git a/backend/.vscode/settings.json b/backend/.vscode/settings.json index 57b9152..2eecfa4 100644 --- a/backend/.vscode/settings.json +++ b/backend/.vscode/settings.json @@ -13,5 +13,6 @@ "python.linting.ruffEnabled": true, "python.terminal.activateEnvInCurrentTerminal": true, "python.terminal.activateEnvironment": true, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "ty.interpreter": ["${workspaceFolder}/.venv/bin/python"] } diff --git a/backend/Dockerfile b/backend/Dockerfile index 74b22a8..fb580fc 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ # --- Builder stage --- -FROM ghcr.io/astral-sh/uv:0.9-python3.13-trixie-slim@sha256:87db60325200a4fa5e9259fe43ff14c90c429adee952a8efe3f21b278409d09a AS builder +FROM ghcr.io/astral-sh/uv:0.9-python3.14-trixie-slim AS builder # Install git for custom dependencies (fastapi-users-db-sqlmodel) RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ @@ -34,11 +34,10 @@ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --locked --no-editable --no-default-groups --group=api # --- Final runtime stage --- -FROM python:3.14-slim@sha256:1e7c3510ceb3d6ebb499c86e1c418b95cb4e5e2f682f8e195069f470135f8d51 +FROM python:3.14-slim # Build arguments ARG WORKDIR=/opt/relab/backend -ARG APP_PORT=8000 ARG APP_USER=appuser # Set up a non-root user @@ -54,11 +53,11 @@ ENV PYTHONPATH=$WORKDIR \ PYTHONUNBUFFERED=1 \ PATH="$WORKDIR/.venv/bin:$PATH" -# Expose the application port -EXPOSE 8000 - # Switch to non-root user USER $APP_USER +# Expose the application port +EXPOSE 8000 + # Run the FastAPI application CMD [".venv/bin/fastapi", "run", "app/main.py", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev index dafcde7..36cea79 100644 --- a/backend/Dockerfile.dev +++ b/backend/Dockerfile.dev @@ -1,6 +1,6 @@ # Development Dockerfile for FastAPI Backend # Note: This requires mounting the source code as a volume in docker-compose.override.yml -FROM ghcr.io/astral-sh/uv:0.9-python3.13-trixie-slim@sha256:87db60325200a4fa5e9259fe43ff14c90c429adee952a8efe3f21b278409d09a +FROM ghcr.io/astral-sh/uv:0.9-python3.14-trixie-slim # Build arguments ARG WORKDIR=/opt/relab/backend diff --git a/backend/Dockerfile.migrations b/backend/Dockerfile.migrations index 5f8b17a..47a017a 100644 --- a/backend/Dockerfile.migrations +++ b/backend/Dockerfile.migrations @@ -1,6 +1,5 @@ # --- Builder stage --- -FROM ghcr.io/astral-sh/uv:0.9-python3.13-trixie-slim@sha256:87db60325200a4fa5e9259fe43ff14c90c429adee952a8efe3f21b278409d09a AS builder - +FROM ghcr.io/astral-sh/uv:0.9-python3.14-trixie-slim AS builder WORKDIR /opt/relab/backend_migrations # Install git for custom dependencies (fastapi-users-db-sqlmodel) @@ -33,7 +32,7 @@ COPY scripts/ scripts/ COPY app/ app/ # --- Final runtime stage --- -FROM python:3.14-slim@sha256:1e7c3510ceb3d6ebb499c86e1c418b95cb4e5e2f682f8e195069f470135f8d51 +FROM python:3.14-slim # Build arguments ARG WORKDIR=/opt/relab/backend_migrations diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 397a112..ef4b0b2 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -4,10 +4,11 @@ from pathlib import Path import alembic_postgresql_enum # noqa: F401 (Make sure the PostgreSQL ENUM type is recognized) -from alembic import context from sqlalchemy import engine_from_config, pool from sqlmodel import SQLModel # Include the SQLModel metadata +from alembic import context + # Load settings from the FastAPI app config project_root = Path(__file__).resolve().parents[1] sys.path.append(str(project_root)) diff --git a/backend/alembic/versions/0faa2fa19f62_move_from_weight_kg_to_weight_g.py b/backend/alembic/versions/0faa2fa19f62_move_from_weight_kg_to_weight_g.py new file mode 100644 index 0000000..3641e9d --- /dev/null +++ b/backend/alembic/versions/0faa2fa19f62_move_from_weight_kg_to_weight_g.py @@ -0,0 +1,55 @@ +"""Move from weight_kg to weight_g + +Revision ID: 0faa2fa19f62 +Revises: b43d157d07f1 +Create Date: 2025-11-17 14:52:08.201228 + +""" + +from collections.abc import Sequence +from typing import Union + +import sqlalchemy as sa +import sqlmodel + +import app.api.common.models.custom_types +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "0faa2fa19f62" +down_revision: str | None = "b43d157d07f1" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("physicalproperties", sa.Column("weight_g", sa.Float(), nullable=True)) + + # Migrate data: convert kg to g (multiply by 1000) + op.execute(""" + UPDATE physicalproperties + SET weight_g = weight_kg * 1000 + WHERE weight_kg IS NOT NULL + """) + + op.drop_column("physicalproperties", "weight_kg") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "physicalproperties", + sa.Column("weight_kg", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True), + ) + + # Migrate data back: convert g to kg (divide by 1000) + op.execute(""" + UPDATE physicalproperties + SET weight_kg = weight_g / 1000 + WHERE weight_g IS NOT NULL + """) + + op.drop_column("physicalproperties", "weight_g") + # ### end Alembic commands ### diff --git a/backend/alembic/versions/33b00b31e537_initial.py b/backend/alembic/versions/33b00b31e537_initial.py index e17d28c..d6d178a 100644 --- a/backend/alembic/versions/33b00b31e537_initial.py +++ b/backend/alembic/versions/33b00b31e537_initial.py @@ -14,6 +14,7 @@ from sqlalchemy.dialects import postgresql import app.api.common.models.custom_types +import app.api.file_storage.models.custom_types as file_storage_custom_types from alembic import op # revision identifiers, used by Alembic. @@ -34,9 +35,9 @@ def upgrade() -> None: "material", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), - sa.Column("source", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=True), + sa.Column("name", sqlmodel.AutoString(length=50), nullable=False), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), + sa.Column("source", sqlmodel.AutoString(length=50), nullable=True), sa.Column("density_kg_m3", sa.Float(), nullable=True), sa.Column("is_crm", sa.Boolean(), nullable=True), sa.Column("id", sa.Integer(), nullable=False), @@ -47,7 +48,7 @@ def upgrade() -> None: "newslettersubscriber", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("email", sqlmodel.AutoString(), nullable=False), sa.Column("id", sa.Uuid(), nullable=False), sa.Column("is_confirmed", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), @@ -57,9 +58,9 @@ def upgrade() -> None: "organization", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False), - sa.Column("location", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=True), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("name", sqlmodel.AutoString(length=50), nullable=False), + sa.Column("location", sqlmodel.AutoString(length=50), nullable=True), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), sa.Column("id", sa.Uuid(), nullable=False), sa.Column("owner_id", sa.Uuid(), nullable=False), sa.ForeignKeyConstraint(["owner_id"], ["user.id"], name="fk_organization_owner", use_alter=True), @@ -70,8 +71,8 @@ def upgrade() -> None: "producttype", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("name", sqlmodel.AutoString(length=50), nullable=False), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), sa.Column("id", sa.Integer(), nullable=False), sa.PrimaryKeyConstraint("id"), ) @@ -80,8 +81,8 @@ def upgrade() -> None: "taxonomy", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("name", sqlmodel.AutoString(length=50), nullable=False), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), sa.Column( "domains", postgresql.ARRAY( @@ -89,7 +90,7 @@ def upgrade() -> None: ), nullable=True, ), - sa.Column("source", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=True), + sa.Column("source", sqlmodel.AutoString(length=50), nullable=True), sa.Column("id", sa.Integer(), nullable=False), sa.PrimaryKeyConstraint("id"), ) @@ -97,14 +98,14 @@ def upgrade() -> None: op.create_table( "user", sa.Column("id", sa.Uuid(), nullable=False), - sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("hashed_password", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("email", sqlmodel.AutoString(), nullable=False), + sa.Column("hashed_password", sqlmodel.AutoString(), nullable=False), sa.Column("is_active", sa.Boolean(), nullable=False), sa.Column("is_superuser", sa.Boolean(), nullable=False), sa.Column("is_verified", sa.Boolean(), nullable=False), sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("username", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("username", sqlmodel.AutoString(), nullable=True), sa.Column("organization_id", sa.Uuid(), nullable=True), sa.Column( "organization_role", @@ -120,12 +121,12 @@ def upgrade() -> None: "camera", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), - sa.Column("url", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("name", sqlmodel.AutoString(length=50), nullable=False), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), + sa.Column("url", sqlmodel.AutoString(), nullable=False), sa.Column("id", sa.Uuid(), nullable=False), - sa.Column("encrypted_api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("encrypted_auth_headers", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("encrypted_api_key", sqlmodel.AutoString(), nullable=False), + sa.Column("encrypted_auth_headers", sqlmodel.AutoString(), nullable=True), sa.Column("owner_id", sa.Uuid(), nullable=False), sa.ForeignKeyConstraint( ["owner_id"], @@ -138,9 +139,9 @@ def upgrade() -> None: "category", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=250), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), - sa.Column("external_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("name", sqlmodel.AutoString(length=250), nullable=False), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), + sa.Column("external_id", sqlmodel.AutoString(), nullable=True), sa.Column("id", sa.Integer(), nullable=False), sa.Column("supercategory_id", sa.Integer(), nullable=True), sa.Column("taxonomy_id", sa.Integer(), nullable=False), @@ -161,12 +162,12 @@ def upgrade() -> None: sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("id", sa.Uuid(), nullable=False), sa.Column("user_id", sa.Uuid(), nullable=False), - sa.Column("oauth_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("access_token", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("oauth_name", sqlmodel.AutoString(), nullable=False), + sa.Column("access_token", sqlmodel.AutoString(), nullable=False), sa.Column("expires_at", sa.Integer(), nullable=True), - sa.Column("refresh_token", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("account_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("account_email", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("refresh_token", sqlmodel.AutoString(), nullable=True), + sa.Column("account_id", sqlmodel.AutoString(), nullable=False), + sa.Column("account_email", sqlmodel.AutoString(), nullable=False), sa.ForeignKeyConstraint( ["user_id"], ["user.id"], @@ -179,11 +180,11 @@ def upgrade() -> None: "product", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), - sa.Column("brand", sqlmodel.sql.sqltypes.AutoString(length=100), nullable=True), - sa.Column("model", sqlmodel.sql.sqltypes.AutoString(length=100), nullable=True), - sa.Column("dismantling_notes", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("name", sqlmodel.AutoString(length=50), nullable=False), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), + sa.Column("brand", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("model", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("dismantling_notes", sqlmodel.AutoString(length=500), nullable=True), sa.Column("dismantling_time_start", sa.TIMESTAMP(timezone=True), nullable=False), sa.Column("dismantling_time_end", sa.TIMESTAMP(timezone=True), nullable=True), sa.Column("id", sa.Integer(), nullable=False), @@ -238,10 +239,10 @@ def upgrade() -> None: "file", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), sa.Column("id", sa.Uuid(), nullable=False), - sa.Column("filename", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("file", app.api.file_storage.models.custom_types.FileType(), nullable=False), + sa.Column("filename", sqlmodel.AutoString(), nullable=False), + sa.Column("file", file_storage_custom_types.FileType(), nullable=False), sa.Column( "parent_type", postgresql.ENUM("PRODUCT", "PRODUCT_TYPE", "MATERIAL", name="fileparenttype", create_type=False), @@ -268,11 +269,11 @@ def upgrade() -> None: "image", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), sa.Column("image_metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column("id", sa.Uuid(), nullable=False), - sa.Column("filename", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("file", app.api.file_storage.models.custom_types.ImageType(), nullable=False), + sa.Column("filename", sqlmodel.AutoString(), nullable=False), + sa.Column("file", file_storage_custom_types.ImageType(), nullable=False), sa.Column( "parent_type", postgresql.ENUM("PRODUCT", "PRODUCT_TYPE", "MATERIAL", name="imageparenttype", create_type=False), @@ -337,9 +338,9 @@ def upgrade() -> None: "video", sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), - sa.Column("url", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("title", sqlmodel.sql.sqltypes.AutoString(length=100), nullable=True), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True), + sa.Column("url", sqlmodel.AutoString(), nullable=False), + sa.Column("title", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("description", sqlmodel.AutoString(length=500), nullable=True), sa.Column("video_metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column("id", sa.Integer(), nullable=False), sa.Column("product_id", sa.Integer(), nullable=False), diff --git a/backend/alembic/versions/65da9d7e309c_increase_string_fields_max_length.py b/backend/alembic/versions/65da9d7e309c_increase_string_fields_max_length.py index a5cb94a..8023e66 100644 --- a/backend/alembic/versions/65da9d7e309c_increase_string_fields_max_length.py +++ b/backend/alembic/versions/65da9d7e309c_increase_string_fields_max_length.py @@ -28,63 +28,63 @@ def upgrade() -> None: "camera", "name", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=False, ) op.alter_column( "material", "name", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=False, ) op.alter_column( "material", "source", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=True, ) op.alter_column( "organization", "name", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=False, ) op.alter_column( "organization", "location", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=True, ) op.alter_column( "product", "name", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=False, ) op.alter_column( "producttype", "name", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=False, ) op.alter_column( "taxonomy", "name", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=100), + type_=sqlmodel.AutoString(length=100), existing_nullable=False, ) op.alter_column( "taxonomy", "source", existing_type=sa.VARCHAR(length=50), - type_=sqlmodel.sql.sqltypes.AutoString(length=500), + type_=sqlmodel.AutoString(length=500), existing_nullable=True, ) # ### end Alembic commands ### @@ -95,63 +95,63 @@ def downgrade() -> None: op.alter_column( "taxonomy", "source", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=500), + existing_type=sqlmodel.AutoString(length=500), type_=sa.VARCHAR(length=50), existing_nullable=True, ) op.alter_column( "taxonomy", "name", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=False, ) op.alter_column( "producttype", "name", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=False, ) op.alter_column( "product", "name", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=False, ) op.alter_column( "organization", "location", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=True, ) op.alter_column( "organization", "name", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=False, ) op.alter_column( "material", "source", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=True, ) op.alter_column( "material", "name", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=False, ) op.alter_column( "camera", "name", - existing_type=sqlmodel.sql.sqltypes.AutoString(length=100), + existing_type=sqlmodel.AutoString(length=100), type_=sa.VARCHAR(length=50), existing_nullable=False, ) diff --git a/backend/alembic/versions/84d2f72dccc7_simplify_circularity_properties_model.py b/backend/alembic/versions/84d2f72dccc7_simplify_circularity_properties_model.py new file mode 100644 index 0000000..f62c861 --- /dev/null +++ b/backend/alembic/versions/84d2f72dccc7_simplify_circularity_properties_model.py @@ -0,0 +1,50 @@ +"""Simplify Circularity_properties model + +Revision ID: 84d2f72dccc7 +Revises: 0faa2fa19f62 +Create Date: 2025-11-27 12:01:32.413795 + +""" + +from collections.abc import Sequence +from typing import Union + +import sqlalchemy as sa +import sqlmodel + +import app.api.common.models.custom_types +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "84d2f72dccc7" +down_revision: str | None = "0faa2fa19f62" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "circularityproperties", "recyclability_observation", existing_type=sa.VARCHAR(length=500), nullable=True + ) + op.alter_column( + "circularityproperties", "repairability_observation", existing_type=sa.VARCHAR(length=500), nullable=True + ) + op.alter_column( + "circularityproperties", "remanufacturability_observation", existing_type=sa.VARCHAR(length=500), nullable=True + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "circularityproperties", "remanufacturability_observation", existing_type=sa.VARCHAR(length=500), nullable=False + ) + op.alter_column( + "circularityproperties", "repairability_observation", existing_type=sa.VARCHAR(length=500), nullable=False + ) + op.alter_column( + "circularityproperties", "recyclability_observation", existing_type=sa.VARCHAR(length=500), nullable=False + ) + # ### end Alembic commands ### diff --git a/backend/alembic/versions/95cc94317b69_add_version_to_taxonomy_model.py b/backend/alembic/versions/95cc94317b69_add_version_to_taxonomy_model.py index 56d2551..82e7282 100644 --- a/backend/alembic/versions/95cc94317b69_add_version_to_taxonomy_model.py +++ b/backend/alembic/versions/95cc94317b69_add_version_to_taxonomy_model.py @@ -24,7 +24,7 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.add_column("taxonomy", sa.Column("version", sqlmodel.sql.sqltypes.AutoString(length=50), nullable=True)) + op.add_column("taxonomy", sa.Column("version", sqlmodel.AutoString(length=50), nullable=True)) # ### end Alembic commands ### diff --git a/backend/alembic/versions/b43d157d07f1_add_basic_circularity_properties_model.py b/backend/alembic/versions/b43d157d07f1_add_basic_circularity_properties_model.py new file mode 100644 index 0000000..ae6025d --- /dev/null +++ b/backend/alembic/versions/b43d157d07f1_add_basic_circularity_properties_model.py @@ -0,0 +1,54 @@ +"""Add basic circularity_properties model + +Revision ID: b43d157d07f1 +Revises: 95cc94317b69 +Create Date: 2025-11-17 13:30:07.435637 + +""" + +from collections.abc import Sequence +from typing import Union + +import sqlalchemy as sa +import sqlmodel + +import app.api.common.models.custom_types +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "b43d157d07f1" +down_revision: str | None = "95cc94317b69" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "circularityproperties", + sa.Column("created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), + sa.Column("updated_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=True), + sa.Column("recyclability_observation", sqlmodel.AutoString(length=500), nullable=False), + sa.Column("recyclability_comment", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("recyclability_reference", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("repairability_observation", sqlmodel.AutoString(length=500), nullable=False), + sa.Column("repairability_comment", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("repairability_reference", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("remanufacturability_observation", sqlmodel.AutoString(length=500), nullable=False), + sa.Column("remanufacturability_comment", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("remanufacturability_reference", sqlmodel.AutoString(length=100), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("product_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["product_id"], + ["product.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("circularityproperties") + # ### end Alembic commands ### diff --git a/backend/app/api/admin/__init__.py b/backend/app/api/admin/__init__.py deleted file mode 100644 index 180309f..0000000 --- a/backend/app/api/admin/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Admin panel package.""" diff --git a/backend/app/api/admin/auth.py b/backend/app/api/admin/auth.py deleted file mode 100644 index 9371707..0000000 --- a/backend/app/api/admin/auth.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Authentication backend for the SQLAdmin interface, based on FastAPI-Users authentication backend.""" - -import json -from typing import Literal - -from fastapi import Response, status -from fastapi.responses import RedirectResponse -from sqladmin.authentication import AuthenticationBackend -from sqlalchemy.ext.asyncio import async_sessionmaker -from sqlmodel.ext.asyncio.session import AsyncSession -from starlette.requests import Request - -from app.api.admin.config import settings as admin_settings -from app.api.auth.config import settings as auth_settings -from app.api.auth.routers.frontend import router as frontend_auth_router -from app.api.auth.services.user_manager import cookie_transport, get_jwt_strategy -from app.api.auth.utils.context_managers import get_chained_async_user_manager_context -from app.core.database import async_engine - -async_session_generator = async_sessionmaker(bind=async_engine, class_=AsyncSession, expire_on_commit=False) - -# TODO: Redirect all backend login systems (admin panel, swagger docs, API landing page) to frontend login system -main_login_page_redirect_path = ( - f"{frontend_auth_router.url_path_for('login_page')}?next={admin_settings.admin_base_url}" -) - - -class AdminAuth(AuthenticationBackend): - """Authentication backend for the SQLAdmin interface, using FastAPI-Users.""" - - async def login(self, request: Request) -> bool: # noqa: ARG002 # Signature expected by the SQLAdmin implementation - """Placeholder logout function. - - Login is handled by the authenticate method, which redirects to the main API login page. - """ - return True - - async def logout(self, request: Request) -> bool: # noqa: ARG002 # Signature expected by the SQLAdmin implementation - """Placeholder logout function. - - Logout requires unsetting a cookie, which is not possible in the standard SQLAdmin logout function, - which is excepted to return a boolean. - Instead, the default logout route is overridden by the custom route below. - """ - return True - - async def authenticate(self, request: Request) -> RedirectResponse | Response | Literal[True]: - token = request.cookies.get(cookie_transport.cookie_name) - if not token: - return RedirectResponse(url=main_login_page_redirect_path) - async with get_chained_async_user_manager_context() as user_manager: - user = await get_jwt_strategy().read_token(token=token, user_manager=user_manager) - if user is None: - return RedirectResponse(url=main_login_page_redirect_path) - if not user.is_superuser: - return Response( - json.dumps({"detail": "You do not have permission to access this resource."}), - status_code=status.HTTP_403_FORBIDDEN, - media_type="application/json", - ) - - return True - - -def get_authentication_backend() -> AdminAuth: - """Get the authentication backend for the SQLAdmin interface.""" - return AdminAuth(secret_key=auth_settings.fastapi_users_secret) - - -async def logout_override(request: Request) -> RedirectResponse: # noqa: ARG001 # Signature expected by the SQLAdmin implementation - """Override of the default admin dashboard logout route to unset the authentication cookie.""" - response = RedirectResponse(url=frontend_auth_router.url_path_for("index"), status_code=302) - response.delete_cookie( - key=cookie_transport.cookie_name, domain=cookie_transport.cookie_domain, path=cookie_transport.cookie_path - ) - return response diff --git a/backend/app/api/admin/config.py b/backend/app/api/admin/config.py deleted file mode 100644 index 7ed8416..0000000 --- a/backend/app/api/admin/config.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Configuration for the admin module.""" - -from pydantic_settings import BaseSettings - - -class AdminSettings(BaseSettings): - """Settings class to store settings related to admin components.""" - - admin_base_url: str = "/admin/dashboard" # The base url of the SQLadmin interface - - -# Create a settings instance that can be imported throughout the app -settings = AdminSettings() diff --git a/backend/app/api/admin/main.py b/backend/app/api/admin/main.py deleted file mode 100644 index df5aa67..0000000 --- a/backend/app/api/admin/main.py +++ /dev/null @@ -1,65 +0,0 @@ -"""SQLAdmin module for the FastAPI app.""" - -from fastapi import FastAPI -from sqladmin import Admin -from sqlalchemy import Engine -from sqlalchemy.ext.asyncio.engine import AsyncEngine -from starlette.applications import Starlette -from starlette.routing import Mount, Route - -from app.api.admin.auth import get_authentication_backend, logout_override -from app.api.admin.config import settings -from app.api.admin.models import ( - CategoryAdmin, - ImageAdmin, - MaterialAdmin, - MaterialProductLinkAdmin, - ProductAdmin, - ProductTypeAdmin, - TaxonomyAdmin, - UserAdmin, - VideoAdmin, -) - - -def init_admin(app: FastAPI, engine: Engine | AsyncEngine) -> Admin: - """Initialize the SQLAdmin interface for the FastAPI app. - - Args: - app (FastAPI): Main FastAPI application instance - engine (Engine | AsyncEngine): SQLAlchemy database engine, sync or async - """ - admin = Admin(app, engine, authentication_backend=get_authentication_backend(), base_url=settings.admin_base_url) - - # HACK: Override SQLAdmin logout route to allow cookie-based auth - for route in admin.app.routes: - # Find the mounted SQLAdmin app - if isinstance(route, Mount) and route.path == settings.admin_base_url and isinstance(route.app, Starlette): - for subroute in route.app.routes: - # Find the logout subroute and replace it with the custom override to allow cookie-based auth - if isinstance(subroute, Route) and subroute.name == "logout": - route.routes.remove(subroute) - route.app.add_route( - subroute.path, - logout_override, - methods=list(subroute.methods) if subroute.methods is not None else None, - name="logout", - ) - break - break - - # Add Background Data views to Admin interface - admin.add_view(CategoryAdmin) - admin.add_view(MaterialAdmin) - admin.add_view(ProductTypeAdmin) - admin.add_view(TaxonomyAdmin) - # Add Data Collection views to Admin interface - admin.add_view(MaterialProductLinkAdmin) - admin.add_view(ImageAdmin) - admin.add_view(ProductAdmin) - admin.add_view(VideoAdmin) - - # Add other admin views - admin.add_view(UserAdmin) - - return admin diff --git a/backend/app/api/admin/models.py b/backend/app/api/admin/models.py deleted file mode 100644 index f0efff6..0000000 --- a/backend/app/api/admin/models.py +++ /dev/null @@ -1,306 +0,0 @@ -"""Models for the admin module.""" - -import uuid -from collections.abc import Callable, Sequence -from pathlib import Path -from typing import Any, ClassVar - -from anyio import to_thread -from markupsafe import Markup -from sqladmin import ModelView -from sqladmin._types import MODEL_ATTR -from starlette.datastructures import UploadFile -from starlette.requests import Request -from wtforms import ValidationError -from wtforms.fields import FileField -from wtforms.form import Form -from wtforms.validators import InputRequired - -from app.api.auth.models import User -from app.api.background_data.models import Category, Material, ProductType, Taxonomy -from app.api.common.models.associations import MaterialProductLink -from app.api.data_collection.models import Product -from app.api.file_storage.models.models import Image, Video - -### Constants ### -ALLOWED_IMAGE_EXTENSIONS: set[str] = {".bmp", ".gif", ".jpeg", ".jpg", ".png", ".tiff", ".webp"} - - -### Form Validators ### -class FileSizeLimit: - """WTForms validator to limit the file size of a FileField.""" - - def __init__(self, max_size_mb: int, message: str | None = None) -> None: - self.max_size_mb = max_size_mb - self.message = message or f"File size must be under {self.max_size_mb} MB." - - def __call__(self, form: Form, field: FileField): # noqa: ARG002 # WTForms uses this signature - if isinstance(field.data, UploadFile) and field.data.size and field.data.size > self.max_size_mb * 1024 * 1024: - raise ValidationError(self.message) - - -class FileTypeValidator: - """WTForms validator to limit the file type of a FileField.""" - - def __init__(self, allowed_extensions: set[str], message: str | None = None): - self.allowed_extensions = allowed_extensions - self.message = message or f"Allowed file types: {', '.join(self.allowed_extensions)}." - - def __call__(self, form: Form, field: FileField): # noqa: ARG002 # WTForms uses this signature - if isinstance(field.data, UploadFile) and field.data.filename: - file_ext = Path(field.data.filename).suffix.lower() - if file_ext not in self.allowed_extensions: - raise ValidationError(self.message) - - -### Linking Models ### -class MaterialProductLinkAdmin(ModelView, model=MaterialProductLink): - """Admin view for Material-Product links.""" - - name = "Material-Product Link" - name_plural = "Material-Product Links" - icon = "fa-solid fa-link" - category = "Data Collection" - - column_list: ClassVar[Sequence[MODEL_ATTR]] = ["material", "product", "quantity", "unit"] - - column_formatters: ClassVar[dict[MODEL_ATTR, Callable]] = { - "material": lambda m, _: Markup('{}').format(m.material_id, m.material), - "product": lambda m, _: Markup('{}').format(m.product_id, m.product), - } - - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["material.name", "product.name"] - - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["quantity", "unit"] - - column_details_list: ClassVar[Sequence[MODEL_ATTR]] = [*column_list, "created_at", "updated_at"] - - -### Background Models ### -class CategoryAdmin(ModelView, model=Category): - """Admin view for Category model.""" - - name = "Category" - name_plural = "Categories" - icon = "fa-solid fa-list" - category = "Background Data" - column_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name", "taxonomy_id"] - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["name", "description"] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name", "taxonomy_id"] - - -class TaxonomyAdmin(ModelView, model=Taxonomy): - """Admin view for Taxonomy model.""" - - name = "Taxonomy" - name_plural = "Taxonomies" - icon = "fa-solid fa-sitemap" - category = "Background Data" - - column_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name", "domain"] - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["name", "domain"] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name"] - - -class MaterialAdmin(ModelView, model=Material): - """Admin view for Material model.""" - - name = "Material" - name_plural = "Materials" - icon = "fa-solid fa-cubes" - category = "Background Data" - - column_labels: ClassVar[dict[MODEL_ATTR, str]] = { - "density_kg_m3": "Density (kg/m³)", - "is_crm": "Is CRM", - } - - column_list: ClassVar[Sequence[MODEL_ATTR]] = [ - "id", - "name", - "description", - "is_crm", - ] - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["name", "description"] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name", "is_crm"] - - -class ProductTypeAdmin(ModelView, model=ProductType): - """Admin view for ProductType model.""" - - name = "Product Type" - name_plural = "Product Types" - icon = "fa-solid fa-tag" - category = "Background Data" - - column_labels: ClassVar[dict[MODEL_ATTR, str]] = { - "lifespan_yr": "Lifespan (years)", - } - - column_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name", "description"] - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["name", "description"] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "name"] - - -### Product Models ### -class ProductAdmin(ModelView, model=Product): - """Admin view for Product model.""" - - name = "Product" - name_plural = "Products" - icon = "fa-solid fa-box" - category = "Data Collection" - - column_list: ClassVar[Sequence[MODEL_ATTR]] = [ - "id", - "name", - "type", - "description", - ] - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["name", "description"] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = [ - "id", - "name", - "product_type_id", - ] - - -### Data Collection Models ### -class VideoAdmin(ModelView, model=Video): - """Admin view for Video model.""" - - name = "Video" - name_plural = "Videos" - icon = "fa-solid fa-video" - category = "Data Collection" - - column_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "url", "description", "product", "created_at"] - - column_formatters: ClassVar[dict[MODEL_ATTR, Callable]] = { - "url": lambda m, _: Markup('{}').format(m.url, m.url), - "product": lambda m, _: Markup('{}').format(m.product_id, m.product) - if m.product - else "", - "created_at": lambda m, _: m.created_at.strftime("%Y-%m-%d %H:%M") if m.created_at else "", - } - - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["description", "url"] - - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["id", "created_at"] - - column_details_list: ClassVar[Sequence[MODEL_ATTR]] = [*column_list, "updated_at"] - - -### User Models ### -class UserAdmin(ModelView, model=User): - """Admin view for User model.""" - - name = "User" - name_plural = "Users" - icon = "fa-solid fa-user" - category = "Users" - - # User CRUD should be handled by the auth module - can_create = False - can_edit = False - can_delete = False - - column_list: ClassVar[Sequence[MODEL_ATTR]] = [ - "id", - "email", - "username", - "organization", - "is_active", - "is_superuser", - "is_verified", - ] - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = ["email", "organization"] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = ["email", "organization"] - - column_details_list: ClassVar[Sequence[MODEL_ATTR]] = column_list - - -### File Storage Models ### -class ImageAdmin(ModelView, model=Image): - """Admin view for Image model.""" - - # TODO: Use Image schema logic instead of duplicating it here - # TODO: Add a method to download the original file (should take it from the filename but rename it to original_name) - - name = "Image" - name_plural = "Images" - icon = "fa-solid fa-camera" - category = "Data Collection" - - # Display settings - column_list: ClassVar[Sequence[MODEL_ATTR]] = [ - "id", - "description", - "filename", - "created_at", - "updated_at", - "image_preview", - ] - column_details_list: ClassVar[Sequence[MODEL_ATTR]] = column_list - column_formatters: ClassVar[dict[MODEL_ATTR, Callable]] = { - "created_at": lambda model, _: model.created_at.strftime("%Y-%m-%d %H:%M:%S") if model.created_at else "", - "updated_at": lambda model, _: model.updated_at.strftime("%Y-%m-%d %H:%M:%S") if model.updated_at else "", - "image_preview": lambda model, _: model.image_preview(100), - } - column_formatters_detail: ClassVar[dict[MODEL_ATTR, Callable]] = column_formatters - - column_searchable_list: ClassVar[Sequence[MODEL_ATTR]] = [ - "id", - "description", - "filename", - "created_at", - "updated_at", - ] - column_sortable_list: ClassVar[Sequence[MODEL_ATTR]] = column_searchable_list - - # Create and edit settings - form_columns: ClassVar[Sequence[MODEL_ATTR]] = [ - "description", - "file", - ] - - form_args: ClassVar[dict[str, Any]] = { - "file": { - "validators": [ - InputRequired(), - FileSizeLimit(max_size_mb=10), - FileTypeValidator(allowed_extensions=ALLOWED_IMAGE_EXTENSIONS), - ], - } - } - - def _delete_image_file(self, image_path: Path) -> None: - """Delete the image file from the filesystem if it exists.""" - if image_path.exists(): - image_path.unlink() - - def handle_model_change(self, data: dict[str, Any], model: Image, is_created: bool) -> None: # noqa: FBT001 # Wtforms uses this signature - def new_image_uploaded(data: dict[str, Any]) -> bool: - """Check if a new image is present in form data.""" - return isinstance(data.get("file"), UploadFile) and data["file"].size - - if new_image_uploaded(data): - model.filename = data["file"].filename # Set the filename to the original filename - data["file"].filename = f"{uuid.uuid4()}{Path(model.filename).suffix}" # Store the file to a unique path - - if not is_created and model.file: # If the model is being edited and it has an existing image - if new_image_uploaded(data): - self._delete_image_file(Path(model.file.path)) - else: - data.pop("file", None) # Keep existing image if no new one uploaded - - def handle_model_delete(self, model: Image) -> None: - if model.file: - self._delete_image_file(model.file.path) - - async def on_model_change(self, data: dict[str, Any], model: Image, is_created: bool, request: Request) -> None: # noqa: ARG002, FBT001 # Wtforms uses this signature - """SQLAdmin expects on_model_change to be asynchronous. This method handles the synchronous model change.""" - await to_thread.run_sync(self.handle_model_change, data, model, is_created) - - async def after_model_delete(self, model: Image, request: Request) -> None: # noqa: ARG002 # Wtforms uses this signature - await to_thread.run_sync(lambda: self._delete_image_file(Path(model.file.path)) if model.file.path else None) diff --git a/backend/app/api/auth/config.py b/backend/app/api/auth/config.py index e412c4f..5a519b4 100644 --- a/backend/app/api/auth/config.py +++ b/backend/app/api/auth/config.py @@ -2,6 +2,7 @@ from pathlib import Path +from pydantic import SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict # Set the project base directory and .env file @@ -12,24 +13,24 @@ class AuthSettings(BaseSettings): """Settings class to store settings related to auth components.""" # Authentication settings - fastapi_users_secret: str = "" - newsletter_secret: str = "" + fastapi_users_secret: SecretStr = SecretStr("") + newsletter_secret: SecretStr = SecretStr("") # OAuth settings - google_oauth_client_id: str = "" - google_oauth_client_secret: str = "" - github_oauth_client_id: str = "" - github_oauth_client_secret: str = "" + google_oauth_client_id: SecretStr = SecretStr("") + google_oauth_client_secret: SecretStr = SecretStr("") + github_oauth_client_id: SecretStr = SecretStr("") + github_oauth_client_secret: SecretStr = SecretStr("") # Settings used to configure the email server for sending emails from the app. email_host: str = "" email_port: int = 587 # Default SMTP port for TLS email_username: str = "" - email_password: str = "" + email_password: SecretStr = SecretStr("") email_from: str = "" email_reply_to: str = "" - # Initialize the settings configuration from the .env file + # Initialize the settings configuration from the .env file (or direct environment variables in Docker) model_config = SettingsConfigDict(env_file=BASE_DIR / ".env", extra="ignore") # Set default values for email settings if not provided diff --git a/backend/app/api/auth/crud/users.py b/backend/app/api/auth/crud/users.py index 5961c18..2942899 100644 --- a/backend/app/api/auth/crud/users.py +++ b/backend/app/api/auth/crud/users.py @@ -6,7 +6,7 @@ from sqlmodel import select from sqlmodel.ext.asyncio.session import AsyncSession -from app.api.auth.exceptions import UserNameAlreadyExistsError +from app.api.auth.exceptions import DisposableEmailError, UserNameAlreadyExistsError from app.api.auth.models import Organization, OrganizationRole, User from app.api.auth.schemas import ( OrganizationCreate, @@ -14,12 +14,15 @@ UserCreateWithOrganization, UserUpdate, ) +from app.api.auth.utils.email_validation import EmailChecker from app.api.common.crud.utils import db_get_model_with_id_if_it_exists ## Create User ## async def create_user_override( - user_db: BaseUserDatabase[User, UUID4], user_create: UserCreate | UserCreateWithOrganization + user_db: BaseUserDatabase[User, UUID4], + user_create: UserCreate | UserCreateWithOrganization, + email_checker: EmailChecker | None = None, ) -> UserCreate: """Override of base user creation with additional username uniqueness check. @@ -27,6 +30,9 @@ async def create_user_override( """ # TODO: Fix type errors in this method and implement custom UserNameAlreadyExists error in FastAPI-Users + if email_checker and await email_checker.is_disposable(user_create.email): + raise DisposableEmailError(email=user_create.email) + if user_create.username is not None: query = select(User).where(User.username == user_create.username) existing_username = await user_db.session.execute(query) diff --git a/backend/app/api/auth/exceptions.py b/backend/app/api/auth/exceptions.py index d03bdef..3ba0302 100644 --- a/backend/app/api/auth/exceptions.py +++ b/backend/app/api/auth/exceptions.py @@ -126,3 +126,13 @@ def __init__( ) -> None: model_name = model_type.get_api_model_name().name_capital super().__init__(message=(f"User {user_id} does not own {model_name} with ID {model_id}.")) + + +class DisposableEmailError(AuthCRUDError): + """Raised when a disposable email address is used.""" + + http_status_code = status.HTTP_400_BAD_REQUEST + + def __init__(self, email: str) -> None: + msg = f"The email address '{email}' is from a disposable email provider, which is not allowed." + super().__init__(msg) diff --git a/backend/app/api/auth/models.py b/backend/app/api/auth/models.py index 46acc52..6b21492 100644 --- a/backend/app/api/auth/models.py +++ b/backend/app/api/auth/models.py @@ -3,10 +3,10 @@ import uuid from enum import Enum from functools import cached_property -from typing import TYPE_CHECKING, Annotated, Optional +from typing import TYPE_CHECKING, Optional from fastapi_users_db_sqlmodel import SQLModelBaseOAuthAccount, SQLModelBaseUserDB -from pydantic import UUID4, BaseModel, ConfigDict, StringConstraints +from pydantic import UUID4, BaseModel, ConfigDict from sqlalchemy import Enum as SAEnum from sqlalchemy import ForeignKey from sqlmodel import Column, Field, Relationship @@ -31,34 +31,63 @@ class OrganizationRole(str, Enum): class UserBase(BaseModel): """Base schema for user data.""" - username: Annotated[ - str | None, - StringConstraints(strip_whitespace=True, pattern=r"^[\w]+$"), # Allows only letters, numbers, and underscores - ] = Field(index=True, unique=True, default=None) + username: str | None = Field(index=True, unique=True, default=None, min_length=2, max_length=50) - model_config = ConfigDict(use_enum_values=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config = ConfigDict(use_enum_values=True) -class User(UserBase, CustomBaseBare, TimeStampMixinBare, SQLModelBaseUserDB, table=True): +class User(SQLModelBaseUserDB, CustomBaseBare, UserBase, TimeStampMixinBare, table=True): """Database model for platform users.""" + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo (see https://github.com/fastapi/sqlmodel/issues/1623) + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + # One-to-many relationship with OAuthAccount - oauth_accounts: list["OAuthAccount"] = Relationship( + oauth_accounts: list[OAuthAccount] = Relationship( back_populates="user", - sa_relationship_kwargs={"lazy": "joined"}, # Required because of FastAPI-Users OAuth implementation + sa_relationship_kwargs={ + "lazy": "joined", # Required because of FastAPI-Users OAuth implementation + "primaryjoin": "User.id == OAuthAccount.user_id", # HACK: Explicitly define join condition because of + "foreign_keys": "[OAuthAccount.user_id]", # pydantic / sqlmodel issues (see https://github.com/fastapi/sqlmodel/issues/1623) + }, # TODO: Check if this is fixed in future versions of pydantic/sqlmodel and we can use automatic + # relationship detection again + ) + products: list[Product] = Relationship( + back_populates="owner", + sa_relationship_kwargs={ + "primaryjoin": "User.id == Product.owner_id", # HACK: Explicitly define join condition because of + "foreign_keys": "[Product.owner_id]", # pydantic / sqlmodel issues + }, ) - products: list["Product"] = Relationship(back_populates="owner") # Many-to-one relationship with Organization organization_id: UUID4 | None = Field( default=None, - sa_column=Column(ForeignKey("organization.id", use_alter=True, name="fk_user_organization"), nullable=True), + sa_column=Column( + ForeignKey("organization.id", use_alter=True, name="fk_user_organization"), + nullable=True, + ), ) - organization: Optional["Organization"] = Relationship( - back_populates="members", sa_relationship_kwargs={"lazy": "selectin", "foreign_keys": "[User.organization_id]"} + organization: Optional["Organization"] = Relationship( # noqa: UP037, UP045 # `Optional` and quotes needed for proper sqlalchemy mapping + back_populates="members", + sa_relationship_kwargs={ + "lazy": "selectin", + "primaryjoin": "User.organization_id == Organization.id", # HACK: Explicitly define join condition because + "foreign_keys": "[User.organization_id]", # of pydantic / sqlmodel issues + }, ) organization_role: OrganizationRole | None = Field(default=None, sa_column=Column(SAEnum(OrganizationRole))) + # One-to-one relationship with owned Organization + owned_organization: Optional["Organization"] = Relationship( # noqa: UP037, UP045 # `Optional` and quotes needed for proper sqlalchemy mapping + back_populates="owner", + sa_relationship_kwargs={ + "uselist": False, + "primaryjoin": "User.id == Organization.owner_id", # HACK: Explicitly define join condition because of + "foreign_keys": "[Organization.owner_id]", # pydantic / sqlmodel issues + }, + ) + @cached_property def is_organization_owner(self) -> bool: return self.organization_role == OrganizationRole.OWNER @@ -71,8 +100,20 @@ def __str__(self) -> str: class OAuthAccount(SQLModelBaseOAuthAccount, CustomBaseBare, TimeStampMixinBare, table=True): """Database model for OAuth accounts. Note that the main implementation is in the base class.""" + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + + # HACK: Redefine user_id to ensure ForeignKey is preserved despite mixin interference + user_id: UUID4 = Field(foreign_key="user.id", nullable=False) + # Many-to-one relationship with User - user: User = Relationship(back_populates="oauth_accounts") + user: User = Relationship( + back_populates="oauth_accounts", + sa_relationship_kwargs={ # HACK: Explicitly define join condition because of pydantic / sqlmodel issues + "primaryjoin": "OAuthAccount.user_id == User.id", # (see https://github.com/fastapi/sqlmodel/issues/1623) + "foreign_keys": "[OAuthAccount.user_id]", + }, + ) ### Organization Model ### @@ -87,19 +128,25 @@ class OrganizationBase(CustomBase): class Organization(OrganizationBase, TimeStampMixinBare, table=True): """Database model for organizations.""" - id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) # One-to-one relationship with owner User + # Use sa_column with explicit ForeignKey to preserve constraint through mixin inheritance owner_id: UUID4 = Field( - sa_column=Column(ForeignKey("user.id", use_alter=True, name="fk_organization_owner"), nullable=False), + sa_column=Column(ForeignKey("user.id", use_alter=True, name="fk_organization_owner"), nullable=False) ) owner: User = Relationship( - back_populates="organization", - sa_relationship_kwargs={"primaryjoin": "Organization.owner_id == User.id", "foreign_keys": "[User.id]"}, + back_populates="owned_organization", + sa_relationship_kwargs={ + "uselist": False, + "primaryjoin": "Organization.owner_id == User.id", # HACK: Explicitly define join condition because of + "foreign_keys": "[Organization.owner_id]", # pydantic / sqlmodel issues + }, ) # One-to-many relationship with member Users - members: list["User"] = Relationship( + members: list[User] = Relationship( back_populates="organization", sa_relationship_kwargs={ "primaryjoin": "Organization.id == User.organization_id", diff --git a/backend/app/api/auth/routers/admin/users.py b/backend/app/api/auth/routers/admin/users.py index 77fc1cf..ac956ac 100644 --- a/backend/app/api/auth/routers/admin/users.py +++ b/backend/app/api/auth/routers/admin/users.py @@ -91,7 +91,7 @@ async def get_users( @router.get( "/{user_id}", summary="View a single user by ID", - response_model=UserReadWithRelationships, + response_model=UserRead, ) async def get_user( user_id: Annotated[UUID4, Path(description="The user's ID")], diff --git a/backend/app/api/auth/routers/auth.py b/backend/app/api/auth/routers/auth.py index 22a105a..d63266a 100644 --- a/backend/app/api/auth/routers/auth.py +++ b/backend/app/api/auth/routers/auth.py @@ -1,11 +1,13 @@ """Authentication, registration, and login routes.""" -from fastapi import APIRouter +from typing import Annotated + +from fastapi import APIRouter, Depends from pydantic import EmailStr from app.api.auth.schemas import UserCreate, UserCreateWithOrganization, UserRead from app.api.auth.services.user_manager import bearer_auth_backend, cookie_auth_backend, fastapi_user_manager -from app.api.auth.utils.email_validation import is_disposable_email +from app.api.auth.utils.email_validation import EmailChecker, get_email_checker_dependency from app.api.common.routers.openapi import mark_router_routes_public router = APIRouter(prefix="/auth", tags=["auth"]) @@ -38,8 +40,13 @@ @router.get("/validate-email") -async def validate_email(email: EmailStr) -> dict: +async def validate_email( + email: EmailStr, + email_checker: Annotated[EmailChecker | None, Depends(get_email_checker_dependency)], +) -> dict: """Validate email address for registration.""" - is_disposable = await is_disposable_email(email) + is_disposable = False + if email_checker: + is_disposable = await email_checker.is_disposable(email) return {"isValid": not is_disposable, "reason": "Please use a permanent email address" if is_disposable else None} diff --git a/backend/app/api/auth/routers/frontend.py b/backend/app/api/auth/routers/frontend.py index 8a83909..6b2aafd 100644 --- a/backend/app/api/auth/routers/frontend.py +++ b/backend/app/api/auth/routers/frontend.py @@ -6,7 +6,6 @@ from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates -from app.api.admin.config import settings as admin_settings from app.api.auth.dependencies import OptionalCurrentActiveUserDep from app.core.config import settings as core_settings @@ -30,7 +29,6 @@ async def index( "user": user, "show_full_docs": user.is_superuser if user else False, "frontend_web_url": core_settings.frontend_web_url, - "admin_path": admin_settings.admin_base_url, }, ) diff --git a/backend/app/api/auth/routers/oauth.py b/backend/app/api/auth/routers/oauth.py index eba947c..51e5478 100644 --- a/backend/app/api/auth/routers/oauth.py +++ b/backend/app/api/auth/routers/oauth.py @@ -32,7 +32,7 @@ fastapi_user_manager.get_oauth_router( oauth_client, auth_backend, - settings.fastapi_users_secret, + settings.fastapi_users_secret.get_secret_value(), associate_by_email=True, is_verified_by_default=True, ), @@ -44,7 +44,7 @@ fastapi_user_manager.get_oauth_associate_router( oauth_client, UserRead, - settings.fastapi_users_secret, + settings.fastapi_users_secret.get_secret_value(), ), prefix=f"/{provider_name}/associate", ) diff --git a/backend/app/api/auth/schemas.py b/backend/app/api/auth/schemas.py index 9f69481..f2ee013 100644 --- a/backend/app/api/auth/schemas.py +++ b/backend/app/api/auth/schemas.py @@ -31,13 +31,13 @@ class OrganizationRead(OrganizationBase): class OrganizationReadWithRelationshipsPublic(BaseReadSchemaWithTimeStamp, OrganizationBase): """Read schema for organizations, including relationships.""" - members: list["UserReadPublic"] = Field(default_factory=list, description="List of users in the organization.") + members: list[UserReadPublic] = Field(default_factory=list, description="List of users in the organization.") class OrganizationReadWithRelationships(BaseReadSchemaWithTimeStamp, OrganizationBase): """Read schema for organizations, including relationships.""" - members: list["UserRead"] = Field(default_factory=list, description="List of users in the organization.") + members: list[UserRead] = Field(default_factory=list, description="List of users in the organization.") class OrganizationUpdate(BaseUpdateSchema): @@ -51,14 +51,21 @@ class OrganizationUpdate(BaseUpdateSchema): ### Users ### + +# Validation constraints for username field +ValidatedUsername = Annotated[ + str | None, StringConstraints(strip_whitespace=True, pattern=r"^\w+$", min_length=2, max_length=50) +] + + class UserCreateBase(UserBase, schemas.BaseUserCreate): """Base schema for user creation.""" - # Override for validation - username: Annotated[str | None, StringConstraints(strip_whitespace=True)] = None + # Override for username field validation + username: ValidatedUsername = None # Override for OpenAPI schema configuration - password: str = Field(json_schema_extra={"format": "password"}) + password: str = Field(json_schema_extra={"format": "password"}, min_length=8) class UserCreate(UserCreateBase): @@ -66,7 +73,7 @@ class UserCreate(UserCreateBase): organization_id: UUID4 | None = None - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ @@ -85,9 +92,9 @@ class UserCreate(UserCreateBase): class UserCreateWithOrganization(UserCreateBase): """Create schema for users with organization to create and own.""" - organization: "OrganizationCreate" + organization: OrganizationCreate - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ @@ -112,7 +119,7 @@ class UserReadPublic(UserBase): class UserRead(UserBase, schemas.BaseUser[uuid.UUID]): """Read schema for users.""" - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ @@ -133,7 +140,7 @@ class UserRead(UserBase, schemas.BaseUser[uuid.UUID]): class UserReadWithOrganization(UserRead): """Read schema for users with organization.""" - organization: Optional["OrganizationRead"] = Field(default=None, description="Organization the user belongs to.") + organization: OrganizationRead | None = Field(default=None, description="Organization the user belongs to.") class UserReadWithRelationships(UserReadWithOrganization): @@ -145,13 +152,15 @@ class UserReadWithRelationships(UserReadWithOrganization): class UserUpdate(UserBase, schemas.BaseUserUpdate): """Update schema for users.""" - username: Annotated[str | None, StringConstraints(strip_whitespace=True)] = None + # Override for username field validation + username: ValidatedUsername = None + organization_id: UUID4 | None = None # Override password field to include password format in JSON schema - password: str | None = Field(default=None, json_schema_extra={"format": "password"}) + password: str | None = Field(default=None, json_schema_extra={"format": "password"}, min_length=8) - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ diff --git a/backend/app/api/auth/services/user_manager.py b/backend/app/api/auth/services/user_manager.py index daad8b7..1a7aa6f 100644 --- a/backend/app/api/auth/services/user_manager.py +++ b/backend/app/api/auth/services/user_manager.py @@ -35,7 +35,7 @@ logger = logging.getLogger(__name__) # Declare constants -SECRET: str = auth_settings.fastapi_users_secret +SECRET: SecretStr = auth_settings.fastapi_users_secret ACCESS_TOKEN_TTL = auth_settings.access_token_ttl_seconds RESET_TOKEN_TTL = auth_settings.reset_password_token_ttl_seconds VERIFICATION_TOKEN_TTL = auth_settings.verification_token_ttl_seconds @@ -45,10 +45,10 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, UUID4]): """User manager class for FastAPI-Users.""" # Set up token secrets and lifetimes - reset_password_token_secret: SecretType = SECRET + reset_password_token_secret: SecretType = SECRET.get_secret_value() reset_password_token_lifetime_seconds = RESET_TOKEN_TTL - verification_token_secret: SecretType = SECRET + verification_token_secret: SecretType = SECRET.get_secret_value() verification_token_lifetime_seconds = VERIFICATION_TOKEN_TTL async def create( @@ -58,8 +58,20 @@ async def create( request: Request | None = None, ) -> User: """Override of base user creation with additional username uniqueness check and organization creation.""" + # HACK: Skipping of emails for synthetic users is implemented in an ugly way here + # Skip initialization of email checker if sending registration email is disabled + if request and hasattr(request.state, "send_registration_email") and not request.state.send_registration_email: + email_checker = None + else: + # Get email checker from app state if request is available + email_checker = ( + request.app.state.email_checker + if (request and request.app and hasattr(request.app.state, "email_checker")) + else None + ) + try: - user_create = await create_user_override(self.user_db, user_create) + user_create = await create_user_override(self.user_db, user_create, email_checker) # HACK: This is a temporary solution to allow error propagation for username and organization creation errors. # The built-in UserManager register route can only catch UserAlreadyExists and InvalidPasswordException errors. # TODO: Implement custom exceptions in custom register router, this will also simplify user creation crud. @@ -91,7 +103,7 @@ async def update( return await super().update(user_update, user, safe, request) - async def validate_password( # pyright: ignore [reportIncompatibleMethodOverride] # Allow overriding user type in method + async def validate_password( self, password: str | SecretStr, user: UserCreate | User, @@ -139,7 +151,7 @@ async def on_after_verify(self, user: User, request: Request | None = None) -> N await send_post_verification_email(user.email, user.username) async def on_after_forgot_password(self, user: User, token: str, request: Request | None = None) -> None: # noqa: ARG002 # Request argument is expected in the method signature - logger.info("User %s has forgot their password. Reset token: %s", user.email, token) + logger.info("User %s has forgot their password. Sending reset token", user.email) await send_reset_password_email(user.email, user.username, token) @@ -172,7 +184,7 @@ async def get_user_manager(user_db: SQLModelUserDatabaseAsync = Depends(get_user def get_jwt_strategy() -> JWTStrategy: """Get a JWT strategy to be used in authentication backends.""" - return JWTStrategy(secret=SECRET, lifetime_seconds=ACCESS_TOKEN_TTL) + return JWTStrategy(secret=SECRET.get_secret_value(), lifetime_seconds=ACCESS_TOKEN_TTL) # Authentication backends diff --git a/backend/app/api/auth/utils/context_managers.py b/backend/app/api/auth/utils/context_managers.py index 9823ba6..af40334 100644 --- a/backend/app/api/auth/utils/context_managers.py +++ b/backend/app/api/auth/utils/context_managers.py @@ -19,7 +19,7 @@ @asynccontextmanager async def get_chained_async_user_manager_context( session: AsyncSession | None = None, -) -> AsyncGenerator["UserManager"]: +) -> AsyncGenerator[UserManager]: """Provides a user manager context using the user database and an async database session. If a session is provided, it will be used; otherwise, a new session for the default database will be created. diff --git a/backend/app/api/auth/utils/email_config.py b/backend/app/api/auth/utils/email_config.py new file mode 100644 index 0000000..d2d2284 --- /dev/null +++ b/backend/app/api/auth/utils/email_config.py @@ -0,0 +1,31 @@ +"""Email configuration for fastapi-mail. + +This module provides the FastMail instance and configuration for sending emails +throughout the application. +""" + +from pathlib import Path + +from fastapi_mail import ConnectionConfig, FastMail + +from app.api.auth.config import settings as auth_settings + +# Path to pre-compiled HTML email templates +TEMPLATE_FOLDER = Path(__file__).parent.parent.parent.parent / "templates" / "emails" / "build" + +# Configure email connection +email_conf = ConnectionConfig( + MAIL_USERNAME=auth_settings.email_username, + MAIL_PASSWORD=auth_settings.email_password, + MAIL_FROM=auth_settings.email_from, + MAIL_PORT=auth_settings.email_port, + MAIL_SERVER=auth_settings.email_host, + MAIL_STARTTLS=True, + MAIL_SSL_TLS=False, + USE_CREDENTIALS=True, + VALIDATE_CERTS=True, + TEMPLATE_FOLDER=TEMPLATE_FOLDER, +) + +# Create FastMail instance +fm = FastMail(email_conf) diff --git a/backend/app/api/auth/utils/email_validation.py b/backend/app/api/auth/utils/email_validation.py index b4e3ac1..5e98026 100644 --- a/backend/app/api/auth/utils/email_validation.py +++ b/backend/app/api/auth/utils/email_validation.py @@ -1,54 +1,139 @@ -# backend/app/api/auth/utils/email_validation.py -from datetime import UTC, datetime, timedelta -from pathlib import Path +"""Utilities for validating email addresses.""" -import anyio -import httpx -from fastapi import HTTPException +import asyncio +import contextlib +import logging +from fastapi import Request +from fastapi_mail.email_utils import DefaultChecker +from redis.asyncio import Redis +from redis.exceptions import RedisError + +logger = logging.getLogger(__name__) + +# Custom source for disposable domains DISPOSABLE_DOMAINS_URL = "https://raw.githubusercontent.com/disposable/disposable-email-domains/master/domains.txt" -BASE_DIR: Path = (Path(__file__).parents[4]).resolve() - -CACHE_FILE = BASE_DIR / "data" / "cache" / "disposable_domains_cache.txt" -CACHE_DURATION = timedelta(days=1) - - -async def get_disposable_domains() -> set[str]: - """Get disposable email domains, using cache if fresh.""" - # Check if cache exists and is fresh - if CACHE_FILE.exists(): - cache_age = datetime.now(tz=UTC) - datetime.fromtimestamp(CACHE_FILE.stat().st_mtime, tz=UTC) - if cache_age < CACHE_DURATION: - async with await anyio.open_file(CACHE_FILE, "r") as f: - content = await f.read() # Read the entire file first - return {line.strip().lower() for line in content.splitlines() if line.strip()} - - # Fetch fresh list - try: - async with httpx.AsyncClient() as client: - response = await client.get(DISPOSABLE_DOMAINS_URL, timeout=10.0) - response.raise_for_status() - domains = {line.strip().lower() for line in response.text.splitlines() if line.strip()} - - # Ensure cache directory exists - CACHE_FILE.parent.mkdir(parents=True, exist_ok=True) - - # Update cache - async with await anyio.open_file(CACHE_FILE, "w") as f: - await f.write("\n".join(sorted(domains))) - - return domains - except Exception as e: - # If fetch fails and cache exists, use stale cache - if CACHE_FILE.exists(): - async with await anyio.open_file(CACHE_FILE, "r") as f: - content = await f.read() # Read the entire file first - return {line.strip().lower() for line in content.splitlines() if line.strip()} - raise HTTPException(status_code=503, detail="Email validation service unavailable") from e - - -async def is_disposable_email(email: str) -> bool: - """Check if email domain is disposable.""" - domain = email.split("@")[-1].lower() - disposable_domains = await get_disposable_domains() - return domain in disposable_domains + + +class EmailChecker: + """Email checker that manages disposable domain validation.""" + + def __init__(self, redis_client: Redis | None) -> None: + """Initialize email checker with Redis client. + + Args: + redis_client: Redis client instance to use for caching + """ + self.redis_client = redis_client + self.checker: DefaultChecker | None = None + self._refresh_task: asyncio.Task | None = None + + async def initialize(self) -> None: + """Initialize the disposable email checker. + + Should be called during application startup. + """ + try: + if self.redis_client is None: + self.checker = DefaultChecker(source=DISPOSABLE_DOMAINS_URL) + logger.info("Disposable email checker initialized without Redis") + else: + self.checker = DefaultChecker( + source=DISPOSABLE_DOMAINS_URL, + db_provider="redis", + redis_client=self.redis_client, + ) + await self.checker.init_redis() + logger.info("Disposable email checker initialized with Redis") + + # Fetch initial domains + await self._refresh_domains() + + # Start periodic refresh task + self._refresh_task = asyncio.create_task(self._periodic_refresh()) + + except (RuntimeError, ValueError, ConnectionError, OSError, RedisError) as e: + logger.warning("Failed to initialize disposable email checker: %s", e) + self.checker = None + + async def _refresh_domains(self) -> None: + """Refresh the list of disposable email domains from the source.""" + if self.checker is None: + logger.warning("Email checker not initialized, cannot refresh domains") + return + try: + await self.checker.fetch_temp_email_domains() + logger.info("Disposable email domains refreshed successfully") + except (RuntimeError, ValueError, ConnectionError, OSError, RedisError): + logger.exception("Failed to refresh disposable email domains:") + + async def _periodic_refresh(self) -> None: + """Periodically refresh disposable domains every 24 hours.""" + while True: + try: + await asyncio.sleep(60 * 60 * 24) # 24 hours + await self._refresh_domains() + except asyncio.CancelledError: + logger.info("Periodic domain refresh task cancelled") + break + except (RuntimeError, ValueError, ConnectionError, OSError, RedisError): + logger.exception("Error in periodic domain refresh:") + + async def close(self) -> None: + """Close the email checker and cleanup resources. + + Should be called during application shutdown. + """ + # Cancel periodic refresh task + if self._refresh_task is not None: + self._refresh_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await self._refresh_task + + # Close checker connections if initialized + if self.checker is not None and self.redis_client is not None: + logger.info("Closing email checker Redis connections") + try: + await self.checker.close_connections() + logger.info("Email checker closed successfully") + except (RuntimeError, ValueError, ConnectionError, OSError, RedisError) as e: + logger.warning("Error closing email checker: %s", e) + finally: + self.checker = None + + async def is_disposable(self, email: str) -> bool: + """Check if email domain is disposable. + + Args: + email: Email address to check + + Returns: + bool: True if email is from a disposable domain, False otherwise + """ + if self.checker is None: + logger.warning("Email checker not initialized, allowing registration") + return False + try: + return await self.checker.is_disposable(email) + except (RuntimeError, ValueError, ConnectionError, OSError, RedisError): + logger.exception("Failed to check if email is disposable: %s. Allowing registration.", email) + # If check fails, allow registration (fail open) + return False + + +def get_email_checker_dependency(request: Request) -> EmailChecker | None: + """FastAPI dependency to get EmailChecker from app state. + + Args: + request: FastAPI request object + + Returns: + EmailChecker instance or None if not initialized + + Usage: + @app.get("/example") + async def example(email_checker: EmailChecker | None = Depends(get_email_checker_dependency)): + if email_checker: + await email_checker.is_disposable("test@example.com") + """ + return request.app.state.email_checker diff --git a/backend/app/api/auth/utils/programmatic_emails.py b/backend/app/api/auth/utils/programmatic_emails.py index dc1cefa..49b67f0 100644 --- a/backend/app/api/auth/utils/programmatic_emails.py +++ b/backend/app/api/auth/utils/programmatic_emails.py @@ -1,169 +1,157 @@ -"""Utilities for sending authentication-related emails.""" +"""Utilities for sending authentication-related emails using fastapi-mail.""" import logging -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from enum import Enum +from typing import TYPE_CHECKING, Any from urllib.parse import urljoin -import markdown -from aiosmtplib import SMTP, SMTPException +from fastapi_mail import MessageSchema, MessageType +from pydantic import AnyUrl, EmailStr, NameEmail -from app.api.auth.config import settings as auth_settings +from app.api.auth.utils.email_config import fm from app.core.config import settings as core_settings -logger: logging.Logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from fastapi import BackgroundTasks +logger: logging.Logger = logging.getLogger(__name__) -### Common email functions ### -# TODO: Move to using MJML or similar templating system for email content. +### Helper functions ### +def generate_token_link(token: str, route: str, base_url: str | AnyUrl | None = None) -> str: + """Generate a link with the specified token and route.""" + if base_url is None: + # Default to frontend app URL from core settings + base_url = str(core_settings.frontend_app_url) + return urljoin(str(base_url), f"{route}?token={token}") -class TextContentType(str, Enum): - """Type for specifying the content type of the email body.""" - PLAIN = "plain" - HTML = "html" - MARKDOWN = "markdown" +def mask_email_for_log(email: EmailStr, *, mask: bool = True, max_len: int = 80) -> str: + """Mask emails for logging. - def body_to_mimetext(self, body: str) -> MIMEText: - """Convert an email body to MIMEText format.""" - match self: - case TextContentType.PLAIN: - return MIMEText(body, "plain") - case TextContentType.HTML: - return MIMEText(body, "html") - case TextContentType.MARKDOWN: - # Convert Markdown to HTML - html = markdown.markdown(body) - return MIMEText(html, "html") + Also remove non-printable characters and truncates long domains. Explicitly removes log-breaking control characters. + """ + # Remove non-printable and log-breaking control characters + string = "".join(ch for ch in str(email) if ch.isprintable()).replace("\n", "").replace("\r", "") + local, sep, domain = string.partition("@") + masked = (f"{local[0]}***@{domain}" if len(local) > 1 else f"*@{domain}") if sep and mask else string + return f"{masked[: max_len - 3]}..." if len(masked) > max_len else masked -async def send_email( - to_email: str, +### Generic email function ### +async def send_email_with_template( + to_email: EmailStr, subject: str, - body: str, - content_type: TextContentType = TextContentType.PLAIN, - headers: dict | None = None, + template_name: str, + template_body: dict[str, Any], + background_tasks: BackgroundTasks | None = None, ) -> None: - """Send an email with the specified subject and body.""" - msg = MIMEMultipart() - msg["From"] = auth_settings.email_from - msg["Reply-To"] = auth_settings.email_reply_to - msg["To"] = to_email - msg["Subject"] = subject - - # Add additional headers if provided - if headers: - for key, value in headers.items(): - msg[key] = value - - # Attach the body in the specified content type - msg.attach(content_type.body_to_mimetext(body)) - - try: - # TODO: Investigate use of managed outlook address for sending emails - smtp = SMTP( - hostname=auth_settings.email_host, - port=auth_settings.email_port, + """Send an HTML email using a template. + + Args: + to_email: Recipient email address + subject: Email subject line + template_name: Name of the template file (e.g., "registration.html") + template_body: Dictionary of variables to pass to the template + background_tasks: Optional BackgroundTasks instance for async sending + """ + message = MessageSchema( + subject=subject, + recipients=[NameEmail(name=str(to_email), email=str(to_email))], + template_body=template_body, + subtype=MessageType.html, + ) + + if background_tasks: + background_tasks.add_task(fm.send_message, message, template_name=template_name) + logger.info( + "Email queued for background sending to %s using template %s", mask_email_for_log(to_email), template_name ) - await smtp.connect() - # logger.info("Sending email to %s", auth_settings.__dict__) - await smtp.login(auth_settings.email_username, auth_settings.email_password) - await smtp.send_message(msg) - await smtp.quit() - logger.info("Email sent to %s", to_email) - except SMTPException as e: - error_message = f"Error sending email: {e}" - raise SMTPException(error_message) from e - - -def generate_token_link(token: str, route: str) -> str: - """Generate a link with the specified token and route.""" - # TODO: Check that the base url works in remote deployment - return urljoin(str(core_settings.frontend_app_url), f"{route}?token={token}") + else: + await fm.send_message(message, template_name=template_name) + logger.info("Email sent to %s using template %s", mask_email_for_log(to_email), template_name) -### Email content ### -async def send_registration_email(to_email: str, username: str | None, token: str) -> None: +### Authentication email functions ### +async def send_registration_email( + to_email: EmailStr, + username: str | None, + token: str, + background_tasks: BackgroundTasks | None = None, +) -> None: """Send a registration email with verification token.""" - # TODO: Store frontend paths required by the backend in a shared .env or other config file in the root directory - # Alternatively, we can send the right path as a parameter from the frontend to the backend verification_link = generate_token_link(token, "/verify") subject = "Welcome to Reverse Engineering Lab - Verify Your Email" - body = f""" -Hello {username if username else to_email}, - -Thank you for registering! Please verify your email by clicking the link below: - -{verification_link} - -This link will expire in 1 hour. -If you did not register for this service, please ignore this email. - -Best regards, - -The Reverse Engineering Lab Team - """ - - await send_email(subject=subject, body=body, to_email=to_email) - - -async def send_reset_password_email(to_email: str, username: str | None, token: str) -> None: + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="registration.html", + template_body={ + "username": username or to_email, + "verification_link": verification_link, + }, + background_tasks=background_tasks, + ) + + +async def send_reset_password_email( + to_email: EmailStr, + username: str | None, + token: str, + background_tasks: BackgroundTasks | None = None, +) -> None: """Send a reset password email with the token.""" - request_password_link = generate_token_link(token, "/reset-password") + reset_link = generate_token_link(token, "/reset-password") subject = "Password Reset" - body = f""" -Hello {username if username else to_email}, - -Please reset your password by clicking the link below: - -{request_password_link} -This link will expire in 1 hour. - -If you did not request a password reset, please ignore this email. - -Best regards, - -The Reverse Engineering Lab Team - """ - await send_email(to_email, subject, body) - - -async def send_verification_email(to_email: str, username: str | None, token: str) -> None: + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="password_reset.html", + template_body={ + "username": username or to_email, + "reset_link": reset_link, + }, + background_tasks=background_tasks, + ) + + +async def send_verification_email( + to_email: EmailStr, + username: str | None, + token: str, + background_tasks: BackgroundTasks | None = None, +) -> None: """Send a verification email with the token.""" verification_link = generate_token_link(token, "/verify") subject = "Email Verification" - body = f""" -Hello {username if username else to_email}, - -Please verify your email by clicking the link below: - -{verification_link} - -This link will expire in 1 hour. -If you did not request verification, please ignore this email. - -Best regards, - -The Reverse Engineering Lab Team - """ - await send_email(to_email, subject, body) - - -async def send_post_verification_email(to_email: str, username: str | None) -> None: + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="verification.html", + template_body={ + "username": username or to_email, + "verification_link": verification_link, + }, + background_tasks=background_tasks, + ) + + +async def send_post_verification_email( + to_email: EmailStr, + username: str | None, + background_tasks: BackgroundTasks | None = None, +) -> None: """Send a post-verification email.""" subject = "Email Verified" - body = f""" -Hello {username if username else to_email}, - -Your email has been verified! -Best regards, - -The Reverse Engineering Lab Team - """ - await send_email(to_email, subject, body) + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="post_verification.html", + template_body={ + "username": username or to_email, + }, + background_tasks=background_tasks, + ) diff --git a/backend/app/api/background_data/crud.py b/backend/app/api/background_data/crud.py index 999b3c8..cab79c5 100644 --- a/backend/app/api/background_data/crud.py +++ b/backend/app/api/background_data/crud.py @@ -361,7 +361,7 @@ async def create_material(db: AsyncSession, material: MaterialCreate | MaterialC # Create links await create_model_links( db, - id1=db_material.id, # pyright: ignore[reportArgumentType] # material ID is guaranteed by database flush above, + id1=db_material.id, # ty: ignore[invalid-argument-type] # material ID is guaranteed by database flush above id1_field="material_id", id2_set=material.category_ids, id2_field="category_id", @@ -420,7 +420,7 @@ async def add_categories_to_material( await create_model_links( db, - id1=db_material.id, # pyright: ignore[reportArgumentType] # material ID is guaranteed by database flush above, + id1=db_material.id, # ty: ignore[invalid-argument-type] # material ID is guaranteed by database flush above id1_field="material_id", id2_set=category_ids, id2_field="category_id", @@ -503,7 +503,7 @@ async def create_product_type( if isinstance(product_type, ProductTypeCreateWithCategories) and product_type.category_ids: await create_model_links( db, - id1=db_product_type.id, # pyright: ignore[reportArgumentType] # material ID is guaranteed by database flush above, + id1=db_product_type.id, # ty: ignore[invalid-argument-type] # material ID is guaranteed by database flush above id1_field="product_type", id2_set=product_type.category_ids, id2_field="category_id", @@ -561,7 +561,7 @@ async def add_categories_to_product_type( await create_model_links( db, - id1=db_product_type.id, # pyright: ignore[reportArgumentType] # material ID is guaranteed by database flush above, + id1=db_product_type.id, # ty: ignore[invalid-argument-type] # material ID is guaranteed by database flush above id1_field="product_type", id2_set=category_ids, id2_field="category_id", diff --git a/backend/app/api/background_data/models.py b/backend/app/api/background_data/models.py index d03a295..4a3c22d 100644 --- a/backend/app/api/background_data/models.py +++ b/backend/app/api/background_data/models.py @@ -56,7 +56,7 @@ class TaxonomyBase(CustomBase): default=None, max_length=500, description="Source of the taxonomy data, e.g. URL, IRI or citation key" ) - model_config: ConfigDict = ConfigDict(use_enum_values=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict(use_enum_values=True) class Taxonomy(TaxonomyBase, TimeStampMixinBare, table=True): @@ -64,9 +64,9 @@ class Taxonomy(TaxonomyBase, TimeStampMixinBare, table=True): id: int | None = Field(default=None, primary_key=True) - categories: list["Category"] = Relationship(back_populates="taxonomy", cascade_delete=True) + categories: list[Category] = Relationship(back_populates="taxonomy", cascade_delete=True) - model_config: ConfigDict = ConfigDict(use_enum_values=True, arbitrary_types_allowed=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict(use_enum_values=True, arbitrary_types_allowed=True) # Magic methods def __str__(self) -> str: @@ -89,11 +89,11 @@ class Category(CategoryBase, TimeStampMixinBare, table=True): # Self-referential relationship supercategory_id: int | None = Field(foreign_key="category.id", default=None, nullable=True) - supercategory: Optional["Category"] = Relationship( + supercategory: Optional["Category"] = Relationship( # noqa: UP037, UP045 # `Optional` and quotes needed for proper sqlalchemy mapping back_populates="subcategories", sa_relationship_kwargs={"remote_side": "Category.id", "lazy": "selectin", "join_depth": 1}, ) - subcategories: list["Category"] | None = Relationship( + subcategories: list[Category] | None = Relationship( back_populates="supercategory", sa_relationship_kwargs={"lazy": "selectin", "join_depth": 1}, cascade_delete=True, @@ -104,8 +104,8 @@ class Category(CategoryBase, TimeStampMixinBare, table=True): taxonomy: Taxonomy = Relationship(back_populates="categories") # Many-to-many relationships. This is ugly but SQLModel doesn't allow for polymorphic association. - materials: list["Material"] | None = Relationship(back_populates="categories", link_model=CategoryMaterialLink) - product_types: list["ProductType"] | None = Relationship( + materials: list[Material] | None = Relationship(back_populates="categories", link_model=CategoryMaterialLink) + product_types: list[ProductType] | None = Relationship( back_populates="categories", link_model=CategoryProductTypeLink ) @@ -138,7 +138,7 @@ class Material(MaterialBase, TimeStampMixinBare, table=True): # Many-to-many relationships categories: list[Category] | None = Relationship(back_populates="materials", link_model=CategoryMaterialLink) - product_links: list["MaterialProductLink"] | None = Relationship(back_populates="material") + product_links: list[MaterialProductLink] | None = Relationship(back_populates="material") # Magic methods def __str__(self) -> str: @@ -159,7 +159,7 @@ class ProductType(ProductTypeBase, TimeStampMixinBare, table=True): id: int | None = Field(default=None, primary_key=True) # One-to-many relationships - products: list["Product"] | None = Relationship(back_populates="product_type") + products: list[Product] | None = Relationship(back_populates="product_type") files: list[File] | None = Relationship(back_populates="product_type", cascade_delete=True) images: list[Image] | None = Relationship(back_populates="product_type", cascade_delete=True) diff --git a/backend/app/api/background_data/routers/admin.py b/backend/app/api/background_data/routers/admin.py index cdc4fa1..31b3e92 100644 --- a/backend/app/api/background_data/routers/admin.py +++ b/backend/app/api/background_data/routers/admin.py @@ -213,6 +213,7 @@ async def create_taxonomy( "description": "Taxonomy for materials", "domains": ["materials"], "source": "DOI:10.2345/12345", + "version": "1.0", }, }, "nested": { @@ -223,6 +224,7 @@ async def create_taxonomy( "description": "Taxonomy for materials", "domains": ["materials"], "source": "DOI:10.2345/12345", + "version": "1.0", "categories": [ { "name": "Metals", @@ -254,7 +256,7 @@ async def update_taxonomy( }, "advanced": { "summary": "Update domain and source", - "value": {"domain": "materials", "source": "https://new-source.com/taxonomy"}, + "value": {"domains": ["materials"], "source": "https://new-source.com/taxonomy"}, }, } ), diff --git a/backend/app/api/background_data/schemas.py b/backend/app/api/background_data/schemas.py index b82b280..e04f3e7 100644 --- a/backend/app/api/background_data/schemas.py +++ b/backend/app/api/background_data/schemas.py @@ -13,6 +13,7 @@ from app.api.common.schemas.base import ( BaseCreateSchema, BaseReadSchema, + BaseReadSchemaWithTimeStamp, BaseUpdateSchema, MaterialRead, ProductRead, @@ -33,8 +34,8 @@ class CategoryCreateWithinCategoryWithSubCategories(BaseCreateSchema, CategoryBa """Schema for creating a new category within a category, with optional subcategories.""" # Database model has a None default, but Pydantic model has empty set default for consistent API type handling - subcategories: set["CategoryCreateWithinCategoryWithSubCategories"] = Field( - default_factory=set, + subcategories: list[CategoryCreateWithinCategoryWithSubCategories] = Field( + default_factory=list, description="List of subcategories", ) @@ -59,7 +60,7 @@ class CategoryCreateWithSubCategories(CategoryCreateWithinTaxonomyWithSubCategor class CategoryReadAsSubCategory(BaseReadSchema, CategoryBase): """Schema for reading subcategory information.""" - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( json_schema_extra={ "examples": [ { @@ -78,7 +79,7 @@ class CategoryRead(CategoryReadAsSubCategory): taxonomy_id: PositiveInt = Field(description="ID of the taxonomy") supercategory_id: PositiveInt | None = None - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see + model_config: ConfigDict = ConfigDict( json_schema_extra={ "examples": [ { @@ -97,7 +98,7 @@ class CategoryReadWithRelationships(CategoryRead): """Schema for reading category information with all relationships.""" materials: list[MaterialRead] = Field(default_factory=list, description="List of materials linked to the category") - product_types: list["ProductTypeRead"] = Field( + product_types: list[ProductTypeRead] = Field( default_factory=list, description="List of product types linked to the category" ) @@ -111,11 +112,11 @@ class CategoryReadWithRelationshipsAndFlatSubCategories(CategoryReadWithRelation class CategoryReadAsSubCategoryWithRecursiveSubCategories(CategoryReadAsSubCategory): """Schema for reading category information with recursive subcategories.""" - subcategories: list["CategoryReadAsSubCategoryWithRecursiveSubCategories"] = Field( + subcategories: list[CategoryReadAsSubCategoryWithRecursiveSubCategories] = Field( default_factory=list, description="List of subcategories" ) - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( json_schema_extra={ "examples": [ { @@ -168,7 +169,7 @@ class CategoryUpdate(BaseUpdateSchema): name: str | None = Field(default=None, min_length=2, max_length=100, description="Name of the category") description: str | None = Field(default=None, max_length=500, description="Description of the category") - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ @@ -191,23 +192,23 @@ class TaxonomyCreate(BaseCreateSchema, TaxonomyBase): class TaxonomyCreateWithCategories(BaseCreateSchema, TaxonomyBase): """Schema for creating a new taxonomy, optionally with new categories.""" - categories: set[CategoryCreateWithinTaxonomyWithSubCategories] = Field( - default_factory=set, description="Set of subcategories" + categories: list[CategoryCreateWithinTaxonomyWithSubCategories] = Field( + default_factory=list, description="Set of subcategories" ) ## Read Schemas ## -class TaxonomyRead(BaseReadSchema, TaxonomyBase): +class TaxonomyRead(BaseReadSchemaWithTimeStamp, TaxonomyBase): """Schema for reading minimal taxonomy information.""" - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ { "name": "Materials Taxonomy", "description": "Taxonomy for materials", - "domain": "materials", + "domains": ["materials"], "source": "DOI:10.2345/12345", } ] @@ -223,14 +224,14 @@ class TaxonomyReadWithCategoryTree(TaxonomyRead): default_factory=set, description="Set of categories in the taxonomy" ) - model_config: ConfigDict = ConfigDict( # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict( { "json_schema_extra": { "examples": [ { "name": "Materials Taxonomy", "description": "Taxonomy for materials", - "domain": "materials", + "domains": ["materials"], "source": "DOI:10.2345/12345", "categories": [ { @@ -260,7 +261,8 @@ class TaxonomyUpdate(BaseUpdateSchema): version: str | None = Field(default=None, min_length=1, max_length=50) description: str | None = Field(default=None, max_length=500) domains: set[TaxonomyDomain] | None = Field( - description="Domains of the taxonomy, e.g. {" + f"{', '.join([d.value for d in TaxonomyDomain][:3])}" + "}" + default=None, + description="Domains of the taxonomy, e.g. {" + f"{', '.join([d.value for d in TaxonomyDomain][:3])}" + "}", ) source: str | None = Field(default=None, max_length=50, description="Source of the taxonomy data") @@ -299,12 +301,10 @@ class MaterialReadWithRelationships(MaterialRead): class MaterialUpdate(BaseUpdateSchema): """Schema for a partial update of a material.""" - name: str | None = Field(default=None, min_length=2, max_length=100, description="Name of the Material") - description: str | None = Field(default=None, max_length=500, description="Description of the Material") + name: str | None = Field(default=None, min_length=2, max_length=100) + description: str | None = Field(default=None, max_length=500) source: str | None = Field( - default=None, - max_length=50, - description="Source of the material data, e.g. URL, IRI or citation key", + default=None, max_length=50, description="Source of the material data, e.g. URL, IRI or citation key" ) density_kg_m3: float | None = Field(default=None, gt=0, description="Volumetric density (kg/m³) ") is_crm: bool | None = Field(default=None, description="Is this material a Critical Raw Material (CRM)?") @@ -319,7 +319,7 @@ class ProductTypeCreate(BaseCreateSchema, ProductTypeBase): class ProductTypeCreateWithCategories(BaseCreateSchema, ProductTypeBase): """Schema for creating a product type with links to existing categories.""" - category_ids: set[int] = Field(default_factory=set, description="List of category IDs") + category_ids: set[int] = Field(default_factory=set) ## Read Schemas ## @@ -330,19 +330,15 @@ class ProductTypeRead(BaseReadSchema, ProductTypeBase): class ProductTypeReadWithRelationships(ProductTypeRead): """Schema for reading product type information with all relationships.""" - products: list[ProductRead] = Field( - default_factory=list, description="List of products that have this product type" - ) - categories: list[CategoryRead] = Field( - default_factory=list, description="List of categories linked to the product type" - ) - images: list[ImageRead] = Field(default_factory=list, description="List of images for the product type") - files: list[FileRead] = Field(default_factory=list, description="List of files for the product type") + products: list[ProductRead] = Field(default_factory=list) + categories: list[CategoryRead] = Field(default_factory=list) + images: list[ImageRead] = Field(default_factory=list) + files: list[FileRead] = Field(default_factory=list) ## Update Schemas ## class ProductTypeUpdate(BaseUpdateSchema): """Schema for a partial update of a product type.""" - name: str | None = Field(default=None, min_length=2, max_length=100, description="Name of the Product Type.") - description: str | None = Field(default=None, max_length=500, description="Description of the Product Type.") + name: str | None = Field(default=None, min_length=2, max_length=100) + description: str | None = Field(default=None, max_length=500) diff --git a/backend/app/api/common/crud/base.py b/backend/app/api/common/crud/base.py index 2864aaa..9b3e768 100644 --- a/backend/app/api/common/crud/base.py +++ b/backend/app/api/common/crud/base.py @@ -95,6 +95,10 @@ def get_models_query( statement = add_filter_joins(statement, model, model_filter) # Apply the filter statement = model_filter.filter(statement) + # Apply sorting if specified + # HACK: Inspect sort vars to see if any sorting is defined + if vars(model_filter.sort): + statement = model_filter.sort(statement) relationships_to_exclude = [] statement, relationships_to_exclude = add_relationship_options( diff --git a/backend/app/api/common/crud/utils.py b/backend/app/api/common/crud/utils.py index 8d051bd..c223a53 100644 --- a/backend/app/api/common/crud/utils.py +++ b/backend/app/api/common/crud/utils.py @@ -47,6 +47,8 @@ def add_relationship_options( """ # Get all relationships from the database model in one pass inspector: Mapper[Any] = inspect(model, raiseerr=True) + # HACK: Using SQLAlchemy internals to get relationship info. This sometimes causes runtime issues with circular model definitions. + # TODO: Fix this by finding a better way to get relationship info without using internals. all_db_rels = {rel.key: (getattr(model, rel.key), rel.uselist) for rel in inspector.relationships} # Determine which relationships are in scope (db ∩ schema) diff --git a/backend/app/api/common/models/associations.py b/backend/app/api/common/models/associations.py index e0da3a4..e19285c 100644 --- a/backend/app/api/common/models/associations.py +++ b/backend/app/api/common/models/associations.py @@ -34,8 +34,8 @@ class MaterialProductLink(MaterialProductLinkBase, TimeStampMixinBare, table=Tru foreign_key="product.id", primary_key=True, description="ID of the product with the material" ) - material: "Material" = Relationship(back_populates="product_links", sa_relationship_kwargs={"lazy": "selectin"}) - product: "Product" = Relationship(back_populates="bill_of_materials", sa_relationship_kwargs={"lazy": "selectin"}) + material: Material = Relationship(back_populates="product_links", sa_relationship_kwargs={"lazy": "selectin"}) + product: Product = Relationship(back_populates="bill_of_materials", sa_relationship_kwargs={"lazy": "selectin"}) def __str__(self) -> str: return f"{self.quantity} {self.unit} of {self.material.name} in {self.product.name}" diff --git a/backend/app/api/common/models/base.py b/backend/app/api/common/models/base.py index 1a43b9c..3d8bea9 100644 --- a/backend/app/api/common/models/base.py +++ b/backend/app/api/common/models/base.py @@ -4,13 +4,16 @@ from datetime import datetime from enum import Enum from functools import cached_property -from typing import Any, ClassVar, Generic, Self, TypeVar +from typing import TYPE_CHECKING, Any, ClassVar, Self, TypeVar from pydantic import BaseModel, ConfigDict, computed_field, model_validator from sqlalchemy import TIMESTAMP, func from sqlalchemy.dialects.postgresql import JSONB from sqlmodel import Column, Field, SQLModel +if TYPE_CHECKING: + from datetime import datetime + ### Base Model ### class APIModelName(BaseModel): @@ -102,15 +105,6 @@ def get_api_model_name(cls) -> APIModelName: class CustomBase(CustomBaseBare, SQLModel): """Base class for all models.""" - api_model_name: ClassVar[APIModelName | None] = None # The name of the model used in API routes - - @classmethod - def get_api_model_name(cls) -> APIModelName: - """Initialize api_model_name for the class.""" - if cls.api_model_name is None: - cls.api_model_name = APIModelName(name_camel=cls.__name__) - return cls.api_model_name - class CustomLinkingModelBase(CustomBase): """Base class for linking models.""" @@ -118,13 +112,6 @@ class CustomLinkingModelBase(CustomBase): # TODO: Separate schema and database model base classes. Schema models should inherit from Pydantic's BaseModel. # Database models should inherit from SQLModel. -class CustomDatabaseModelBase(CustomBase, SQLModel): - """Base class for models with database tables.""" - - id: int = Field( - default=None, - primary_key=True, - ) ### Mixins ### @@ -138,12 +125,12 @@ class TimeStampMixinBare: created_at: datetime | None = Field( default=None, - sa_type=TIMESTAMP(timezone=True), # pyright: ignore [reportArgumentType] # SQLModel mixins with SQLAlchemy Column specifications are complicated, see https://github.com/fastapi/sqlmodel/discussions/743 + sa_type=TIMESTAMP(timezone=True), sa_column_kwargs={"server_default": func.now()}, ) updated_at: datetime | None = Field( default=None, - sa_type=TIMESTAMP(timezone=True), # pyright: ignore [reportArgumentType] + sa_type=TIMESTAMP(timezone=True), sa_column_kwargs={"server_default": func.now(), "onupdate": func.now()}, ) @@ -160,7 +147,7 @@ class SingleParentMixin[ParentTypeEnum](SQLModel): parent_type: ParentTypeEnum # Type of the parent object. To be overridden by derived classes. - model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) @classmethod def get_parent_type_description(cls, enum_class: type[Enum]) -> str: diff --git a/backend/app/api/common/models/custom_fields.py b/backend/app/api/common/models/custom_fields.py deleted file mode 100644 index 343940e..0000000 --- a/backend/app/api/common/models/custom_fields.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Custom Pydantic fields for database models.""" - -from typing import Annotated - -from pydantic import AnyUrl, HttpUrl, PlainSerializer - -# HTTP URL that is stored as string in the database. -HttpUrlInDB = Annotated[HttpUrl, PlainSerializer(lambda x: str(x), return_type=str)] -AnyUrlInDB = Annotated[AnyUrl, PlainSerializer(lambda x: str(x), return_type=str)] diff --git a/backend/app/api/common/models/custom_types.py b/backend/app/api/common/models/custom_types.py index 392c009..e47467b 100644 --- a/backend/app/api/common/models/custom_types.py +++ b/backend/app/api/common/models/custom_types.py @@ -10,7 +10,7 @@ ### Type aliases ### # Type alias for ID types -IDT = int | UUID +IDT = TypeVar("IDT", bound=int | UUID) ### TypeVars ### # TypeVar for models diff --git a/backend/app/api/common/schemas/custom_fields.py b/backend/app/api/common/schemas/custom_fields.py new file mode 100644 index 0000000..7fd682e --- /dev/null +++ b/backend/app/api/common/schemas/custom_fields.py @@ -0,0 +1,9 @@ +"""Shared fields for DTO schemas.""" + +from typing import Annotated, TypeAlias + +from pydantic import AnyUrl, HttpUrl, PlainSerializer, StringConstraints + +# HTTP URL that is stored as string in the database. +HttpUrlToDB: TypeAlias = Annotated[HttpUrl, PlainSerializer(str, return_type=str), StringConstraints(max_length=250)] +AnyUrlToDB: TypeAlias = Annotated[AnyUrl, PlainSerializer(str, return_type=str), StringConstraints(max_length=250)] diff --git a/backend/app/api/data_collection/crud.py b/backend/app/api/data_collection/crud.py index 52d7570..2532afb 100644 --- a/backend/app/api/data_collection/crud.py +++ b/backend/app/api/data_collection/crud.py @@ -29,8 +29,10 @@ MaterialProductLinkUpdate, ) from app.api.data_collection.filters import ProductFilterWithRelationships -from app.api.data_collection.models import PhysicalProperties, Product +from app.api.data_collection.models import CircularityProperties, PhysicalProperties, Product from app.api.data_collection.schemas import ( + CircularityPropertiesCreate, + CircularityPropertiesUpdate, ComponentCreateWithComponents, PhysicalPropertiesCreate, PhysicalPropertiesUpdate, @@ -122,6 +124,72 @@ async def delete_physical_properties(db: AsyncSession, product: Product) -> None await db.commit() +### CircularityProperty CRUD operations ### +async def get_circularity_properties(db: AsyncSession, product_id: int) -> CircularityProperties: + """Get circularity properties for a product.""" + product: Product = await db_get_model_with_id_if_it_exists(db, Product, product_id) + + if not product.circularity_properties: + err_msg: str = f"Circularity properties for product with id {product_id} not found" + raise ValueError(err_msg) + + return product.circularity_properties + + +async def create_circularity_properties( + db: AsyncSession, + circularity_properties: CircularityPropertiesCreate, + product_id: int, +) -> CircularityProperties: + """Create circularity properties for a product.""" + # Validate that product exists and doesn't have circularity properties + product: Product = await db_get_model_with_id_if_it_exists(db, Product, product_id) + if product.circularity_properties: + err_msg: str = f"Product with id {product_id} already has circularity properties" + raise ValueError(err_msg) + + # Create circularity properties + db_circularity_property = CircularityProperties( + **circularity_properties.model_dump(), + product_id=product_id, + ) + db.add(db_circularity_property) + await db.commit() + await db.refresh(db_circularity_property) + + return db_circularity_property + + +async def update_circularity_properties( + db: AsyncSession, product_id: int, circularity_properties: CircularityPropertiesUpdate +) -> CircularityProperties: + """Update circularity properties for a product.""" + # Validate that product exists and has circularity properties + product: Product = await db_get_model_with_id_if_it_exists(db, Product, product_id) + if not (db_circularity_properties := product.circularity_properties): + err_msg: EmailStr = f"Circularity properties for product with id {product_id} not found" + raise ValueError(err_msg) + + circularity_properties_data: dict[str, Any] = circularity_properties.model_dump(exclude_unset=True) + db_circularity_properties.sqlmodel_update(circularity_properties_data) + + db.add(db_circularity_properties) + await db.commit() + await db.refresh(db_circularity_properties) + return db_circularity_properties + + +async def delete_circularity_properties(db: AsyncSession, product: Product) -> None: + """Delete circularity properties for a product.""" + # Validate that product exists and has circularity properties + if not (db_circularity_properties := product.circularity_properties): + err_msg: EmailStr = f"Circularity properties for product with id {product.id} not found" + raise ValueError(err_msg) + + await db.delete(db_circularity_properties) + await db.commit() + + ### Product CRUD operations ### ## Basic CRUD operations ### async def get_product_trees( @@ -178,6 +246,7 @@ async def create_component( "components", "owner_id", "physical_properties", + "circularity_properties", "videos", "bill_of_materials", } @@ -185,7 +254,7 @@ async def create_component( db_component = Product( **component_data, parent_id=parent_product_id, - owner_id=owner_id, # pyright: ignore[reportArgumentType] # owner ID is guaranteed by database fetch above + owner_id=owner_id, ) db.add(db_component) await db.flush() # Assign component ID @@ -194,10 +263,17 @@ async def create_component( if component.physical_properties: db_physical_property = PhysicalProperties( **component.physical_properties.model_dump(), - product_id=db_component.id, # pyright: ignore[reportArgumentType] # component ID is guaranteed by database flush above + product_id=db_component.id, ) db.add(db_physical_property) + if component.circularity_properties: + db_circularity_property = CircularityProperties( + **component.circularity_properties.model_dump(), + product_id=db_component.id, + ) + db.add(db_circularity_property) + # Create videos if component.videos: for video in component.videos: @@ -215,7 +291,7 @@ async def create_component( # Create material-product links db.add_all( - MaterialProductLink(**material.model_dump(), product_id=db_component.id) # pyright: ignore[reportArgumentType] # product ID is guaranteed by database flush above + MaterialProductLink(**material.model_dump(), product_id=db_component.id) for material in component.bill_of_materials ) @@ -225,7 +301,7 @@ async def create_component( await create_component( db, subcomponent, - parent_product_id=db_component.id, # pyright: ignore[reportArgumentType] # component ID is guaranteed by database flush above + parent_product_id=db_component.id, # ty: ignore[invalid-argument-type] # component ID is guaranteed by database flush above owner_id=owner_id, _is_recursive_call=True, ) @@ -257,6 +333,7 @@ async def create_product( exclude={ "components", "physical_properties", + "circularity_properties", "videos", "bill_of_materials", } @@ -270,10 +347,17 @@ async def create_product( if product.physical_properties: db_physical_properties = PhysicalProperties( **product.physical_properties.model_dump(), - product_id=db_product.id, # pyright: ignore[reportArgumentType] # product ID is guaranteed by database flush above + product_id=db_product.id, ) db.add(db_physical_properties) + if product.circularity_properties: + db_circularity_properties = CircularityProperties( + **product.circularity_properties.model_dump(), + product_id=db_product.id, + ) + db.add(db_circularity_properties) + # Create videos if product.videos: for video in product.videos: @@ -291,7 +375,7 @@ async def create_product( # Create material-product links db.add_all( - MaterialProductLink(**material.model_dump(), product_id=db_product.id) # pyright: ignore[reportArgumentType] # product ID is guaranteed by database flush above + MaterialProductLink(**material.model_dump(), product_id=db_product.id) for material in product.bill_of_materials ) @@ -302,7 +386,7 @@ async def create_product( await create_component( db, component, - parent_product_id=db_product.id, # pyright: ignore[reportArgumentType] # component ID is guaranteed by database flush above + parent_product_id=db_product.id, # ty: ignore[invalid-argument-type] # component ID is guaranteed by database flush above owner_id=owner_id, _is_recursive_call=True, ) @@ -327,12 +411,17 @@ async def update_product( if product.product_type_id: await db_get_model_with_id_if_it_exists(db, ProductType, product.product_type_id) - product_data: dict[str, Any] = product.model_dump(exclude_unset=True, exclude={"physical_properties"}) + product_data: dict[str, Any] = product.model_dump( + exclude_unset=True, exclude={"physical_properties", "circularity_properties"} + ) db_product.sqlmodel_update(product_data) # Update properties - if isinstance(product, ProductUpdateWithProperties) and product.physical_properties: - await update_physical_properties(db, product_id, product.physical_properties) + if isinstance(product, ProductUpdateWithProperties): + if product.physical_properties: + await update_physical_properties(db, product_id, product.physical_properties) + if product.circularity_properties: + await update_circularity_properties(db, product_id, product.circularity_properties) db.add(db_product) await db.commit() diff --git a/backend/app/api/data_collection/dependencies.py b/backend/app/api/data_collection/dependencies.py index 666577a..b8bb5b7 100644 --- a/backend/app/api/data_collection/dependencies.py +++ b/backend/app/api/data_collection/dependencies.py @@ -38,7 +38,7 @@ async def get_user_owned_product( current_user: CurrentActiveVerifiedUserDep, ) -> Product: """Verify that the current user owns the specified product.""" - if product.owner_id == current_user.id: + if product.owner_id == current_user.id or current_user.is_superuser: return product raise UserOwnershipError(model_type=Product, model_id=product.id, user_id=current_user.id) from None diff --git a/backend/app/api/data_collection/filters.py b/backend/app/api/data_collection/filters.py index c01d801..47ed408 100644 --- a/backend/app/api/data_collection/filters.py +++ b/backend/app/api/data_collection/filters.py @@ -31,8 +31,8 @@ class Constants(Filter.Constants): class PhysicalPropertiesFilter(Filter): """FastAPI-filter class for Physical Properties filtering.""" - weight_kg__gte: float | None = None - weight_kg__lte: float | None = None + weight_g__gte: float | None = None + weight_g__lte: float | None = None height_cm__gte: float | None = None height_cm__lte: float | None = None width_cm__gte: float | None = None @@ -58,6 +58,11 @@ class ProductFilter(Filter): dismantling_time_start__lte: datetime | None = None dismantling_time_end__gte: datetime | None = None dismantling_time_end__lte: datetime | None = None + created_at__gte: datetime | None = None + created_at__lte: datetime | None = None + updated_at__gte: datetime | None = None + updated_at__lte: datetime | None = None + order_by: list[str] | None = None search: str | None = None diff --git a/backend/app/api/data_collection/models.py b/backend/app/api/data_collection/models.py index 9afe6fa..e626d21 100644 --- a/backend/app/api/data_collection/models.py +++ b/backend/app/api/data_collection/models.py @@ -35,7 +35,7 @@ def validate_start_and_end_time(start_time: datetime, end_time: datetime | None) class PhysicalPropertiesBase(CustomBase): """Base model to store physical properties of a product.""" - weight_kg: float | None = Field(default=None, gt=0) + weight_g: float | None = Field(default=None, gt=0) height_cm: float | None = Field(default=None, gt=0) width_cm: float | None = Field(default=None, gt=0) depth_cm: float | None = Field(default=None, gt=0) @@ -58,7 +58,36 @@ class PhysicalProperties(PhysicalPropertiesBase, TimeStampMixinBare, table=True) # One-to-one relationships product_id: int = Field(foreign_key="product.id") - product: "Product" = Relationship(back_populates="physical_properties") + product: Product = Relationship(back_populates="physical_properties") + + +class CircularityPropertiesBase(CustomBase): + """Base model to store circularity properties of a product.""" + + # Recyclability + recyclability_observation: str | None = Field(default=None, max_length=500) + recyclability_comment: str | None = Field(default=None, max_length=100) + recyclability_reference: str | None = Field(default=None, max_length=100) + + # Repairability + repairability_observation: str | None = Field(default=None, max_length=500) + repairability_comment: str | None = Field(default=None, max_length=100) + repairability_reference: str | None = Field(default=None, max_length=100) + + # Remanufacturability + remanufacturability_observation: str | None = Field(default=None, max_length=500) + remanufacturability_comment: str | None = Field(default=None, max_length=100) + remanufacturability_reference: str | None = Field(default=None, max_length=100) + + +class CircularityProperties(CircularityPropertiesBase, TimeStampMixinBare, table=True): + """Model to store circularity properties of a product.""" + + id: int | None = Field(default=None, primary_key=True) + + # One-to-one relationships + product_id: int = Field(foreign_key="product.id") + product: Product = Relationship(back_populates="circularity_properties") ### Product Model ### @@ -95,7 +124,7 @@ class Product(ProductBase, TimeStampMixinBare, table=True): # Self-referential relationship for hierarchy parent_id: int | None = Field(default=None, foreign_key="product.id") - parent: Optional["Product"] = Relationship( + parent: Optional["Product"] = Relationship( # noqa: UP037, UP045 # `Optional` and quotes needed for proper sqlalchemy mapping back_populates="components", sa_relationship_kwargs={ "uselist": False, @@ -105,7 +134,7 @@ class Product(ProductBase, TimeStampMixinBare, table=True): }, ) amount_in_parent: int | None = Field(default=None, description="Quantity within parent product") - components: list["Product"] | None = Relationship( + components: list[Product] | None = Relationship( back_populates="parent", cascade_delete=True, sa_relationship_kwargs={"lazy": "selectin", "join_depth": 1}, # Eagerly load linked parent product @@ -115,22 +144,31 @@ class Product(ProductBase, TimeStampMixinBare, table=True): physical_properties: PhysicalProperties | None = Relationship( back_populates="product", cascade_delete=True, sa_relationship_kwargs={"uselist": False, "lazy": "selectin"} ) + circularity_properties: CircularityProperties | None = Relationship( + back_populates="product", cascade_delete=True, sa_relationship_kwargs={"uselist": False, "lazy": "selectin"} + ) # Many-to-one relationships - files: list["File"] | None = Relationship(back_populates="product", cascade_delete=True) - images: list["Image"] | None = Relationship( + files: list[File] | None = Relationship(back_populates="product", cascade_delete=True) + images: list[Image] | None = Relationship( back_populates="product", cascade_delete=True, sa_relationship_kwargs={"lazy": "subquery"} ) - videos: list["Video"] | None = Relationship(back_populates="product", cascade_delete=True) + videos: list[Video] | None = Relationship(back_populates="product", cascade_delete=True) # One-to-many relationships owner_id: UUID4 = Field(foreign_key="user.id") - owner: "User" = Relationship( - back_populates="products", sa_relationship_kwargs={"uselist": False, "lazy": "selectin"} + owner: User = Relationship( + back_populates="products", + sa_relationship_kwargs={ + "uselist": False, + "lazy": "selectin", + "primaryjoin": "Product.owner_id == User.id", # HACK: Explicitly define join condition because of + "foreign_keys": "[Product.owner_id]", # pydantic / sqlmodel issues (see https://github.com/fastapi/sqlmodel/issues/1623) + }, ) product_type_id: int | None = Field(default=None, foreign_key="producttype.id") - product_type: "ProductType" = Relationship(back_populates="products", sa_relationship_kwargs={"uselist": False}) + product_type: ProductType = Relationship(back_populates="products", sa_relationship_kwargs={"uselist": False}) # Many-to-many relationships bill_of_materials: list[MaterialProductLink] | None = Relationship( @@ -156,7 +194,7 @@ def has_cycles(self) -> bool: """Check if the product hierarchy contains cycles.""" visited = set() - def visit(node: "Product") -> bool: + def visit(node: Product) -> bool: if node.id in visited: return True # Cycle detected visited.add(node.id) @@ -172,7 +210,7 @@ def visit(node: "Product") -> bool: def components_resolve_to_materials(self) -> bool: """Ensure all leaf components have a non-empty bill of materials.""" - def check(node: "Product") -> bool: + def check(node: Product) -> bool: if not node.components: # Leaf node if not node.bill_of_materials: @@ -261,7 +299,7 @@ async def traverse(product: Product, quantity_multiplier: float) -> None: await traverse(self, 1.0) return total_materials - model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) def __str__(self): return f"{self.name} (id: {self.id})" diff --git a/backend/app/api/data_collection/routers.py b/backend/app/api/data_collection/routers.py index c70f2dd..475a24b 100644 --- a/backend/app/api/data_collection/routers.py +++ b/backend/app/api/data_collection/routers.py @@ -45,10 +45,14 @@ get_user_owned_product_id, ) from app.api.data_collection.models import ( + CircularityProperties, PhysicalProperties, Product, ) from app.api.data_collection.schemas import ( + CircularityPropertiesCreate, + CircularityPropertiesRead, + CircularityPropertiesUpdate, ComponentCreateWithComponents, ComponentReadWithRecursiveComponents, PhysicalPropertiesCreate, @@ -61,11 +65,11 @@ ProductUpdate, ProductUpdateWithProperties, ) -from app.api.file_storage.crud import create_video, delete_video +from app.api.file_storage.crud import create_video, delete_video, update_video from app.api.file_storage.filters import VideoFilter from app.api.file_storage.models.models import Video from app.api.file_storage.router_factories import StorageRouteMethod, add_storage_routes -from app.api.file_storage.schemas import VideoCreateWithinProduct, VideoReadWithinProduct +from app.api.file_storage.schemas import VideoCreateWithinProduct, VideoReadWithinProduct, VideoUpdateWithinProduct if TYPE_CHECKING: from sqlmodel.sql._expression_select_cls import SelectOfScalar @@ -102,7 +106,7 @@ async def redirect_to_current_user_products( @user_product_router.get( "", - response_model=list[ProductReadWithRelationshipsAndFlatComponents], + response_model=Page[ProductReadWithRelationshipsAndFlatComponents], summary="Get products collected by a user", ) async def get_user_products( @@ -116,13 +120,14 @@ async def get_user_products( description="Relationships to include", openapi_examples={ "none": {"value": {}}, - "properties": {"value": {"physical_properties"}}, + "properties": {"value": {"physical_properties", "circularity_properties"}}, "materials": {"value": {"bill_of_materials"}}, "components": {"value": {"components"}}, "media": {"value": {"images", "videos", "files"}}, "all": { "value": { "physical_properties", + "circularity_properties", "images", "videos", "files", @@ -134,18 +139,29 @@ async def get_user_products( }, ), ] = None, + *, + include_components_as_base_products: Annotated[ + bool | None, + Query(description="Whether to include components as base products in the response"), + ] = None, ) -> Sequence[Product]: """Get products collected by a specific user.""" # NOTE: If needed, we can open up this endpoint to any user by removing this ownership check if user_id != current_user.id and not current_user.is_superuser: raise HTTPException(status_code=403, detail="Not authorized to view this user's products") - return await get_models( + statement = select(Product).where(Product.owner_id == user_id) + + if not include_components_as_base_products: + statement: SelectOfScalar[Product] = statement.where(Product.parent_id == None) + + return await get_paginated_models( session, Product, include_relationships=include, model_filter=product_filter, - statement=(select(Product).where(Product.owner_id == user_id)), + statement=statement, + read_schema=ProductReadWithRelationshipsAndFlatComponents, ) @@ -187,13 +203,14 @@ async def get_products( description="Relationships to include", openapi_examples={ "none": {"value": []}, - "properties": {"value": ["physical_properties"]}, + "properties": {"value": ["physical_properties", "circularity_properties"]}, "materials": {"value": ["bill_of_materials"]}, "media": {"value": ["images", "videos", "files"]}, "components": {"value": ["components"]}, "all": { "value": [ "physical_properties", + "circularity_properties", "images", "videos", "files", @@ -215,6 +232,7 @@ async def get_products( Relationships that can be included: - physical_properties: Physical measurements and attributes + - circularity_properties: Circularity properties (recyclability, repairability, remanufacturability) - images: Product images - videos: Product videos - files: Related documents @@ -228,9 +246,6 @@ async def get_products( else: statement: SelectOfScalar[Product] = select(Product).where(Product.parent_id == None) - if product_filter: - statement = product_filter.filter(statement) - return await get_paginated_models( session, Product, @@ -327,13 +342,14 @@ async def get_product( description="Relationships to include", openapi_examples={ "none": {"value": []}, - "properties": {"value": ["physical_properties"]}, + "properties": {"value": ["physical_properties", "circularity_properties"]}, "materials": {"value": ["bill_of_materials"]}, "media": {"value": ["images", "videos", "files"]}, "components": {"value": ["components"]}, "all": { "value": [ "physical_properties", + "circularity_properties", "images", "videos", "files", @@ -350,6 +366,7 @@ async def get_product( Relationships that can be included: - physical_properties: Physical measurements and attributes + - circularity_properties: Circularity properties (recyclability, repairability, remanufacturability) - images: Product images - videos: Product videos - files: Related documents @@ -383,7 +400,7 @@ async def create_product( "dismantling_time_end": "2025-09-22T16:30:45Z", "product_type_id": 1, "physical_properties": { - "weight_kg": 20, + "weight_g": 2000, "height_cm": 150, "width_cm": 70, "depth_cm": 50, @@ -392,8 +409,8 @@ async def create_product( {"url": "https://www.youtube.com/watch?v=123456789", "description": "Disassembly video"} ], "bill_of_materials": [ - {"quantity": 15, "unit": "kg", "material_id": 1}, - {"quantity": 5, "unit": "kg", "material_id": 2}, + {"quantity": 15, "unit": "g", "material_id": 1}, + {"quantity": 5, "unit": "g", "material_id": 2}, ], }, }, @@ -408,7 +425,7 @@ async def create_product( "dismantling_time_end": "2025-09-22T16:30:45Z", "product_type_id": 1, "physical_properties": { - "weight_kg": 20, + "weight_g": 20000, "height_cm": 150, "width_cm": 70, "depth_cm": 50, @@ -428,7 +445,7 @@ async def create_product( "amount_in_parent": 1, "product_type_id": 2, "physical_properties": { - "weight_kg": 5, + "weight_g": 5000, "height_cm": 50, "width_cm": 40, "depth_cm": 30, @@ -439,15 +456,15 @@ async def create_product( "description": "Seat cushion assembly", "amount_in_parent": 1, "physical_properties": { - "weight_kg": 2, + "weight_g": 2000, "height_cm": 10, "width_cm": 40, "depth_cm": 30, }, "product_type_id": 3, "bill_of_materials": [ - {"quantity": 1.5, "unit": "kg", "material_id": 1}, - {"quantity": 0.5, "unit": "kg", "material_id": 2}, + {"quantity": 1.5, "unit": "g", "material_id": 1}, + {"quantity": 0.5, "unit": "g", "material_id": 2}, ], } ], @@ -567,13 +584,14 @@ async def get_product_components( description="Relationships to include", openapi_examples={ "none": {"value": []}, - "properties": {"value": ["physical_properties"]}, + "properties": {"value": ["physical_properties", "circularity_properties"]}, "materials": {"value": ["bill_of_materials"]}, "media": {"value": ["images", "videos", "files"]}, "components": {"value": ["components"]}, "all": { "value": [ "physical_properties", + "circularity_properties", "images", "videos", "files", @@ -615,13 +633,14 @@ async def get_product_component( description="Relationships to include", openapi_examples={ "none": {"value": []}, - "properties": {"value": ["physical_properties"]}, + "properties": {"value": ["physical_properties", "circularity_properties"]}, "materials": {"value": ["bill_of_materials"]}, "media": {"value": ["images", "videos", "files"]}, "components": {"value": ["components"]}, "all": { "value": [ "physical_properties", + "circularity_properties", "images", "videos", "files", @@ -660,7 +679,7 @@ async def add_component_to_product( "name": "Seat Assembly", "description": "Chair seat component", "amount_in_parent": 1, - "bill_of_materials": [{"material_id": 1, "quantity": 0.5, "unit": "kg"}], + "bill_of_materials": [{"material_id": 1, "quantity": 0.5, "unit": "g"}], }, }, "nested": { @@ -675,7 +694,7 @@ async def add_component_to_product( "name": "Cushion", "description": "Foam cushion", "amount_in_parent": 1, - "bill_of_materials": [{"material_id": 2, "quantity": 0.3, "unit": "kg"}], + "bill_of_materials": [{"material_id": 2, "quantity": 0.3, "unit": "g"}], } ], }, @@ -776,6 +795,60 @@ async def delete_product_physical_properties( await crud.delete_physical_properties(session, product) +@product_router.get( + "/{product_id}/circularity_properties", + response_model=CircularityPropertiesRead, + summary="Get product circularity properties", +) +async def get_product_circularity_properties( + product_id: PositiveInt, session: AsyncSessionDep +) -> CircularityProperties: + """Get circularity properties for a product.""" + return await crud.get_circularity_properties(session, product_id) + + +@product_router.post( + "/{product_id}/circularity_properties", + response_model=CircularityPropertiesRead, + status_code=201, + summary="Create product circularity properties", +) +async def create_product_circularity_properties( + product: UserOwnedProductDep, + properties: CircularityPropertiesCreate, + session: AsyncSessionDep, +) -> CircularityProperties: + """Create circularity properties for a product.""" + return await crud.create_circularity_properties(session, properties, product.id) + + +@product_router.patch( + "/{product_id}/circularity_properties", + response_model=CircularityPropertiesRead, + summary="Update product circularity properties", +) +async def update_product_circularity_properties( + product: UserOwnedProductDep, + properties: CircularityPropertiesUpdate, + session: AsyncSessionDep, +) -> CircularityProperties: + """Update circularity properties for a product.""" + return await crud.update_circularity_properties(session, product.id, properties) + + +@product_router.delete( + "/{product_id}/circularity_properties", + status_code=204, + summary="Delete product circularity properties", +) +async def delete_product_circularity_properties( + product: UserOwnedProductDep, + session: AsyncSessionDep, +) -> None: + """Delete circularity properties for a product.""" + await crud.delete_circularity_properties(session, product) + + ## Product Video routers ## @product_router.get( "/{product_id}/videos", @@ -871,6 +944,25 @@ async def create_product_video( return await create_video(session, video, product_id=product.id) +@product_router.patch( + "/{product_id}/videos/{video_id}", + response_model=VideoReadWithinProduct, + summary="Update video by ID", +) +async def update_product_video( + product: UserOwnedProductDep, + video_id: PositiveInt, + video_update: VideoUpdateWithinProduct, + session: AsyncSessionDep, +) -> Video: + """Update a video associated with a specific product.""" + # Validate existence of product and video + await get_nested_model_by_id(session, Product, product.id, Video, video_id, "product_id") + + # Update video + return await update_video(session, video_id, video_update) + + @product_router.delete( "/{product_id}/videos/{video_id}", status_code=204, @@ -947,8 +1039,8 @@ async def add_materials_to_product( description="List of materials-product links to add to the product", examples=[ [ - {"material_id": 1, "quantity": 5, "unit": "kg"}, - {"material_id": 2, "quantity": 10, "unit": "kg"}, + {"material_id": 1, "quantity": 5, "unit": "g"}, + {"material_id": 2, "quantity": 10, "unit": "g"}, ] ], ), @@ -975,7 +1067,7 @@ async def add_material_to_product( MaterialProductLinkCreateWithinProductAndMaterial, Body( description="Material-product link details", - examples=[[{"quantity": 5, "unit": "kg"}]], + examples=[[{"quantity": 5, "unit": "g"}]], ), ], session: AsyncSessionDep, diff --git a/backend/app/api/data_collection/schemas.py b/backend/app/api/data_collection/schemas.py index 6306902..03a9d6a 100644 --- a/backend/app/api/data_collection/schemas.py +++ b/backend/app/api/data_collection/schemas.py @@ -27,6 +27,7 @@ ProductRead, ) from app.api.data_collection.models import ( + CircularityPropertiesBase, PhysicalPropertiesBase, ProductBase, ) @@ -75,7 +76,7 @@ class PhysicalPropertiesCreate(BaseCreateSchema, PhysicalPropertiesBase): """Schema for creating physical properties.""" model_config: ConfigDict = ConfigDict( - json_schema_extra={"examples": [{"weight_kg": 20, "height_cm": 150, "width_cm": 70, "depth_cm": 50}]} + json_schema_extra={"examples": [{"weight_g": 20000, "height_cm": 150, "width_cm": 70, "depth_cm": 50}]} ) @@ -83,14 +84,79 @@ class PhysicalPropertiesRead(BaseReadSchemaWithTimeStamp, PhysicalPropertiesBase """Schema for reading physical properties.""" model_config: ConfigDict = ConfigDict( - json_schema_extra={"examples": [{"id": 1, "weight_kg": 20, "height_cm": 150, "width_cm": 70, "depth_cm": 50}]} + json_schema_extra={"examples": [{"id": 1, "weight_g": 20000, "height_cm": 150, "width_cm": 70, "depth_cm": 50}]} ) class PhysicalPropertiesUpdate(BaseUpdateSchema, PhysicalPropertiesBase): """Schema for updating physical properties.""" - model_config: ConfigDict = ConfigDict(json_schema_extra={"examples": [{"weight_kg": 15, "height_cm": 120}]}) + model_config: ConfigDict = ConfigDict(json_schema_extra={"examples": [{"weight_g": 15000, "height_cm": 120}]}) + + +class CircularityPropertiesCreate(BaseCreateSchema, CircularityPropertiesBase): + """Schema for creating circularity properties.""" + + model_config: ConfigDict = ConfigDict( + json_schema_extra={ + "examples": [ + { + "recyclability_observation": "The product can be easily disassembled and materials separated", + "recyclability_comment": "High recyclability rating", + "recyclability_reference": "ISO 14021:2016", + "repairability_observation": "Components are modular and can be replaced individually", + "repairability_comment": "Good repairability score", + "repairability_reference": "EN 45554:2020", + "remanufacturability_observation": "Core components can be refurbished and reused", + "remanufacturability_comment": "Suitable for remanufacturing", + "remanufacturability_reference": "BS 8887-2:2009", + } + ] + } + ) + + +class CircularityPropertiesRead(BaseReadSchemaWithTimeStamp, CircularityPropertiesBase): + """Schema for reading circularity properties.""" + + model_config: ConfigDict = ConfigDict( + json_schema_extra={ + "examples": [ + { + "id": 1, + "recyclability_observation": "The product can be easily disassembled and materials separated", + "recyclability_comment": "High recyclability rating", + "recyclability_reference": "ISO 14021:2016", + "repairability_observation": "Components are modular and can be replaced individually", + "repairability_comment": "Good repairability score", + "repairability_reference": "EN 45554:2020", + "remanufacturability_observation": "Core components can be refurbished and reused", + "remanufacturability_comment": "Suitable for remanufacturing", + "remanufacturability_reference": "BS 8887-2:2009", + } + ] + } + ) + + +class CircularityPropertiesUpdate(BaseUpdateSchema, CircularityPropertiesBase): + """Schema for updating circularity properties.""" + + # Make all fields optional for updates + recyclability_observation: str | None = Field(default=None, max_length=500) + repairability_observation: str | None = Field(default=None, max_length=500) + remanufacturability_observation: str | None = Field(default=None, max_length=500) + + model_config: ConfigDict = ConfigDict( + json_schema_extra={ + "examples": [ + { + "recyclability_observation": "Updated observation on recyclability", + "recyclability_comment": "Updated comment", + } + ] + } + ) ### Product Schemas ### @@ -128,6 +194,9 @@ class ProductCreateWithRelationships(ProductCreateBase): physical_properties: PhysicalPropertiesCreate | None = Field( default=None, description="Physical properties of the product" ) + circularity_properties: CircularityPropertiesCreate | None = Field( + default=None, description="Circularity properties of the product" + ) videos: list[VideoCreateWithinProduct] = Field(default_factory=list, description="Disassembly videos") bill_of_materials: list[MaterialProductLinkCreateWithinProduct] = Field( @@ -150,7 +219,7 @@ class ProductCreateBaseProduct(ProductCreateWithRelationships): "dismantling_time_end": "2025-09-22T16:30:45Z", "product_type_id": 1, "physical_properties": { - "weight_kg": 20, + "weight_g": 20000, "height_cm": 150, "width_cm": 70, "depth_cm": 50, @@ -159,8 +228,8 @@ class ProductCreateBaseProduct(ProductCreateWithRelationships): {"url": "https://www.youtube.com/watch?v=123456789", "description": "Disassembly video"} ], "bill_of_materials": [ - {"quantity": 0.3, "unit": "kg", "material_id": 1}, - {"quantity": 0.1, "unit": "kg", "material_id": 2}, + {"quantity": 0.3, "unit": "g", "material_id": 1}, + {"quantity": 0.1, "unit": "g", "material_id": 2}, ], } ] @@ -187,7 +256,7 @@ class ComponentCreateWithComponents(ComponentCreate): """ # Recursive components - components: list["ComponentCreateWithComponents"] = Field( + components: list[ComponentCreateWithComponents] = Field( default_factory=list, description="Set of component products" ) @@ -222,6 +291,7 @@ class ProductReadWithProperties(ProductRead): """Schema for reading product information with all properties.""" physical_properties: PhysicalPropertiesRead | None = None + circularity_properties: CircularityPropertiesRead | None = None class ProductReadWithRelationships(ProductReadWithProperties): @@ -239,13 +309,13 @@ class ProductReadWithRelationships(ProductReadWithProperties): class ProductReadWithRelationshipsAndFlatComponents(ProductReadWithRelationships): """Schema for reading product information with one level of components.""" - components: list["ComponentRead"] = Field(default_factory=list, description="List of component products") + components: list[ComponentRead] = Field(default_factory=list, description="List of component products") class ComponentReadWithRecursiveComponents(ComponentRead): """Schema for reading product information with recursive components.""" - components: list["ComponentReadWithRecursiveComponents"] = Field( + components: list[ComponentReadWithRecursiveComponents] = Field( default_factory=list, description="List of component products" ) @@ -290,3 +360,4 @@ class ProductUpdateWithProperties(ProductUpdate): """Schema for a partial update of a product with properties.""" physical_properties: PhysicalPropertiesUpdate | None = None + circularity_properties: CircularityPropertiesUpdate | None = None diff --git a/backend/app/api/file_storage/crud.py b/backend/app/api/file_storage/crud.py index d07bcdb..2c90086 100644 --- a/backend/app/api/file_storage/crud.py +++ b/backend/app/api/file_storage/crud.py @@ -4,7 +4,7 @@ import uuid from collections.abc import Callable, Sequence from pathlib import Path -from typing import Any, Generic, TypeVar +from typing import Any, TypeVar from anyio import to_thread from fastapi import UploadFile @@ -30,6 +30,7 @@ VideoCreate, VideoCreateWithinProduct, VideoUpdate, + VideoUpdateWithinProduct, ) logger = logging.getLogger(__name__) @@ -107,7 +108,7 @@ async def create_file(db: AsyncSession, file_data: FileCreate) -> File: id=file_id, description=file_data.description, filename=original_filename, - file=file_data.file, # pyright: ignore [reportArgumentType] # Incoming UploadFile cannot be preemptively cast to FileType because of how FastAPI-storages works. + file=file_data.file, parent_type=file_data.parent_type, ) @@ -191,7 +192,7 @@ async def create_image(db: AsyncSession, image_data: ImageCreateFromForm | Image description=image_data.description, image_metadata=image_data.image_metadata, filename=original_filename, - file=image_data.file, # pyright: ignore [reportArgumentType] # Incoming UploadFile cannot be preemptively cast to FileType because of how FastAPI-storages works. + file=image_data.file, parent_type=image_data.parent_type, ) @@ -269,7 +270,7 @@ async def create_video( return db_video -async def update_video(db: AsyncSession, video_id: int, video: VideoUpdate) -> Video: +async def update_video(db: AsyncSession, video_id: int, video: VideoUpdate | VideoUpdateWithinProduct) -> Video: """Update an existing video in the database.""" db_video: Video = await db_get_model_with_id_if_it_exists(db, Video, video_id) diff --git a/backend/app/api/file_storage/models/custom_types.py b/backend/app/api/file_storage/models/custom_types.py index 602e7e2..40d2184 100644 --- a/backend/app/api/file_storage/models/custom_types.py +++ b/backend/app/api/file_storage/models/custom_types.py @@ -1,15 +1,17 @@ """Custom types for FastAPI Storages models.""" -from typing import Any, BinaryIO +from typing import TYPE_CHECKING, Any, BinaryIO from fastapi_storages import FileSystemStorage, StorageImage from fastapi_storages.integrations.sqlalchemy import FileType as _FileType from fastapi_storages.integrations.sqlalchemy import ImageType as _ImageType -from sqlalchemy import Dialect from app.api.file_storage.exceptions import FastAPIStorageFileNotFoundError from app.core.config import settings +if TYPE_CHECKING: + from sqlalchemy import Dialect + ## Custom error handling for file not found in storage class CustomFileSystemStorage(FileSystemStorage): diff --git a/backend/app/api/file_storage/models/models.py b/backend/app/api/file_storage/models/models.py index ae78b78..fe7622d 100644 --- a/backend/app/api/file_storage/models/models.py +++ b/backend/app/api/file_storage/models/models.py @@ -10,11 +10,10 @@ from markupsafe import Markup from pydantic import UUID4, ConfigDict from sqlalchemy.dialects.postgresql import JSONB -from sqlmodel import AutoString, Column, Field, Relationship +from sqlmodel import Column, Field, Relationship from sqlmodel import Enum as SAEnum from app.api.common.models.base import APIModelName, CustomBase, SingleParentMixin, TimeStampMixinBare -from app.api.common.models.custom_fields import AnyUrlInDB from app.api.data_collection.models import Product from app.api.file_storage.exceptions import FastAPIStorageFileNotFoundError from app.api.file_storage.models.custom_types import FileType, ImageType @@ -49,7 +48,9 @@ class FileBase(CustomBase): class File(FileBase, TimeStampMixinBare, SingleParentMixin[FileParentType], table=True): """Database model for generic files stored in the local file system, using FastAPI-Storages.""" - id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True) + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + filename: str = Field(description="Original file name of the file. Automatically generated.") # TODO: Add custom file paths based on parent object (Product, year, etc.) @@ -64,16 +65,16 @@ class File(FileBase, TimeStampMixinBare, SingleParentMixin[FileParentType], tabl ) product_id: int | None = Field(default=None, foreign_key="product.id") - product: "Product" = Relationship(back_populates="files") + product: Product = Relationship(back_populates="files") material_id: int | None = Field(default=None, foreign_key="material.id") - material: "Material" = Relationship(back_populates="files") + material: Material = Relationship(back_populates="files") product_type_id: int | None = Field(default=None, foreign_key="producttype.id") - product_type: "ProductType" = Relationship(back_populates="files") + product_type: ProductType = Relationship(back_populates="files") # Model configuration - model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True, use_enum_values=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True, use_enum_values=True) @cached_property def file_url(self) -> str: @@ -111,7 +112,10 @@ class ImageBase(CustomBase): class Image(ImageBase, TimeStampMixinBare, SingleParentMixin, table=True): """Database model for images stored in the local file system, using FastAPI-Storages.""" - id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True) + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo + # TODO: To avoid this hack, for all database models, create a InDB child class that has non-optional id field + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + filename: str = Field(description="Original file name of the image. Automatically generated.", nullable=False) file: ImageType = Field( sa_column=Column(ImageType, nullable=False), @@ -125,16 +129,16 @@ class Image(ImageBase, TimeStampMixinBare, SingleParentMixin, table=True): ) product_id: int | None = Field(default=None, foreign_key="product.id") - product: "Product" = Relationship(back_populates="images") + product: Product = Relationship(back_populates="images") material_id: int | None = Field(default=None, foreign_key="material.id") - material: "Material" = Relationship(back_populates="images") + material: Material = Relationship(back_populates="images") product_type_id: int | None = Field(default=None, foreign_key="producttype.id") - product_type: "ProductType" = Relationship(back_populates="images") + product_type: ProductType = Relationship(back_populates="images") # Model configuration - model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) # pyright: ignore [reportIncompatibleVariableOverride] # This is not a type override, see https://github.com/fastapi/sqlmodel/discussions/855 + model_config: ConfigDict = ConfigDict(arbitrary_types_allowed=True) @cached_property def image_url(self) -> str: @@ -153,7 +157,7 @@ def image_preview(self, size: int = 100) -> str: class VideoBase(CustomBase): """Base model for videos stored online.""" - url: AnyUrlInDB = Field(description="URL linking to the video", sa_type=AutoString, nullable=False) + url: str = Field(description="URL linking to the video", nullable=False) title: str | None = Field(default=None, max_length=100, description="Title of the video") description: str | None = Field(default=None, max_length=500, description="Description of the video") video_metadata: dict[str, Any] | None = Field( diff --git a/backend/app/api/file_storage/schemas.py b/backend/app/api/file_storage/schemas.py index ab4bf4b..23b22be 100644 --- a/backend/app/api/file_storage/schemas.py +++ b/backend/app/api/file_storage/schemas.py @@ -3,10 +3,11 @@ from typing import Annotated, Any from fastapi import UploadFile -from pydantic import AfterValidator, Field, HttpUrl, Json, PositiveInt +from pydantic import AfterValidator, Field, Json, PositiveInt from app.api.common.models.custom_types import IDT from app.api.common.schemas.base import BaseCreateSchema, BaseReadSchemaWithTimeStamp, BaseUpdateSchema +from app.api.common.schemas.custom_fields import AnyUrlToDB from app.api.file_storage.models.models import FileBase, FileParentType, ImageBase, ImageParentType, VideoBase ### Constants ### @@ -171,15 +172,20 @@ class ImageUpdate(BaseUpdateSchema, ImageBase): class VideoCreateWithinProduct(BaseCreateSchema, VideoBase): """Schema for creating a video.""" + # Override url field to add validation + url: AnyUrlToDB + class VideoCreate(BaseCreateSchema, VideoBase): """Schema for creating a video.""" + # Override url field to add validation + url: AnyUrlToDB product_id: PositiveInt class VideoReadWithinProduct(BaseReadSchemaWithTimeStamp, VideoBase): - """Schema for reading video information.""" + """Schema for reading video information within a product.""" class VideoRead(BaseReadSchemaWithTimeStamp, VideoBase): @@ -188,11 +194,16 @@ class VideoRead(BaseReadSchemaWithTimeStamp, VideoBase): product_id: PositiveInt -class VideoUpdate(BaseUpdateSchema): - """Schema for updating a video.""" +class VideoUpdateWithinProduct(BaseUpdateSchema): + """Schema for updating a video within a product.""" - url: HttpUrl | None = Field(default=None, max_length=250, description="HTTP(S) URL linking to the video") + url: AnyUrlToDB | None = Field(default=None, description="URL linking to the video") title: str | None = Field(default=None, max_length=100, description="Title of the video") description: str | None = Field(default=None, max_length=500, description="Description of the video") video_metadata: dict[str, Any] | None = Field(default=None, description="Video metadata as a JSON dict") + + +class VideoUpdate(VideoUpdateWithinProduct): + """Schema for updating a video.""" + product_id: PositiveInt diff --git a/backend/app/api/newsletter/models.py b/backend/app/api/newsletter/models.py index 1bbdc23..5c16113 100644 --- a/backend/app/api/newsletter/models.py +++ b/backend/app/api/newsletter/models.py @@ -1,9 +1,8 @@ """Database models related to newsletter subscribers.""" import uuid -from typing import Annotated -from pydantic import UUID4, EmailStr, StringConstraints +from pydantic import UUID4, EmailStr from sqlmodel import Field from app.api.common.models.base import CustomBase, TimeStampMixinBare @@ -12,11 +11,12 @@ class NewsletterSubscriberBase(CustomBase): """Base schema for newsletter subscribers.""" - email: Annotated[EmailStr, StringConstraints(strip_whitespace=True)] = Field(index=True, unique=True) + email: EmailStr = Field(index=True, unique=True) class NewsletterSubscriber(NewsletterSubscriberBase, TimeStampMixinBare, table=True): """Database model for newsletter subscribers.""" - id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) is_confirmed: bool = Field(default=False) diff --git a/backend/app/api/newsletter/routers.py b/backend/app/api/newsletter/routers.py index 4483832..83f7b00 100644 --- a/backend/app/api/newsletter/routers.py +++ b/backend/app/api/newsletter/routers.py @@ -3,7 +3,7 @@ from collections.abc import Sequence from typing import Annotated -from fastapi import APIRouter, HTTPException, Security +from fastapi import APIRouter, BackgroundTasks, HTTPException, Security from fastapi.params import Body from pydantic import EmailStr from sqlmodel import select @@ -23,7 +23,9 @@ @backend_router.post("/subscribe", status_code=201, response_model=NewsletterSubscriberRead) -async def subscribe_to_newsletter(email: Annotated[EmailStr, Body()], db: AsyncSessionDep) -> NewsletterSubscriber: +async def subscribe_to_newsletter( + email: Annotated[EmailStr, Body()], db: AsyncSessionDep, background_tasks: BackgroundTasks +) -> NewsletterSubscriber: """Subscribe to the newsletter to receive updates about the app launch.""" # Check if the email already exists existing_subscriber = ( @@ -36,7 +38,7 @@ async def subscribe_to_newsletter(email: Annotated[EmailStr, Body()], db: AsyncS # If not confirmed, generate new token and send email token = create_jwt_token(email, JWTType.NEWSLETTER_CONFIRMATION) - await send_newsletter_subscription_email(email, token) + await send_newsletter_subscription_email(email, token, background_tasks=background_tasks) raise HTTPException( status_code=400, detail="Already subscribed, but not confirmed. A new confirmation email has been sent.", @@ -50,7 +52,7 @@ async def subscribe_to_newsletter(email: Annotated[EmailStr, Body()], db: AsyncS # Send confirmation email token = create_jwt_token(email, JWTType.NEWSLETTER_CONFIRMATION) - await send_newsletter_subscription_email(email, token) + await send_newsletter_subscription_email(email, token, background_tasks=background_tasks) return new_subscriber @@ -83,7 +85,9 @@ async def confirm_newsletter_subscription(token: Annotated[str, Body()], db: Asy @backend_router.post("/request-unsubscribe", status_code=200) -async def request_unsubscribe(email: Annotated[EmailStr, Body()], db: AsyncSessionDep) -> dict: +async def request_unsubscribe( + email: Annotated[EmailStr, Body()], db: AsyncSessionDep, background_tasks: BackgroundTasks +) -> dict: """Request to unsubscribe by sending an email with unsubscribe link.""" # Check if the email is subscribed existing_subscriber = ( @@ -98,7 +102,7 @@ async def request_unsubscribe(email: Annotated[EmailStr, Body()], db: AsyncSessi token = create_jwt_token(email, JWTType.NEWSLETTER_UNSUBSCRIBE) # Send unsubscription email with the link - await send_newsletter_unsubscription_request_email(email, token) + await send_newsletter_unsubscription_request_email(email, token, background_tasks=background_tasks) return {"message": "If you are subscribed, we've sent an unsubscribe link to your email."} diff --git a/backend/app/api/newsletter/schemas.py b/backend/app/api/newsletter/schemas.py index cacc5bd..0e237a0 100644 --- a/backend/app/api/newsletter/schemas.py +++ b/backend/app/api/newsletter/schemas.py @@ -1,6 +1,8 @@ """DTO schemas for newsletter subscribers.""" -from pydantic import Field +from typing import Annotated + +from pydantic import EmailStr, Field, StringConstraints from app.api.common.schemas.base import BaseCreateSchema, BaseReadSchemaWithTimeStamp from app.api.newsletter.models import NewsletterSubscriberBase @@ -9,6 +11,8 @@ class NewsletterSubscriberCreate(BaseCreateSchema, NewsletterSubscriberBase): """Create schema for newsletter subscribers.""" + email: Annotated[EmailStr, StringConstraints(strip_whitespace=True)] = Field() + class NewsletterSubscriberRead(BaseReadSchemaWithTimeStamp, NewsletterSubscriberBase): """Read schema for newsletter subscribers.""" diff --git a/backend/app/api/newsletter/utils/emails.py b/backend/app/api/newsletter/utils/emails.py index 32b4459..cc4d85c 100644 --- a/backend/app/api/newsletter/utils/emails.py +++ b/backend/app/api/newsletter/utils/emails.py @@ -1,71 +1,72 @@ """Email sending utilities for the newsletter service.""" -from app.api.auth.utils.programmatic_emails import TextContentType, generate_token_link, send_email +from fastapi import BackgroundTasks +from pydantic import EmailStr + +from app.api.auth.utils.programmatic_emails import generate_token_link, send_email_with_template from app.api.newsletter.utils.tokens import JWTType, create_jwt_token +from app.core.config import settings as core_settings -async def send_newsletter_subscription_email(to_email: str, token: str) -> None: +async def send_newsletter_subscription_email( + to_email: EmailStr, + token: str, + background_tasks: BackgroundTasks | None = None, +) -> None: """Send a newsletter subscription email.""" subject = "Reverse Engineering Lab: Confirm Your Newsletter Subscription" - # TODO: Dynamically generate the confirmation link based on the frontend URL tree - # Alternatively, send the frontend-side link to the backend as a parameter - confirmation_link = generate_token_link(token, "newsletter/confirm") - - body = f""" -Hello, - -Thank you for subscribing to the Reverse Engineering Lab newsletter! - -Please confirm your subscription by clicking [here]({confirmation_link}). - -This link will expire in 24 hours. - -We'll keep you updated with our progress and let you know when the full application is launched. - -Best regards, - -The Reverse Engineering Lab Team - """ - await send_email(to_email, subject, body, content_type=TextContentType.MARKDOWN) - - -async def send_newsletter(to_email: str, subject: str, content: str) -> None: - """Send newsletter with proper unsubscribe headers.""" + confirmation_link = generate_token_link(token, "newsletter/confirm", base_url=core_settings.frontend_web_url) + + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="newsletter_subscription.html", + template_body={ + "confirmation_link": confirmation_link, + }, + background_tasks=background_tasks, + ) + + +async def send_newsletter( + to_email: EmailStr, + subject: str, + content: str, + background_tasks: BackgroundTasks | None = None, +) -> None: + """Send newsletter with proper unsubscribe link.""" # Create unsubscribe token and link token = create_jwt_token(to_email, JWTType.NEWSLETTER_UNSUBSCRIBE) - unsubscribe_link = generate_token_link(token, "newsletter/unsubscribe") - - # Add footer with unsubscribe link - body = f""" - {content} - ---- -You're receiving this email because you subscribed to the Reverse Engineering Lab newsletter. -To unsubscribe, click [here]({unsubscribe_link}) - """ - - # Add List-Unsubscribe header for email clients that support it - headers = {"List-Unsubscribe": f"<{unsubscribe_link}>", "List-Unsubscribe-Post": "List-Unsubscribe=One-Click"} - - await send_email(to_email, subject, body, content_type=TextContentType.MARKDOWN, headers=headers) - - -async def send_newsletter_unsubscription_request_email(to_email: str, token: str) -> None: + unsubscribe_link = generate_token_link(token, "newsletter/unsubscribe", base_url=core_settings.frontend_web_url) + + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="newsletter.html", + template_body={ + "subject": subject, + "content": content, + "unsubscribe_link": unsubscribe_link, + }, + background_tasks=background_tasks, + ) + + +async def send_newsletter_unsubscription_request_email( + to_email: EmailStr, + token: str, + background_tasks: BackgroundTasks | None = None, +) -> None: """Send an email with unsubscribe link.""" subject = "Reverse Engineering Lab: Unsubscribe Request" - unsubscribe_link = generate_token_link(token, "newsletter/unsubscribe") - - body = f""" -Hello, - -We received a request to unsubscribe this email address from the Reverse Engineering Lab newsletter. - -If you made this request, please click [here]({unsubscribe_link}) to unsubscribe. - -If you did not request to unsubscribe, you can safely ignore this email. - -Best regards, - -The Reverse Engineering Lab Team - """ - await send_email(to_email, subject, body, content_type=TextContentType.MARKDOWN) + unsubscribe_link = generate_token_link(token, "newsletter/unsubscribe", base_url=core_settings.frontend_web_url) + + await send_email_with_template( + to_email=to_email, + subject=subject, + template_name="newsletter_unsubscribe.html", + template_body={ + "unsubscribe_link": unsubscribe_link, + }, + background_tasks=background_tasks, + ) diff --git a/backend/app/api/newsletter/utils/tokens.py b/backend/app/api/newsletter/utils/tokens.py index 04ba19c..f94bdf5 100644 --- a/backend/app/api/newsletter/utils/tokens.py +++ b/backend/app/api/newsletter/utils/tokens.py @@ -4,10 +4,12 @@ from enum import Enum import jwt +from pydantic import SecretStr from app.api.auth.config import settings ALGORITHM = "HS256" # Algorithm used for JWT encoding/decoding +SECRET: SecretStr = settings.newsletter_secret class JWTType(str, Enum): @@ -33,13 +35,13 @@ def create_jwt_token(email: str, token_type: JWTType) -> str: """Create a JWT token for newsletter confirmation.""" expiration = datetime.now(UTC) + timedelta(seconds=token_type.expiration_seconds) payload = {"sub": email, "exp": expiration, "type": token_type.value} - return jwt.encode(payload, settings.newsletter_secret, algorithm=ALGORITHM) + return jwt.encode(payload, SECRET.get_secret_value(), algorithm=ALGORITHM) def verify_jwt_token(token: str, expected_token_type: JWTType) -> str | None: """Verify the JWT token and return the email if valid.""" try: - payload = jwt.decode(token, settings.newsletter_secret, algorithms=[ALGORITHM]) + payload = jwt.decode(token, SECRET.get_secret_value(), algorithms=[ALGORITHM]) if payload["type"] != expected_token_type.value: return None return payload["sub"] # Returns the email address from the token diff --git a/backend/app/api/plugins/rpi_cam/config.py b/backend/app/api/plugins/rpi_cam/config.py index 4d40723..5088326 100644 --- a/backend/app/api/plugins/rpi_cam/config.py +++ b/backend/app/api/plugins/rpi_cam/config.py @@ -14,7 +14,7 @@ class RPiCamSettings(BaseSettings): # Authentication settings rpi_cam_plugin_secret: str = "" - # Initialize the settings configuration from the .env file + # Initialize the settings configuration from the .env file (or direct environment variables in Docker) model_config = SettingsConfigDict(env_file=BASE_DIR / ".env", extra="ignore") api_key_header_name: str = "X-API-Key" diff --git a/backend/app/api/plugins/rpi_cam/models.py b/backend/app/api/plugins/rpi_cam/models.py index 974fab6..fc8dc46 100644 --- a/backend/app/api/plugins/rpi_cam/models.py +++ b/backend/app/api/plugins/rpi_cam/models.py @@ -14,7 +14,6 @@ from sqlmodel import AutoString, Field, Relationship from app.api.common.models.base import CustomBase, TimeStampMixinBare -from app.api.common.models.custom_fields import HttpUrlInDB from app.api.plugins.rpi_cam.config import settings from app.api.plugins.rpi_cam.utils.encryption import decrypt_dict, decrypt_str, encrypt_dict @@ -68,23 +67,29 @@ class CameraBase(CustomBase): # NOTE: Local addresses only work when they are on the local network of this API # TODO: Add support for server communication to local network cameras for users via websocket or similar - # NOTE: Database models will have url as string type. This is likely because of how sa_type=Autostring works - # This means HttpUrl methods are not available in database model instances. - # TODO: Only validate the URL format in Pydantic schemas and store as plain string in the database model. - url: HttpUrlInDB = Field(description="HTTP(S) URL where the camera API is hosted", sa_type=AutoString) + # NOTE: URL validation is done in the Pydantic schemas (CameraCreate/CameraUpdate). + # The database stores it as a plain string. + url: str = Field(description="HTTP(S) URL where the camera API is hosted", sa_type=AutoString) class Camera(CameraBase, TimeStampMixinBare, table=True): """Database model for Camera.""" - id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True) + # HACK: Redefine id to allow None in the backend which is required by the > 2.12 pydantic/sqlmodel combo + id: UUID4 | None = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False) + encrypted_api_key: str = Field(nullable=False) # TODO: Consider merging encrypted_auth_headers and encrypted_api_key into a single encrypted_credentials field encrypted_auth_headers: str | None = Field(default=None) # Many-to-one relationship with User owner_id: UUID4 = Field(foreign_key="user.id") - owner: "User" = Relationship() # One-way relationship to maintain plugin isolation + owner: User = Relationship( # One-way relationship to maintain plugin isolation + sa_relationship_kwargs={ + "primaryjoin": "Camera.owner_id == User.id", + "foreign_keys": "[Camera.owner_id]", + } + ) @computed_field @cached_property diff --git a/backend/app/api/plugins/rpi_cam/routers/camera_interaction/utils.py b/backend/app/api/plugins/rpi_cam/routers/camera_interaction/utils.py index 178851e..cd5de42 100644 --- a/backend/app/api/plugins/rpi_cam/routers/camera_interaction/utils.py +++ b/backend/app/api/plugins/rpi_cam/routers/camera_interaction/utils.py @@ -1,16 +1,22 @@ """Utilities for the camera interaction endpoints.""" +import logging from enum import Enum +from typing import TYPE_CHECKING from urllib.parse import urljoin from fastapi import HTTPException -from httpx import AsyncClient, Headers, HTTPStatusError, QueryParams, Response +from httpx import AsyncClient, Headers, HTTPStatusError, QueryParams, RequestError, Response from pydantic import UUID4 from sqlmodel.ext.asyncio.session import AsyncSession from app.api.common.utils import get_user_owned_object from app.api.plugins.rpi_cam.models import Camera, CameraConnectionStatus +if TYPE_CHECKING: + from pydantic import UUID4 + from sqlmodel.ext.asyncio.session import AsyncSession + class HttpMethod(str, Enum): """HTTP method type.""" @@ -68,5 +74,16 @@ async def fetch_from_camera_url( status_code=e.response.status_code, detail={"main API": error_msg, "Camera API": e.response.json().get("detail")}, ) from e + except RequestError as e: + # Network-level errors (DNS, connection refused, timeouts). + logger = logging.getLogger(__name__) + logger.warning("Network error contacting camera %s%s: %s", camera.url, endpoint, e) + raise HTTPException( + status_code=503, + detail={ + "main API": f"Network error contacting camera: {endpoint}", + "error": str(e), + }, + ) from e else: return response diff --git a/backend/app/api/plugins/rpi_cam/schemas.py b/backend/app/api/plugins/rpi_cam/schemas.py index 18de973..1240978 100644 --- a/backend/app/api/plugins/rpi_cam/schemas.py +++ b/backend/app/api/plugins/rpi_cam/schemas.py @@ -9,7 +9,6 @@ AfterValidator, BaseModel, Field, - HttpUrl, PlainSerializer, SecretStr, ) @@ -20,6 +19,7 @@ BaseReadSchemaWithTimeStamp, BaseUpdateSchema, ) +from app.api.common.schemas.custom_fields import HttpUrlToDB from app.api.plugins.rpi_cam.config import settings from app.api.plugins.rpi_cam.models import Camera, CameraBase, CameraStatus from app.api.plugins.rpi_cam.utils.encryption import decrypt_str @@ -107,6 +107,8 @@ def validate_auth_headers_size(headers: list[HeaderCreate] | None) -> list[Heade class CameraCreate(BaseCreateSchema, CameraBase): """Schema for creating a camera.""" + # Override url field to add validation + url: HttpUrlToDB = Field(description="HTTP(S) URL where the camera API is hosted") auth_headers: OptionalAuthHeaderCreateList @@ -156,7 +158,7 @@ class CameraUpdate(BaseUpdateSchema): name: str | None = Field(default=None, min_length=2, max_length=100) description: str | None = Field(default=None, max_length=500) - url: HttpUrl | None = Field(default=None, description="HTTP(S) URL where the camera API is hosted") + url: HttpUrlToDB | None = Field(default=None, description="HTTP(S) URL where the camera API is hosted") auth_headers: OptionalAuthHeaderCreateList # TODO: Make it only possible to change ownership to existing users within the same organization diff --git a/backend/app/api/plugins/rpi_cam/services.py b/backend/app/api/plugins/rpi_cam/services.py index ca56db4..8040449 100644 --- a/backend/app/api/plugins/rpi_cam/services.py +++ b/backend/app/api/plugins/rpi_cam/services.py @@ -63,11 +63,11 @@ async def capture_and_store_image( image_data = ImageCreateInternal( file=UploadFile( file=BytesIO(image_response.content), - filename=filename if filename else f"{camera.name}_{serialize_datetime_with_z(datetime.now(UTC))}.jpg", + filename=filename or f"{camera.name}_{serialize_datetime_with_z(datetime.now(UTC))}.jpg", size=len(image_response.content), headers=Headers({"content-type": "image/jpeg"}), ), - description=(description if description else f"Captured from camera {camera.name} at {timestamp_str}."), + description=(description or f"Captured from camera {camera.name} at {timestamp_str}."), image_metadata=capture_data.get("metadata"), parent_type=ImageParentType.PRODUCT, parent_id=product_id, diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 7bf2740..04313c1 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -3,7 +3,7 @@ from functools import cached_property from pathlib import Path -from pydantic import EmailStr, HttpUrl, PostgresDsn, computed_field +from pydantic import EmailStr, HttpUrl, PostgresDsn, SecretStr, computed_field from pydantic_settings import BaseSettings, SettingsConfigDict # Set the project base directory and .env file @@ -17,16 +17,22 @@ class CoreSettings(BaseSettings): database_host: str = "localhost" database_port: int = 5432 postgres_user: str = "postgres" - postgres_password: str = "" + postgres_password: SecretStr = SecretStr("") postgres_db: str = "relab_db" postgres_test_db: str = "relab_test_db" + # Redis settings for caching + redis_host: str = "localhost" + redis_port: int = 6379 + redis_db: int = 0 + redis_password: SecretStr = SecretStr("") + # Debug settings debug: bool = False # Superuser settings superuser_email: EmailStr = "your-email@example.com" - superuser_password: str = "" + superuser_password: SecretStr = SecretStr("") # Network settings frontend_web_url: HttpUrl = HttpUrl("http://127.0.0.1:8000") @@ -49,7 +55,7 @@ class CoreSettings(BaseSettings): def _build_database_url(self, driver: str, database: str) -> str: """Build and validate PostgreSQL database URL.""" url = ( - f"postgresql+{driver}://{self.postgres_user}:{self.postgres_password}" + f"postgresql+{driver}://{self.postgres_user}:{self.postgres_password.get_secret_value()}" f"@{self.database_host}:{self.database_port}/{database}" ) PostgresDsn(url) # Validate URL format diff --git a/backend/app/core/utils/custom_logging.py b/backend/app/core/logging.py similarity index 100% rename from backend/app/core/utils/custom_logging.py rename to backend/app/core/logging.py diff --git a/backend/app/core/redis.py b/backend/app/core/redis.py new file mode 100644 index 0000000..a8a0973 --- /dev/null +++ b/backend/app/core/redis.py @@ -0,0 +1,135 @@ +"""Redis connection management.""" + +import logging +from typing import Any + +from fastapi import Request +from redis.asyncio import Redis +from redis.exceptions import RedisError + +from app.core.config import settings + +logger = logging.getLogger(__name__) + + +async def init_redis() -> Redis | None: + """Initialize Redis client instance with connection pooling. + + Returns: + Redis: Async Redis client with connection pooling, or None if connection fails + + This should be called once during application startup. + Gracefully handles connection failures and returns None if Redis is unavailable. + """ + try: + redis_client = Redis( + host=settings.redis_host, + port=settings.redis_port, + db=settings.redis_db, + password=settings.redis_password.get_secret_value() if settings.redis_password else None, + decode_responses=True, + socket_connect_timeout=5, + socket_timeout=5, + ) + + # Verify connection on startup + await redis_client.pubsub().ping() + logger.info("Redis client initialized and connected: %s:%s", settings.redis_host, settings.redis_port) + + except (TimeoutError, RedisError, OSError, ConnectionError) as e: + logger.warning( + "Failed to connect to Redis during initialization: %s. Application will continue without Redis.", e + ) + return None + else: + return redis_client + + +async def close_redis(redis_client: Redis) -> None: + """Close Redis connection and connection pool. + + Args: + redis_client: Redis client to close + + This properly closes all connections in the pool. + """ + if redis_client: + await redis_client.aclose() + logger.info("Redis connection pool closed") + + +async def ping_redis(redis_client: Redis) -> bool: + """Check if Redis is available (health check). + + Args: + redis_client: Redis client to ping + + Returns: + bool: True if Redis is responding, False otherwise + + This is useful for health check endpoints. + """ + try: + await redis_client.pubsub().ping() + except (TimeoutError, RedisError, OSError) as e: + logger.warning("Redis ping failed: %s", e) + return False + else: + return True + + +async def get_redis_value(redis_client: Redis, key: str) -> str | None: + """Get value from Redis. + + Args: + redis_client: Redis client + key: Redis key + + Returns: + Value as string, or None if not found + """ + try: + return await redis_client.get(key) + except (TimeoutError, RedisError, OSError): + logger.exception("Failed to get Redis value for key %s.", key) + return None + + +async def set_redis_value(redis_client: Redis, key: str, value: Any, ex: int | None = None) -> bool: + """Set value in Redis. + + Args: + redis_client: Redis client + key: Redis key + value: Value to store + ex: Expiration time in seconds (optional) + + Returns: + bool: True if successful, False otherwise + """ + try: + await redis_client.set(key, value, ex=ex) + except (TimeoutError, RedisError, OSError): + logger.exception("Failed to set Redis value for key %s.", key) + return False + else: + return True + + +def get_redis_dependency(request: Request) -> Redis | None: + """FastAPI dependency to get Redis client from app state. + + Args: + request: FastAPI request object + + Returns: + Redis client instance, or None if Redis is not available + + Usage: + @app.get("/example") + async def example(redis: Redis | None = Depends(get_redis_dependency)): + if redis is None: + raise HTTPException(status_code=503, detail="Redis is not available") + await redis.get("key") + """ + return request.app.state.redis diff --git a/backend/app/core/utils/__init__.py b/backend/app/core/utils/__init__.py deleted file mode 100644 index 9b957b1..0000000 --- a/backend/app/core/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Cross-package utility functions.""" diff --git a/backend/app/main.py b/backend/app/main.py index 8623cfe..8357c7b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,35 +1,88 @@ """Main application module for the Reverse Engineering Lab - Data collection API. This module initializes the FastAPI application, sets up the API routes, -mounts static and upload directories, and initializes the admin interface. +and mounts static and upload directories. """ +import logging +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING + from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi_pagination import add_pagination -from app.api.admin.main import init_admin +from app.api.auth.utils.email_validation import EmailChecker from app.api.common.routers.exceptions import register_exception_handlers from app.api.common.routers.main import router from app.api.common.routers.openapi import init_openapi_docs from app.core.config import settings -from app.core.database import async_engine -from app.core.utils.custom_logging import setup_logging +from app.core.logging import setup_logging +from app.core.redis import close_redis, init_redis + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator # Initialize logging setup_logging() +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(app: FastAPI) -> AsyncGenerator: + """Manage application lifespan: startup and shutdown events.""" + # Startup + logger.info("Starting up application...") + + # Initialize Redis connection and store in app.state + # The init_redis() function will verify the connection on startup and return None if it fails + app.state.redis = await init_redis() + + # Initialize disposable email checker and store in app.state + app.state.email_checker = None + try: + email_checker = EmailChecker(app.state.redis) + await email_checker.initialize() + app.state.email_checker = email_checker + except (RuntimeError, ValueError, ConnectionError) as e: + logger.warning("Failed to initialize email checker: %s", e) + + logger.info("Application startup complete") -# Initialize FastAPI application + yield + + # Shutdown + logger.info("Shutting down application...") + + # Close email checker (this will cancel background tasks) + if app.state.email_checker is not None: + try: + await app.state.email_checker.close() + except (RuntimeError, OSError) as e: + logger.warning("Error closing email checker: %s", e) + + # Close Redis connection + if app.state.redis is not None: + try: + await close_redis(app.state.redis) + except (ConnectionError, OSError) as e: + logger.warning("Error closing Redis: %s", e) + + logger.info("Application shutdown complete") + + +# Initialize FastAPI application with lifespan app = FastAPI( openapi_url=None, docs_url=None, redoc_url=None, + lifespan=lifespan, ) # Add CORS middleware app.add_middleware( - CORSMiddleware, + CORSMiddleware, # ty: ignore[invalid-argument-type] # Known false positive https://github.com/astral-sh/ty/issues/1635 allow_origins=settings.allowed_origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], @@ -42,9 +95,6 @@ # Initialize OpenAPI documentation init_openapi_docs(app) -# Initialize admin interface -admin = init_admin(app, async_engine) - # Mount local file storage app.mount("/uploads", StaticFiles(directory=settings.uploads_path), name="uploads") app.mount("/static", StaticFiles(directory=settings.static_files_path), name="static") diff --git a/backend/app/templates/emails/build/newsletter.html b/backend/app/templates/emails/build/newsletter.html new file mode 100644 index 0000000..5a11a13 --- /dev/null +++ b/backend/app/templates/emails/build/newsletter.html @@ -0,0 +1,259 @@ + + + + {{ subject }} + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+
{{ content }}
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + +
+

+ + +
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + +
+

+ + +
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/build/newsletter_subscription.html b/backend/app/templates/emails/build/newsletter_subscription.html new file mode 100644 index 0000000..74db9ce --- /dev/null +++ b/backend/app/templates/emails/build/newsletter_subscription.html @@ -0,0 +1,170 @@ + + + + Reverse Engineering Lab: Confirm Your Newsletter Subscription + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
Hello,
+
+
Thank you for subscribing to the Reverse Engineering Lab newsletter!
+
+
Please confirm your subscription by clicking the button below:
+
+ + + + +
+ Confirm Subscription +
+
+
+ Or copy and paste this link in your browser:
+ {{ confirmation_link }} +
+
+
This link will expire in 24 hours.
+
+
We'll keep you updated with our progress and let you know when the full application is launched.
+
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/build/newsletter_unsubscribe.html b/backend/app/templates/emails/build/newsletter_unsubscribe.html new file mode 100644 index 0000000..2bba8b1 --- /dev/null +++ b/backend/app/templates/emails/build/newsletter_unsubscribe.html @@ -0,0 +1,165 @@ + + + + Reverse Engineering Lab: Unsubscribe Request + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
Hello,
+
+
We received a request to unsubscribe this email address from the Reverse Engineering Lab newsletter.
+
+
If you made this request, please click the button below to unsubscribe:
+
+ + + + +
+ Unsubscribe +
+
+
+ Or copy and paste this link in your browser:
+ {{ unsubscribe_link }} +
+
+
If you did not request to unsubscribe, you can safely ignore this email.
+
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/build/password_reset.html b/backend/app/templates/emails/build/password_reset.html new file mode 100644 index 0000000..0974956 --- /dev/null +++ b/backend/app/templates/emails/build/password_reset.html @@ -0,0 +1,165 @@ + + + + Password Reset + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
Hello {{ username }},
+
+
Please reset your password by clicking the button below:
+
+ + + + +
+ Reset Password +
+
+
+ Or copy and paste this link in your browser:
+ {{ reset_link }} +
+
+
This link will expire in 1 hour.
+
+
If you did not request a password reset, please ignore this email.
+
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/build/post_verification.html b/backend/app/templates/emails/build/post_verification.html new file mode 100644 index 0000000..39f7850 --- /dev/null +++ b/backend/app/templates/emails/build/post_verification.html @@ -0,0 +1,141 @@ + + + + Email Verified + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + +
+
Hello {{ username }},
+
+
Your email has been verified!
+
+
Thank you for verifying your email address. You can now enjoy full access to all features.
+
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/build/registration.html b/backend/app/templates/emails/build/registration.html new file mode 100644 index 0000000..a37667d --- /dev/null +++ b/backend/app/templates/emails/build/registration.html @@ -0,0 +1,324 @@ + + + + Welcome to Reverse Engineering Lab - Verify Your Email + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+
Reverse Engineering Lab
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
Hello {{ username }},
+
+
Thank you for registering! Please verify your email by clicking the button below:
+
+ + + + +
+ Verify Email Address +
+
+
+ Or copy and paste this link in your browser:
+ {{ verification_link }} +
+
+
This link will expire in 1 hour.
+
+
If you did not register for this service, please ignore this email.
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + +
+

+ + +
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + +
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/build/verification.html b/backend/app/templates/emails/build/verification.html new file mode 100644 index 0000000..d71be70 --- /dev/null +++ b/backend/app/templates/emails/build/verification.html @@ -0,0 +1,165 @@ + + + + Email Verification + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
Hello {{ username }},
+
+
Please verify your email by clicking the button below:
+
+ + + + +
+ Verify Email Address +
+
+
+ Or copy and paste this link in your browser:
+ {{ verification_link }} +
+
+
This link will expire in 1 hour.
+
+
If you did not request verification, please ignore this email.
+
+
+ +
+
+ +
+ + diff --git a/backend/app/templates/emails/src/components/footer.mjml b/backend/app/templates/emails/src/components/footer.mjml new file mode 100644 index 0000000..19da9ed --- /dev/null +++ b/backend/app/templates/emails/src/components/footer.mjml @@ -0,0 +1,15 @@ + + + + + Best regards,
+ The Reverse Engineering Lab Team +
+
+
+ + + + This email was sent from Reverse Engineering Lab + + diff --git a/backend/app/templates/emails/src/components/header.mjml b/backend/app/templates/emails/src/components/header.mjml new file mode 100644 index 0000000..caafe50 --- /dev/null +++ b/backend/app/templates/emails/src/components/header.mjml @@ -0,0 +1,5 @@ + + + Reverse Engineering Lab + + diff --git a/backend/app/templates/emails/src/components/styles.mjml b/backend/app/templates/emails/src/components/styles.mjml new file mode 100644 index 0000000..a564039 --- /dev/null +++ b/backend/app/templates/emails/src/components/styles.mjml @@ -0,0 +1,9 @@ + + + + + + + .header { font-size: 24px; font-weight: bold; color: #007bff; } .footer { font-size: 12px; color: #666666; } .success + { font-size: 18px; color: #28a745; font-weight: bold; } + diff --git a/backend/app/templates/emails/src/newsletter.mjml b/backend/app/templates/emails/src/newsletter.mjml new file mode 100644 index 0000000..274d6d4 --- /dev/null +++ b/backend/app/templates/emails/src/newsletter.mjml @@ -0,0 +1,35 @@ + + + {{subject}} + {{include:styles}} + + + {{include:header}} + + + + {{content}} + + + + + + + + Best regards,
+ The Reverse Engineering Lab Team +
+
+
+ + + + + + You're receiving this email because you subscribed to the Reverse Engineering Lab newsletter.
+ Unsubscribe +
+
+
+
+
diff --git a/backend/app/templates/emails/src/newsletter_subscription.mjml b/backend/app/templates/emails/src/newsletter_subscription.mjml new file mode 100644 index 0000000..89b0294 --- /dev/null +++ b/backend/app/templates/emails/src/newsletter_subscription.mjml @@ -0,0 +1,28 @@ + + + Reverse Engineering Lab: Confirm Your Newsletter Subscription + {{include:styles}} + + + {{include:header}} + + + + Hello, + Thank you for subscribing to the Reverse Engineering Lab newsletter! + Please confirm your subscription by clicking the button below: + Confirm Subscription + + Or copy and paste this link in your browser:
+ {{confirmation_link}} +
+ This link will expire in 24 hours. + + We'll keep you updated with our progress and let you know when the full application is launched. + +
+
+ + {{include:footer}} +
+
diff --git a/backend/app/templates/emails/src/newsletter_unsubscribe.mjml b/backend/app/templates/emails/src/newsletter_unsubscribe.mjml new file mode 100644 index 0000000..05f2d4b --- /dev/null +++ b/backend/app/templates/emails/src/newsletter_unsubscribe.mjml @@ -0,0 +1,30 @@ + + + Reverse Engineering Lab: Unsubscribe Request + {{include:styles}} + + + + + + {{include:header}} + + + + Hello, + + We received a request to unsubscribe this email address from the Reverse Engineering Lab newsletter. + + If you made this request, please click the button below to unsubscribe: + Unsubscribe + + Or copy and paste this link in your browser:
+ {{unsubscribe_link}} +
+ If you did not request to unsubscribe, you can safely ignore this email. +
+
+ + {{include:footer}} +
+
diff --git a/backend/app/templates/emails/src/password_reset.mjml b/backend/app/templates/emails/src/password_reset.mjml new file mode 100644 index 0000000..0ec11a7 --- /dev/null +++ b/backend/app/templates/emails/src/password_reset.mjml @@ -0,0 +1,25 @@ + + + Password Reset + {{include:styles}} + + + {{include:header}} + + + + Hello {{username}}, + Please reset your password by clicking the button below: + Reset Password + + Or copy and paste this link in your browser:
+ {{reset_link}} +
+ This link will expire in 1 hour. + If you did not request a password reset, please ignore this email. +
+
+ + {{include:footer}} +
+
diff --git a/backend/app/templates/emails/src/post_verification.mjml b/backend/app/templates/emails/src/post_verification.mjml new file mode 100644 index 0000000..8961c8b --- /dev/null +++ b/backend/app/templates/emails/src/post_verification.mjml @@ -0,0 +1,19 @@ + + + Email Verified + {{include:styles}} + + + {{include:header}} + + + + Hello {{username}}, + Your email has been verified! + Thank you for verifying your email address. You can now enjoy full access to all features. + + + + {{include:footer}} + + diff --git a/backend/app/templates/emails/src/registration.mjml b/backend/app/templates/emails/src/registration.mjml new file mode 100644 index 0000000..437203c --- /dev/null +++ b/backend/app/templates/emails/src/registration.mjml @@ -0,0 +1,50 @@ + + + Welcome to Reverse Engineering Lab - Verify Your Email + + + + + + + .header { font-size: 24px; font-weight: bold; color: #007bff; } .footer { font-size: 12px; color: #666666; } + + + + + + Reverse Engineering Lab + + + + + + Hello {{ username }}, + Thank you for registering! Please verify your email by clicking the button below: + Verify Email Address + + Or copy and paste this link in your browser:
+ {{ verification_link }} +
+ This link will expire in 1 hour. + If you did not register for this service, please ignore this email. +
+
+ + + + + + Best regards,
+ The Reverse Engineering Lab Team +
+
+
+ + + + This email was sent from Reverse Engineering Lab + + +
+
diff --git a/backend/app/templates/emails/src/verification.mjml b/backend/app/templates/emails/src/verification.mjml new file mode 100644 index 0000000..cc324a5 --- /dev/null +++ b/backend/app/templates/emails/src/verification.mjml @@ -0,0 +1,25 @@ + + + Email Verification + {{include:styles}} + + + {{include:header}} + + + + Hello {{username}}, + Please verify your email by clicking the button below: + Verify Email Address + + Or copy and paste this link in your browser:
+ {{verification_link}} +
+ This link will expire in 1 hour. + If you did not request verification, please ignore this email. +
+
+ + {{include:footer}} +
+
diff --git a/backend/app/templates/index.html b/backend/app/templates/index.html index 7ddf4a8..005923a 100644 --- a/backend/app/templates/index.html +++ b/backend/app/templates/index.html @@ -25,13 +25,6 @@

API Documentation

{% endif %} - {% if show_full_docs %} -
-

Administration

- Admin Dashboard -
- {% endif %} -

API Login

{% if user %} diff --git a/backend/app/templates/login.html b/backend/app/templates/login.html index 3adfb68..1fb1f6d 100644 --- a/backend/app/templates/login.html +++ b/backend/app/templates/login.html @@ -35,7 +35,7 @@

Login

const errorDiv = document.getElementById('error') const nextInput = document.getElementById('next') const nextValue = nextInput ? nextInput.value : null - + try { const response = await fetch('/auth/cookie/login', { method: 'POST', @@ -49,7 +49,7 @@

Login

}), credentials: 'include' }) - + if (response.ok) { window.location.href = nextValue || '{{ url_for("index") }}' } else { diff --git a/backend/pyproject.toml b/backend/pyproject.toml index c5198a9..10482c0 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -21,13 +21,14 @@ readme = "README.md" ## Dependencies and version constraints - dependencies = [ # Core dependencies. - "aiosmtplib>=4.0.1", + dependencies = [ "asyncache>=0.3.1", + # TODO: Move to Python 3.14 once asyncpg supports it (version 0.31.0+, see https://github.com/MagicStack/asyncpg/issues/1282) "asyncpg>=0.30.0", "cachetools>=5.5.2", "email-validator>=2.2.0", "fastapi-filter>=2.0.1", + "fastapi-mail", "fastapi-pagination>=0.13.2", # NOTE: This is a heavy dependency (~40MB) due to its use of boto3, even though we don't use any cloud storage # We should consider using a more lightweight alternative if it becomes available. @@ -38,17 +39,18 @@ "fastapi-users[oauth,sqlalchemy]>=14.0.1", "fastapi[standard] >=0.115.14", "markdown>=3.8.2", + "mjml>=0.11.1", "pillow >=11.2.1", "psycopg[binary] >=3.2.9", - # TODO: Investigate pydantic v2.12 compatibility issues (might have to do with custom fastapi-users-db-sqlmodel fork) - "pydantic >=2.11,<2.12", + "pydantic >=2.12", "pydantic-extra-types >=2.10.5", "pydantic-settings >=2.10.1", "python-dotenv >=1.1.1", "python-slugify>=8.0.4", + "redis>=5.2.1", "relab-rpi-cam-models>=0.1.1", "sqlalchemy >=2.0.41", - "sqlmodel >=0.0.24", + "sqlmodel >=0.0.27", "tldextract>=5.3.0", ] requires-python = ">= 3.13" @@ -64,8 +66,8 @@ dev = [ # Development dependencies. See also https://docs.astral.sh/uv/concepts/dependencies/#development-dependencies "alembic-autogen-check >=1.1.1", "paracelsus>=0.9.0", - "pyright>=1.1.402", "ruff >=0.12.1", + "ty>=0.0.15", ] api = [ @@ -77,7 +79,6 @@ "google-auth>=2.40.3", "itsdangerous>=2.2.0", "markupsafe >=3.0.2", - "sqladmin >=0.20.1", ] migrations = ["alembic >=1.16.2", "alembic-postgresql-enum >=1.7.0", "openpyxl>=3.1.5", "pandas>=2.3.3"] @@ -103,20 +104,13 @@ "app.api.plugins.rpi_cam.models", ] -[tool.pyright] - # NOTE: Pyright doesn't work well by only setting exclude, so we explicitly include the directories we want to check - include = ["app", "scripts", "tests"] - typeCheckingMode = "standard" - venv = ".venv" - venvPath = "." - [tool.pytest.ini_options] asyncio_mode = "auto" [tool.ruff] fix = true line-length = 120 - target-version = "py313" + target-version = "py314" # Exclude automatically generated files from linting extend-exclude = ["./alembic/versions"] @@ -135,7 +129,6 @@ "C4", # flake8-comprehensions (fixes iterable comprehensions) "C90", # mccabe "D", # pydocstyle - "DJ", # flake8-django "DTZ", # flake8-datetimez (checks for naive datetime uses without timezone) "E", # pycodestyle errors "EM", # flake8-errmsgs (checks for error messages) @@ -247,5 +240,7 @@ default-groups = ["api", "dev", "migrations", "tests"] [tool.uv.sources] + # HACK: Fetch FastAPI-Mail from custom fork on GitHub to allow passing existing Redis client + fastapi-mail = { git = "https://github.com/simonvanlierde/fastapi-mail", rev = "6c6f04a7afaf3cdced82764009a2f1f2a3c3ee6c" } # Fetch FastAPI-Users-DB-SQLModel from custom fork on GitHub for Pydantic V2 support fastapi-users-db-sqlmodel = { git = "https://github.com/simonvanlierde/fastapi-users-db-sqlmodel", rev = "7e9c4830e53ee20c38e3de80066cb19d7c3efc43" } diff --git a/backend/scripts/backup/README.md b/backend/scripts/backup/README.md new file mode 100644 index 0000000..abeb00f --- /dev/null +++ b/backend/scripts/backup/README.md @@ -0,0 +1,136 @@ +# ReLab Data Backups + +Scripts for backing up ReLab data locally and syncing to remote storage. + +## Overview + +Two types of data are backed up: + +- **PostgreSQL database**: Compressed SQL dumps +- **User uploads**: Compressed tarballs of product images and files + +Backups are created locally first, then optionally synced to remote storage. + +______________________________________________________________________ + +## Local Backups + +### Configuration + +In the root [`.env`](../../../.env) file, set where backups are stored: + +```env +BACKUP_DIR=/path/to/local/backups +``` + +Ensure the directory exists and is writable by the Docker user. + +The backup scripts create subdirectories: + +- `$BACKUP_DIR/postgres_db`: PostgreSQL backups +- `$BACKUP_DIR/user_upload_backups`: User upload backups + +### Usage + +**Manual backup:** + +Run the backup scripts directly: + +```bash +./backup_user_uploads.sh +``` + +```bash +./backup_postgres_database.sh +``` + +> 💡 **Note:** By default these scripts back up services running on the host, not processes inside Docker containers. To back up Dockerized services, configure the scripts to back up the docker volume (for user uploads) or connect to the database container (for database backups). + +**Automated backup:** + +From the repo root, start the stack with the `backups` profile: + +```bash +docker compose -f compose.yml -f compose.prod.yml --profile backups up -d +``` + +This runs: + +- `backend_user_upload_backups`: Scheduled user upload backups, backed up to `$BACKUP_DIR/user_upload_backups` directory +- `database_backups`: Scheduled PostgreSQL backups, backed up to `$BACKUP_DIR/postgres_db` directory + +Backup schedules and retention policies are configured in [`compose.prod.yml`](../../../compose.prod.yml). + +______________________________________________________________________ + +## Remote Backups + +Optionally, you can sync local backups to remote storage using **rsync** (SSH/local network) or **rclone** (cloud/SFTP). Both scripts include safety checks to prevent data loss if the local directory is unexpectedly empty. + +### Option 1: rsync (SSH/Local Network) + +Ideal for fast local networks or SSH-accessible servers + +#### Prerequisites + +- SSH key-based authentication configured for the remote host +- `rsync` installed on both local and remote machines + +#### Configuration + +Add to root [`.env`](../../../.env) file: + +```env +BACKUP_RSYNC_REMOTE_HOST=user@hostname +BACKUP_RSYNC_REMOTE_PATH=/path/to/remote/backup +``` + +#### Usage + +**Manual sync:** + +```bash +./backend/scripts/backup/rsync_backup.sh +``` + +**Automated sync (cron):** + +```cron +# Daily at 3:30 AM +30 3 * * * /path/to/relab/backend/scripts/backup/rsync_backup.sh >> /var/log/relab/rsync_backup.log 2>&1 +``` + +______________________________________________________________________ + +### Option 2: rclone (Cloud/SFTP) + +Ideal for cloud storage (S3, SharePoint, Google Drive, etc.) + +#### Prerequisites + +- `rclone` installed +- Rclone remote configured with `rclone config` (SFTP, S3, SharePoint, etc.) + +#### Configuration + +Add to root [`.env`](../../../.env) file: + +```env +BACKUP_RCLONE_REMOTE=myremote:/backup/relab +BACKUP_RCLONE_MULTI_THREAD_STREAMS=16 # Optional: adjust for network speed +``` + +#### Usage + +**Manual sync:** + +```bash +./backend/scripts/backup/rclone_backup.sh +``` + +**Automated sync (cron):** + +```cron +# Daily at 3:30 AM +30 3 * * * /path/to/relab/backend/scripts/backup/rclone_backup.sh >> /var/log/relab/rclone_backup.log 2>&1 +``` diff --git a/backend/scripts/backup/backup_pg_database.sh b/backend/scripts/backup/backup_pg_database.sh new file mode 100755 index 0000000..c67e6f4 --- /dev/null +++ b/backend/scripts/backup/backup_pg_database.sh @@ -0,0 +1,63 @@ +#!/bin/sh +### Simple script to backup the postgres database manually +set -e + +# Load backend and root .env files +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PG_ENV_FILE="$SCRIPT_DIR/../../.env" +ROOT_ENV_FILE="$SCRIPT_DIR/../../../.env" + +if [ -f "$PG_ENV_FILE" ]; then + . "$PG_ENV_FILE" + echo "[$(date)] Loaded backend env file: $PG_ENV_FILE" +else + echo "[$(date)] ERROR: Backend env file not found at $PG_ENV_FILE. Aborting." + exit 1 +fi + +if [ -f "$ROOT_ENV_FILE" ]; then + . "$ROOT_ENV_FILE" + echo "[$(date)] Loaded root env file: $ROOT_ENV_FILE" +else + echo "[$(date)] INFO: Root env file not found at $ROOT_ENV_FILE. Skipping." +fi + +# Configuration +BACKUP_DIR_PG="${BACKUP_DIR:-$SCRIPT_DIR/../../backups}/postgres_db/manual" +DATABASE_HOST="${DATABASE_HOST:-localhost}" +DATABASE_PORT="${DATABASE_PORT:-5432}" +POSTGRES_USER="${POSTGRES_USER:-postgres}" +POSTGRES_PASSWORD="${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}" +POSTGRES_DB="${POSTGRES_DB:?POSTGRES_DB not set}" + +COMPRESSION="${POSTGRES_COMPRESSION:-zstd:3}" +SCHEMA="${POSTGRES_SCHEMA:-public}" +FILENAME="${POSTGRES_DB}-$(date +%Y%m%d-%H%M%S).sql.zst" + +# Wait for PostgreSQL +echo "[$(date)] Waiting for PostgreSQL..." +for i in $(seq 1 10); do + if PGPASSWORD="$POSTGRES_PASSWORD" pg_isready -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$POSTGRES_USER" -q; then + echo "[$(date)] PostgreSQL ready" + break + fi + [ "$i" -eq 10 ] && { echo "[$(date)] ERROR: PostgreSQL timeout"; exit 1; } + sleep 2 +done + +echo "Successfully connected to PostgreSQL." + +# Perform backup +mkdir -p "$BACKUP_DIR_PG" +echo "[$(date)] Backing up '$POSTGRES_DB' to $BACKUP_DIR_PG/$FILENAME" + +PGPASSWORD="$POSTGRES_PASSWORD" pg_dump \ + -h "$DATABASE_HOST" \ + -p "$DATABASE_PORT" \ + -U "$POSTGRES_USER" \ + --compress="$COMPRESSION" \ + --schema="$SCHEMA" \ + "$POSTGRES_DB" \ + > "$BACKUP_DIR_PG/$FILENAME" + +echo "[$(date)] Backup completed. Size: $(du -h "$BACKUP_DIR_PG/$FILENAME" | cut -f1)" diff --git a/backend/scripts/backup/rclone_backup.sh b/backend/scripts/backup/rclone_backup.sh new file mode 100755 index 0000000..2620d12 --- /dev/null +++ b/backend/scripts/backup/rclone_backup.sh @@ -0,0 +1,47 @@ +#!/bin/sh +### Rclone script to mirror a local backup directory to a remote server. +set -e + +# Load root .env file +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ENV_FILE="$SCRIPT_DIR/../../../.env" + +if [ -f "$ENV_FILE" ]; then + . "$ENV_FILE" + echo "[$(date)] Loaded env file: $ENV_FILE" +else + echo "[$(date)] ERROR: Env file not found at $ENV_FILE. Aborting." + exit 1 +fi + +# Configuration +BACKUP_DIR="${BACKUP_DIR:-$REPO_ROOT/backend/backups}" +BACKUP_RCLONE_REMOTE="${BACKUP_RCLONE_REMOTE?BACKUP_RCLONE_REMOTE not set}" +BACKUP_RCLONE_MULTI_THREAD_STREAMS="${BACKUP_RCLONE_MULTI_THREAD_STREAMS:-16}" +BACKUP_RCLONE_TIMEOUT="${BACKUP_RCLONE_TIMEOUT:-5m}" +BACKUP_RCLONE_USE_COOKIES="${BACKUP_RCLONE_USE_COOKIES:-false}" + +# Safety Check: If the local dir has 0 files AND the remote has more than 0 files, abort. +LOCAL_FILE_COUNT=$(find "$BACKUP_DIR" -type f | wc -l) +REMOTE_FILE_COUNT=$(rclone lsf "$BACKUP_RCLONE_REMOTE" --files-only --max-depth=3 2>/dev/null | wc -l) + +if [ "$LOCAL_FILE_COUNT" -eq 0 ] && [ "$REMOTE_FILE_COUNT" -gt 0 ]; then + echo "[$(date)] ERROR: Local backup directory is empty, but remote is not. Aborting sync to prevent data loss." + exit 1 +fi + +echo "[$(date)] Safety check passed. Syncing backups to $BACKUP_RCLONE_REMOTE..." +rclone sync "$BACKUP_DIR" "$BACKUP_RCLONE_REMOTE" \ + --multi-thread-streams="$BACKUP_RCLONE_MULTI_THREAD_STREAMS" \ + --links \ + --checksum \ + --transfers="$BACKUP_RCLONE_MULTI_THREAD_STREAMS" \ + --retries 3 \ + --low-level-retries 10 \ + --stats=30s \ + --stats-one-line-date \ + --timeout="$BACKUP_RCLONE_TIMEOUT" \ + --use-cookies="$BACKUP_RCLONE_USE_COOKIES" + +echo "[$(date)] Sync complete. Remote backup stats after sync:" +rclone size "$BACKUP_RCLONE_REMOTE" --max-depth=3 2>/dev/null | sed 's/^/ /' diff --git a/backend/scripts/backup/rsync_backup.sh b/backend/scripts/backup/rsync_backup.sh index 05e3537..2315fb4 100755 --- a/backend/scripts/backup/rsync_backup.sh +++ b/backend/scripts/backup/rsync_backup.sh @@ -16,17 +16,17 @@ fi # Configuration BACKUP_DIR="${BACKUP_DIR:-$REPO_ROOT/backend/backups}" -BACKUP_REMOTE_HOST="${BACKUP_REMOTE_HOST?BACKUP_REMOTE_HOST not set}" -BACKUP_REMOTE_DIR="${BACKUP_REMOTE_DIR?BACKUP_REMOTE_DIR not set}" +BACKUP_RSYNC_REMOTE_HOST="${BACKUP_RSYNC_REMOTE_HOST?BACKUP_RSYNC_REMOTE_HOST not set}" +BACKUP_RSYNC_REMOTE_DIR="${BACKUP_RSYNC_REMOTE_DIR?BACKUP_RSYNC_REMOTE_DIR not set}" # Safety Check: If the local dir has 0 files AND the remote has more than 0 files, abort. if [ "$(find "$BACKUP_DIR" -type f | wc -l)" -eq 0 ] && \ - [ "$(ssh "$BACKUP_REMOTE_HOST" "find '$BACKUP_REMOTE_DIR' -type f 2>/dev/null | wc -l")" -gt 0 ]; then + [ "$(ssh "$BACKUP_RSYNC_REMOTE_HOST" "find '$BACKUP_RSYNC_REMOTE_DIR' -type f 2>/dev/null | wc -l")" -gt 0 ]; then echo "[$(date)] ERROR: Local backup directory is empty, but remote is not. Aborting sync to prevent data loss." exit 1 fi -BACKUP_REMOTE="$BACKUP_REMOTE_HOST:$BACKUP_REMOTE_DIR" +BACKUP_REMOTE="$BACKUP_RSYNC_REMOTE_HOST:$BACKUP_RSYNC_REMOTE_DIR" echo "[$(date)] Safety check passed. Mirroring backups to $BACKUP_REMOTE..." rsync -avz --delete "$BACKUP_DIR"/ "$BACKUP_REMOTE" diff --git a/backend/scripts/compile_email_templates.py b/backend/scripts/compile_email_templates.py new file mode 100755 index 0000000..713e720 --- /dev/null +++ b/backend/scripts/compile_email_templates.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Compile MJML email templates to HTML. + +This script reads MJML templates from app/templates/emails/src/, +expands any {{include:component}} directives from src/components/, +compiles them to HTML, and saves the output to app/templates/emails/build/. +""" + +import logging +from pathlib import Path + +from mjml.mjml2html import mjml_to_html + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Paths +SCRIPT_DIR = Path(__file__).parent +BACKEND_DIR = SCRIPT_DIR.parent +SRC_DIR = BACKEND_DIR / "app" / "templates" / "emails" / "src" +BUILD_DIR = BACKEND_DIR / "app" / "templates" / "emails" / "build" + + +def compile_mjml_templates() -> None: + """Compile all MJML templates in src/ to HTML in build/.""" + if not SRC_DIR.exists(): + logger.error("Source directory not found: %s", SRC_DIR) + return + + # Create build directory if it doesn't exist + BUILD_DIR.mkdir(parents=True, exist_ok=True) + + # Find all MJML files + mjml_files = list(SRC_DIR.glob("*.mjml")) + + if not mjml_files: + logger.warning("No MJML files found in %s", SRC_DIR) + return + + logger.info("Found %d MJML template(s) to compile", len(mjml_files)) + + # Compile each template + for mjml_file in mjml_files: + try: + logger.info("Compiling %s...", mjml_file.name) + + # Read MJML content + mjml_content = mjml_file.read_text() + + # Compile to HTML + html_dotmap = mjml_to_html(mjml_content) + html_content = html_dotmap.html + + # Write HTML to build directory + html_file = BUILD_DIR / mjml_file.with_suffix(".html").name + html_file.write_text(html_content) + + logger.info(" ✓ Compiled to %s", html_file.name) + + except Exception: + logger.exception(" ✗ Failed to compile %s", mjml_file.name) + + logger.info("Compilation complete!") + + +if __name__ == "__main__": + compile_mjml_templates() diff --git a/backend/scripts/create_superuser.py b/backend/scripts/create_superuser.py index 1b044fe..c70b78a 100755 --- a/backend/scripts/create_superuser.py +++ b/backend/scripts/create_superuser.py @@ -6,11 +6,12 @@ import logging import anyio +from fastapi_users.exceptions import InvalidPasswordException, UserAlreadyExists + from app.api.auth.schemas import UserCreate from app.api.auth.utils.programmatic_user_crud import create_user from app.core.config import settings from app.core.database import get_async_session -from fastapi_users.exceptions import InvalidPasswordException, UserAlreadyExists # Set up logging logger: logging.Logger = logging.getLogger(__name__) @@ -35,7 +36,7 @@ async def create_superuser() -> None: async_session=async_session, user_create=UserCreate( email=superuser_email, - password=superuser_password, + password=superuser_password.get_secret_value(), organization_id=None, is_superuser=True, is_verified=True, diff --git a/backend/scripts/seed/dummy_data.py b/backend/scripts/seed/dummy_data.py index b084400..a4a5de1 100755 --- a/backend/scripts/seed/dummy_data.py +++ b/backend/scripts/seed/dummy_data.py @@ -4,15 +4,15 @@ import asyncio import contextlib +import io import logging import mimetypes from typing import TYPE_CHECKING +import anyio from fastapi import UploadFile -from sqlmodel.ext.asyncio.session import AsyncSession from starlette.datastructures import Headers -from app.api.auth.models import User from app.api.auth.schemas import UserCreate from app.api.auth.utils.programmatic_user_crud import create_user from app.api.background_data.models import ( @@ -38,12 +38,17 @@ if TYPE_CHECKING: from pathlib import Path + from sqlmodel.ext.asyncio.session import AsyncSession + + from app.api.auth.models import User + # Set up logging logger: logging.Logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) ### Sample Data ### # TODO: Add organization and Camera models + # Sample data for Users user_data = [ { @@ -144,7 +149,7 @@ "model": "A2403", "product_type_name": "Smartphone", "physical_properties": { - "weight_kg": 0.164, + "weight_g": 164, "height_cm": 14.7, "width_cm": 7.15, "depth_cm": 0.74, @@ -161,7 +166,7 @@ "model": "XPS9380", "product_type_name": "Laptop", "physical_properties": { - "weight_kg": 1.23, + "weight_g": 1230, "height_cm": 1.16, "width_cm": 30.2, "depth_cm": 19.9, @@ -174,10 +179,10 @@ ] # Sample data for Images -image_data = [ +image_data: list[dict[str, str]] = [ { "description": "Example phone image", - "path": settings.static_files_path / "images" / "example_phone.jpg", + "path": str(settings.static_files_path / "images" / "example_phone.jpg"), "parent_product_name": "iPhone 12", } ] @@ -295,14 +300,14 @@ async def seed_products( brand=data["brand"], model=data["model"], product_type_id=product_type.id, - owner_id=next(iter(user_map.values())).id, # pyright: ignore [reportArgumentType] # ID is guaranteed because these objects have been committed to the DB earlier. + owner_id=next(iter(user_map.values())).id, ) session.add(product) await session.commit() await session.refresh(product) # Ensures ID for product # Now create physical properties with product_id - physical_props = PhysicalProperties(**data["physical_properties"], product_id=product.id) # pyright: ignore [reportArgumentType] # ID is guaranteed because these objects have been committed to the DB earlier. + physical_props = PhysicalProperties(**data["physical_properties"], product_id=product.id) # ty: ignore[invalid-argument-type] # properties ID is guaranteed by database flush above session.add(physical_props) await session.commit() @@ -326,8 +331,8 @@ async def seed_products( async def seed_images(session: AsyncSession, product_map: dict[str, Product]) -> None: """Seed the database with initial image data.""" for data in image_data: - path: Path = data["path"] - description: str = data["description"] + path: Path = Path(data.get("path", None)) + description: str = data.get("description", "") parent_type = ImageParentType.PRODUCT parent = product_map.get(data["parent_product_name"]) @@ -338,34 +343,40 @@ async def seed_images(session: AsyncSession, product_map: dict[str, Product]) -> continue filename: str = path.name - size: int = path.stat().st_size + async_path = anyio.Path(path) + size: int = (await async_path.stat()).st_size mime_type, _ = mimetypes.guess_type(path) if mime_type is None: err_msg = f"Could not determine MIME type for image file {filename}." raise ValueError(err_msg) - with path.open("rb") as file: - upload_file = UploadFile( - file=file, - filename=filename, - size=size, - headers=Headers( - { - "filename": filename, - "size": str(size), - "content-type": mime_type, - } - ), - ) - - image_create = ImageCreateFromForm( - description=description, - file=upload_file, - parent_id=parent_id, - parent_type=parent_type, - ) - await create_image(session, image_create) + # Read file into memory + async with await async_path.open("rb") as file: + file_content = await file.read() + + # Create BytesIO object for UploadFile + file_obj = io.BytesIO(file_content) + upload_file = UploadFile( + file=file_obj, + filename=filename, + size=size, + headers=Headers( + { + "filename": filename, + "size": str(size), + "content-type": mime_type, + } + ), + ) + + image_create = ImageCreateFromForm( + description=description, + file=upload_file, + parent_id=parent_id, + parent_type=parent_type, + ) + await create_image(session, image_create) async def async_main() -> None: diff --git a/backend/scripts/seed/migrations_entrypoint.sh b/backend/scripts/seed/migrations_entrypoint.sh index e48e483..aeb72b7 100755 --- a/backend/scripts/seed/migrations_entrypoint.sh +++ b/backend/scripts/seed/migrations_entrypoint.sh @@ -3,8 +3,17 @@ # Exit immediately if a command exits with a non-zero status set -e +# Helper to lowercase a value (POSIX) +lc() { echo "$1" | tr '[:upper:]' '[:lower:]'; } + +# Defaults (so missing env vars behave as "false") +SEED_TAXONOMIES="${SEED_TAXONOMIES:-false}" +SEED_PRODUCT_TYPES="${SEED_PRODUCT_TYPES:-false}" +SEED_DUMMY_DATA="${SEED_DUMMY_DATA:-false}" +DEBUG="${DEBUG:-false}" + # Run Alembic migrations -if [ "$DEBUG" = "True" ]; then +if [ "$(lc "$DEBUG")" = "true" ]; then echo "Current migration status:" .venv/bin/alembic current fi @@ -12,30 +21,31 @@ fi echo "Upgrading database to the latest revision..." .venv/bin/alembic upgrade head -# Check if we should seed taxonomies -if [ "$SEED_TAXONOMIES" = "true" ]; then +# Seed taxonomies — run cpv once and pass the product-types flag if requested +if [ "$(lc "$SEED_TAXONOMIES")" = "true" ]; then echo "Seeding taxonomies..." - .venv/bin/python -m scripts.seed.taxonomies.cpv + if [ "$(lc "$SEED_PRODUCT_TYPES")" = "true" ]; then + .venv/bin/python -m scripts.seed.taxonomies.cpv --seed-product-types + else + .venv/bin/python -m scripts.seed.taxonomies.cpv + fi .venv/bin/python -m scripts.seed.taxonomies.harmonized_system fi -# Check if we should seed product types -if [ "$SEED_PRODUCT_TYPES" = "true" ]; then - echo "Seeding product types..." - .venv/bin/python -m scripts.seed.taxonomies.cpv --seed-product-types -fi - -# Check if all tables are empty -echo "Checking if all tables in the database are empty using scripts/db_is_empty.py..." - -# Run the script and temporarily disable exit-on-error to capture the exit code -DB_EMPTY=$(.venv/bin/python -m scripts.db_is_empty) - -if [ "$DB_EMPTY" = "TRUE" ]; then - echo "All tables are empty, proceeding to seed dummy data..." - .venv/bin/python -m scripts.seed.dummy_data +# Seed dummy data if enabled and if the database is empty +if [ "$(lc "$SEED_DUMMY_DATA")" = "true" ]; then + echo "Dummy data seeding is enabled." + echo "Checking if all tables in the database are empty using scripts/db_is_empty.py..." + DB_EMPTY=$(.venv/bin/python -m scripts.db_is_empty) + + if [ "$(lc "$DB_EMPTY")" = "true" ]; then + echo "All tables are empty, proceeding to seed dummy data..." + .venv/bin/python -m scripts.seed.dummy_data + else + echo "Database already has data seeding disabled, skipping." + fi else - echo "Database already has data, skipping seeding." + echo "Dummy data seeding is disabled." fi # Create a superuser if the required environment variables are set diff --git a/backend/scripts/seed/taxonomies/common.py b/backend/scripts/seed/taxonomies/common.py index 7e716fb..795c4a6 100644 --- a/backend/scripts/seed/taxonomies/common.py +++ b/backend/scripts/seed/taxonomies/common.py @@ -1,15 +1,16 @@ """Common utilities for seeding taxonomies and categories.""" import logging -from collections.abc import Callable -from typing import Any +from typing import TYPE_CHECKING, Any -from sqlalchemy import select -from sqlalchemy.orm import Session +from sqlmodel import select from app.api.background_data.models import Category, Taxonomy -logger = logging.getLogger("seeding.taxonomies") +if TYPE_CHECKING: + from collections.abc import Callable + + from sqlalchemy.orm import Session def configure_logging(level: int = logging.INFO) -> None: @@ -21,6 +22,9 @@ def configure_logging(level: int = logging.INFO) -> None: ) +logger = logging.getLogger("seeding.taxonomies.common") + + def get_or_create_taxonomy( session: Session, name: str, @@ -31,11 +35,12 @@ def get_or_create_taxonomy( ) -> Taxonomy: """Get existing taxonomy or create a new one.""" existing: Taxonomy | None = ( - session.execute(select(Taxonomy).where(Taxonomy.name == name, Taxonomy.version == version)).scalars().first() + session.execute(select(Taxonomy).where((Taxonomy.name == name) & (Taxonomy.version == version))) + .scalars() + .first() ) if existing: - logger.info("Taxonomy '%s' already exists (id: %s)", name, existing.id) return existing taxonomy = Taxonomy( @@ -53,7 +58,7 @@ def get_or_create_taxonomy( def seed_categories_from_rows( session: Session, - taxonomy: Taxonomy, + taxonomy_id: int, rows: list[dict[str, Any]], get_parent_id_fn: Callable[[dict[str, Any]], str | None], ) -> tuple[int, int]: @@ -61,14 +66,13 @@ def seed_categories_from_rows( Args: session: Database session - taxonomy: The taxonomy to add categories to + taxonomy_id: The taxonomy ID to add categories to (must be committed with non-None ID) rows: List of dictionaries with category data (must have 'external_id' and 'name') get_parent_id_fn: Function that takes a row and returns parent external_id or None Returns: Tuple of (categories_created, relationships_created) """ - # Build a map of external_id -> category for parent lookup id_to_category: dict[str, Category] = {} parent_relations: dict[str, str] = {} count = 0 @@ -82,7 +86,7 @@ def seed_categories_from_rows( category = Category( name=name, external_id=external_id, - taxonomy_id=taxonomy.id, + taxonomy_id=taxonomy_id, ) session.add(category) id_to_category[external_id] = category diff --git a/backend/scripts/seed/taxonomies/cpv.py b/backend/scripts/seed/taxonomies/cpv.py index 13fc9ed..16cccfc 100644 --- a/backend/scripts/seed/taxonomies/cpv.py +++ b/backend/scripts/seed/taxonomies/cpv.py @@ -10,9 +10,9 @@ import pandas as pd import requests -from sqlmodel import select +from sqlmodel import func, select -from app.api.auth.models import User # noqa: F401 # Need to explictly import User for SQLModel relationships +from app.api.auth.models import User # noqa: F401 # Need to explicitly import User for SQLModel relationships from app.api.background_data.models import ( Category, ProductType, # Adjust import as needed @@ -164,8 +164,18 @@ def seed_taxonomy(excel_path: Path = EXCEL_PATH) -> None: source=TAXONOMY_SOURCE, ) + if taxonomy.id is None: + # TODO: Refactor base models so that comitted database objects always have non-None ID to avoid this check + logger.error( + "Taxonomy '%s' version '%s' has no ID after creation, cannot seed categories.", + TAXONOMY_NAME, + TAXONOMY_VERSION, + ) + return + # If taxonomy already existed, skip seeding - existing_count = session.query(Category).filter_by(taxonomy_id=taxonomy.id).count() + existing_count = session.exec(select(func.count(Category.id)).where(Category.taxonomy_id == taxonomy.id)).one() + if existing_count > 0: logger.info("Taxonomy already has %d categories, skipping seeding", existing_count) return @@ -175,10 +185,10 @@ def seed_taxonomy(excel_path: Path = EXCEL_PATH) -> None: logger.info("Loaded %d CPV codes from Excel", len(rows)) # Seed categories - cat_count, rel_count = seed_categories_from_rows(session, taxonomy, rows, get_parent_id_fn=get_cpv_parent_id) + cat_count, rel_count = seed_categories_from_rows(session, taxonomy.id, rows, get_parent_id_fn=get_cpv_parent_id) # Commit - session.commit() + # session.commit() logger.info( "✓ Added %s taxonomy (version %s) with %d categories and %d relationships", TAXONOMY_NAME, diff --git a/backend/scripts/seed/taxonomies/harmonized_system.py b/backend/scripts/seed/taxonomies/harmonized_system.py index 7e96f31..95d273a 100644 --- a/backend/scripts/seed/taxonomies/harmonized_system.py +++ b/backend/scripts/seed/taxonomies/harmonized_system.py @@ -6,9 +6,10 @@ from typing import Any import pandas as pd +from sqlmodel import func, select # TODO: Fix circular import issue with User model in seeding scripts -from app.api.auth.models import User # noqa: F401 # Need to explictly import User for SQLModel relationships +from app.api.auth.models import User # noqa: F401 # Need to explicitly import User for SQLModel relationships from app.api.background_data.models import Category, TaxonomyDomain from app.core.database import sync_session_context from scripts.seed.taxonomies.common import configure_logging, get_or_create_taxonomy, seed_categories_from_rows @@ -96,8 +97,18 @@ def seed_taxonomy() -> None: source=TAXONOMY_SOURCE, ) + if taxonomy.id is None: + # TODO: Refactor base models so that comitted database objects always have non-None ID to avoid this check + logger.error( + "Taxonomy '%s' version '%s' has no ID after creation, cannot seed categories.", + TAXONOMY_NAME, + TAXONOMY_VERSION, + ) + return + # If taxonomy already existed, skip seeding - existing_count = session.query(Category).filter_by(taxonomy_id=taxonomy.id).count() + existing_count = session.exec(select(func.count(Category.id)).where(Category.taxonomy_id == taxonomy.id)).one() + if existing_count > 0: logger.info("Taxonomy already has %d categories, skipping seeding", existing_count) return @@ -106,7 +117,7 @@ def seed_taxonomy() -> None: rows = load_hs_rows_from_csv(CSV_PATH) # Seed categories - cat_count, rel_count = seed_categories_from_rows(session, taxonomy, rows, get_parent_id_fn=get_hs_parent_id) + cat_count, rel_count = seed_categories_from_rows(session, taxonomy.id, rows, get_parent_id_fn=get_hs_parent_id) # Commit session.commit() diff --git a/backend/tests/api/__init__.py b/backend/tests/api/__init__.py new file mode 100644 index 0000000..1c4bb23 --- /dev/null +++ b/backend/tests/api/__init__.py @@ -0,0 +1 @@ +"""API/E2E tests package.""" diff --git a/backend/tests/api/test_background_data_endpoints.py b/backend/tests/api/test_background_data_endpoints.py new file mode 100755 index 0000000..5a3e978 --- /dev/null +++ b/backend/tests/api/test_background_data_endpoints.py @@ -0,0 +1,213 @@ +"""API endpoint tests for background data (E2E tests).""" + +import pytest +from dirty_equals import IsInt, IsList, IsPositive, IsStr +from httpx import AsyncClient +from sqlalchemy.ext.asyncio import AsyncSession + +from app.api.background_data.models import Category, Taxonomy, TaxonomyDomain + + +@pytest.mark.api +class TestTaxonomyAPI: + """Test Taxonomy API endpoints.""" + + async def test_create_taxonomy(self, superuser_client: AsyncClient): + """Test POST /admin/taxonomies creates a taxonomy.""" + data = { + "name": "Test API Taxonomy", + "version": "v1.0.0", + "description": "Created via API", + "domains": ["materials"], + } + + response = await superuser_client.post("/admin/taxonomies", json=data) + + if response.status_code != 201: + print(f"\nResponse status: {response.status_code}") + print(f"Response Content: {response.text}") + assert response.status_code == 201 + json_data = response.json() + assert json_data["name"] == "Test API Taxonomy" + assert json_data["version"] == "v1.0.0" + assert "id" in json_data + assert "created_at" in json_data + + async def test_get_taxonomy(self, async_client: AsyncClient, db_taxonomy: Taxonomy): + """Test GET /taxonomies/{id} retrieves a taxonomy.""" + response = await async_client.get(f"/taxonomies/{db_taxonomy.id}") + + assert response.status_code == 200 + json_data = response.json() + assert json_data["id"] == db_taxonomy.id + assert json_data["name"] == db_taxonomy.name + + async def test_get_nonexistent_taxonomy(self, async_client: AsyncClient): + """Test GET /taxonomies/{id} with non-existent ID returns 404.""" + response = await async_client.get("/taxonomies/99999") + assert response.status_code == 404 + + async def test_list_taxonomies(self, async_client: AsyncClient, session: AsyncSession): + """Test GET /taxonomies returns list of taxonomies.""" + # Create a few taxonomies + for i in range(3): + taxonomy = Taxonomy( + name=f"Taxonomy {i}", + version=f"v{i}.0.0", + domains={TaxonomyDomain.MATERIALS}, + ) + session.add(taxonomy) + await session.flush() + + response = await async_client.get("/taxonomies") + + assert response.status_code == 200 + json_data = response.json() + assert isinstance(json_data, list) + assert len(json_data) >= 3 + + async def test_update_taxonomy(self, superuser_client: AsyncClient, db_taxonomy: Taxonomy): + """Test PATCH /admin/taxonomies/{id} updates a taxonomy.""" + update_data = { + "name": "Updated Taxonomy Name", + "version": "v2.0.0", + } + + response = await superuser_client.patch(f"/admin/taxonomies/{db_taxonomy.id}", json=update_data) + + if response.status_code != 200: + print(f"DEBUG: {response.json()}") + assert response.status_code == 200 + json_data = response.json() + assert json_data["name"] == "Updated Taxonomy Name" + assert json_data["version"] == "v2.0.0" + + async def test_delete_taxonomy(self, superuser_client: AsyncClient, db_taxonomy: Taxonomy): + """Test DELETE /admin/taxonomies/{id} deletes a taxonomy.""" + response = await superuser_client.delete(f"/admin/taxonomies/{db_taxonomy.id}") + + assert response.status_code == 204 + + # Verify it's deleted + get_response = await superuser_client.get(f"/taxonomies/{db_taxonomy.id}") + assert get_response.status_code == 404 + + +@pytest.mark.api +class TestCategoryAPI: + """Test Category API endpoints.""" + + async def test_create_category(self, superuser_client: AsyncClient, db_taxonomy: Taxonomy): + """Test POST /admin/categories creates a category.""" + data = { + "name": "Test API Category", + "description": "Created via API", + "taxonomy_id": db_taxonomy.id, + } + + response = await superuser_client.post("/admin/categories", json=data) + + assert response.status_code == 201 + json_data = response.json() + assert json_data["name"] == "Test API Category" + assert json_data["taxonomy_id"] == db_taxonomy.id + + async def test_get_category(self, async_client: AsyncClient, db_category: Category): + """Test GET /categories/{id} retrieves a category.""" + response = await async_client.get(f"/categories/{db_category.id}") + + assert response.status_code == 200 + json_data = response.json() + assert json_data["id"] == db_category.id + assert json_data["name"] == db_category.name + + async def test_create_category_with_subcategories(self, superuser_client: AsyncClient, db_taxonomy: Taxonomy): + """Test creating category with nested subcategories.""" + data = { + "name": "Parent Category", + "taxonomy_id": db_taxonomy.id, + "subcategories": [{"name": "Child Category", "subcategories": [{"name": "Grandchild Category"}]}], + } + + response = await superuser_client.post("/admin/categories", json=data) + + if response.status_code != 201: + print(f"DEBUG: {response.json()}") + assert response.status_code == 201 + json_data = response.json() + assert json_data["name"] == "Parent Category" + # Verify subcategories were created (depending on endpoint response structure) + + +@pytest.mark.api +class TestMaterialAPI: + """Test Material API endpoints.""" + + async def test_create_material(self, superuser_client: AsyncClient): + """Test POST /admin/materials creates a material.""" + data = { + "name": "Test API Material", + "description": "Created via API", + "density_kg_m3": 8000.0, + "is_crm": True, + } + + response = await superuser_client.post("/admin/materials", json=data) + + assert response.status_code == 201 + json_data = response.json() + assert json_data["name"] == "Test API Material" + assert json_data["density_kg_m3"] == 8000.0 + + async def test_create_material_with_invalid_density(self, superuser_client: AsyncClient): + """Test POST /admin/materials with negative density fails.""" + data = { + "name": "Invalid Material", + "density_kg_m3": -100.0, + } + + response = await superuser_client.post("/admin/materials", json=data) + assert response.status_code == 422 # Validation error + + +@pytest.mark.api +class TestProductTypeAPI: + """Test ProductType API endpoints.""" + + async def test_create_product_type(self, superuser_client: AsyncClient): + """Test POST /admin/product-types creates a product type.""" + data = { + "name": "Test API Product Type", + "description": "Created via API", + } + + response = await superuser_client.post("/admin/product-types", json=data) + + assert response.status_code == 201 + json_data = response.json() + assert json_data["name"] == "Test API Product Type" + + +@pytest.mark.api +@pytest.mark.slow +class TestAPIWithDirtyEquals: + """Example tests using dirty-equals for flexible assertions.""" + + async def test_taxonomy_response_structure(self, async_client: AsyncClient, db_taxonomy: Taxonomy): + """Test taxonomy response has expected structure using dirty-equals.""" + response = await async_client.get(f"/taxonomies/{db_taxonomy.id}") + + assert response.status_code == 200 + json_data = response.json() + + # Use dirty-equals for flexible type checking + assert json_data == { + "id": IsInt & IsPositive, + "name": IsStr, + "version": IsStr | None, + "description": IsStr | None, + "domains": ["materials"], + "source": IsStr | None, + "created_at": IsStr, + "updated_at": IsStr, + } diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 0a4732a..31e64fe 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,6 +1,14 @@ -"""Test configuration file. +"""Root test configuration with modern 2026 best practices. -Inspired by https://medium.com/@gnetkov/testing-fastapi-application-with-pytest-57080960fd62. +This conftest provides: +- Database setup with transaction isolation +- Async HTTP client using httpx (via plugins) +- Factory fixtures (via plugins) +- Common test utilities (via plugins) +- Mocking utilities via pytest-mock (mocker fixture auto-injected) + +Key Fixtures: +- session: Isolated async database session with transaction rollback """ import logging @@ -8,89 +16,148 @@ from pathlib import Path import pytest -from alembic import command from alembic.config import Config -from app.core.config import settings -from app.main import app -from fastapi.testclient import TestClient from sqlalchemy import Engine, create_engine, text from sqlalchemy.exc import ProgrammingError -from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine -from sqlalchemy.ext.asyncio.engine import AsyncEngine +from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine from sqlmodel.ext.asyncio.session import AsyncSession +from alembic import command +from app.core.config import settings + # Set up logger -logger: logging.Logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) + +# Register plugins for fixture discovery +pytest_plugins = [ + "tests.fixtures.client", + "tests.fixtures.data", + "tests.fixtures.database", +] + +# ============================================================================ +# Database Setup +# ============================================================================ -# Set up sync engine for test database construction +# Sync engine for database creation/destruction sync_engine: Engine = create_engine(settings.sync_database_url, isolation_level="AUTOCOMMIT") -# Set up an async test engine for the actual -TEST_SQLALCHEMY_DATABASE_URL: str = settings.async_test_database_url +# Async engine for tests +# Async engine for tests +TEST_DATABASE_URL: str = settings.async_test_database_url TEST_DATABASE_NAME: str = settings.postgres_test_db -async_engine: AsyncEngine = create_async_engine(TEST_SQLALCHEMY_DATABASE_URL, echo=settings.debug) +# Use NullPool to ensure connections are closed after each test and not reused across loops +from sqlalchemy.pool import NullPool + +async_engine: AsyncEngine = create_async_engine(TEST_DATABASE_URL, echo=False, future=True, poolclass=NullPool) async_session_local = async_sessionmaker( - bind=async_engine, autocommit=False, autoflush=False, class_=AsyncSession, expire_on_commit=False + bind=async_engine, + class_=AsyncSession, + autocommit=False, + autoflush=False, + expire_on_commit=False, ) -### Set up bare test database using sync engine def create_test_database() -> None: - """Create the test database if it doesn't exist.""" + """Create the test database. Recreate if it exists.""" with sync_engine.connect() as connection: - try: - connection.execute(text(f"CREATE DATABASE {TEST_DATABASE_NAME}")) - logger.info("Test database created successfully.") - except ProgrammingError: - logger.info("Test database already exists, continuing...") + # Terminate connections to allow drop + connection.execute( + text( + f""" + SELECT pg_terminate_backend(pg_stat_activity.pid) + FROM pg_stat_activity + WHERE pg_stat_activity.datname = '{TEST_DATABASE_NAME}' + AND pid <> pg_backend_pid(); + """ + ) + ) + connection.execute(text(f"DROP DATABASE IF EXISTS {TEST_DATABASE_NAME}")) + connection.execute(text(f"CREATE DATABASE {TEST_DATABASE_NAME}")) + logger.info("Test database created successfully.") def get_alembic_config() -> Config: - """Get Alembic config for tests.""" + """Get Alembic config for running migrations in tests.""" alembic_cfg = Config() project_root: Path = Path(__file__).parents[1] alembic_cfg.set_main_option("script_location", str(project_root / "alembic")) - alembic_cfg.set_main_option("sqlalchemy.url", TEST_SQLALCHEMY_DATABASE_URL) + alembic_cfg.set_main_option("script_location", str(project_root / "alembic")) + alembic_cfg.set_main_option("sqlalchemy.url", settings.sync_test_database_url) + alembic_cfg.set_main_option("is_test", "true") return alembic_cfg -@pytest.fixture(scope="session", autouse=True) -def setup_test_database() -> Generator: - """Create test database, run migrations, and cleanup after tests.""" - create_test_database() # Create empty database +@pytest.fixture(scope="session") +def setup_test_database() -> Generator[None]: + """Create test database and run migrations once per test session.""" + create_test_database() - # Run migrations + # Run migrations to latest alembic_cfg: Config = get_alembic_config() + print("Running Alembic upgrade head...") command.upgrade(alembic_cfg, "head") + print("Alembic upgrade complete.") yield - # Cleanup - with sync_engine.connect() as connection: - connection.execute(text("DROP DATABASE IF EXISTS " + TEST_DATABASE_NAME)) - - -### Async test session generators -@pytest.fixture(scope="function") -async def get_async_session() -> AsyncGenerator[AsyncSession]: - """Create a new database session for each test and roll it back after the test.""" - async with async_engine.begin() as connection, async_session_local(bind=connection) as session: - transaction = await connection.begin_nested() - yield session - await transaction.rollback() - + # Dispose async engine connections before dropping database + import asyncio -@pytest.fixture(scope="function") -async def client(db: AsyncSession) -> AsyncGenerator[TestClient]: - """Provide a TestClient that uses the test database session.""" + asyncio.run(async_engine.dispose()) - async def override_get_db() -> AsyncGenerator[AsyncSession]: - yield db - - app.dependency_overrides[get_async_session] = override_get_db - - with TestClient(app) as c: - yield c - - app.dependency_overrides.clear() + # Cleanup + with sync_engine.connect() as connection: + # Terminate other connections to the database to ensure DROP works + connection.execute( + text( + f""" + SELECT pg_terminate_backend(pg_stat_activity.pid) + FROM pg_stat_activity + WHERE pg_stat_activity.datname = '{TEST_DATABASE_NAME}' + AND pid <> pg_backend_pid(); + """ + ) + ) + connection.execute(text(f"DROP DATABASE IF EXISTS {TEST_DATABASE_NAME}")) + + +@pytest.fixture +async def session(setup_test_database: None) -> AsyncGenerator[AsyncSession]: + """Provide isolated database session using transaction rollback. + + This uses the 'connection.begin()' pattern which is more robust for async tests + than the nested transaction approach. + """ + async with async_engine.connect() as connection: + # Begin a transaction that will be rolled back + transaction = await connection.begin() + + # Bind the session to this specific connection + session_factory = async_sessionmaker( + bind=connection, + class_=AsyncSession, + autocommit=False, + autoflush=False, + expire_on_commit=False, + ) + + async with session_factory() as session: + yield session + + # Rollback the transaction after the test completes + if transaction.is_active: + await transaction.rollback() + + +# ============================================================================ +# Utility Fixtures +# ============================================================================ + + +@pytest.fixture +def anyio_backend(): + """Configure anyio backend for async tests.""" + return "asyncio" diff --git a/backend/tests/constants/__init__.py b/backend/tests/constants/__init__.py deleted file mode 100644 index 2564ef9..0000000 --- a/backend/tests/constants/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Constants used in testing.""" diff --git a/backend/tests/constants/background_data.py b/backend/tests/constants/background_data.py deleted file mode 100644 index 196044a..0000000 --- a/backend/tests/constants/background_data.py +++ /dev/null @@ -1 +0,0 @@ -"""Constants for background data tests.""" diff --git a/backend/tests/factories/__init__.py b/backend/tests/factories/__init__.py index df3d47b..8fa6b52 100644 --- a/backend/tests/factories/__init__.py +++ b/backend/tests/factories/__init__.py @@ -1 +1,4 @@ -"""Factory-boy factories for generating test objects.""" +"""Factories package. + +Contains Polyfactory model factories and TypedDict factories. +""" diff --git a/backend/tests/factories/background_data.py b/backend/tests/factories/background_data.py deleted file mode 100644 index 4ef9990..0000000 --- a/backend/tests/factories/background_data.py +++ /dev/null @@ -1,6 +0,0 @@ -import factory -from app.api.background_data.models import Taxonomy - - -class TaxonomyFactory(factory.alchemy.SQLAlchemyModelFactory): - pass diff --git a/backend/tests/factories/emails.py b/backend/tests/factories/emails.py new file mode 100644 index 0000000..5b24501 --- /dev/null +++ b/backend/tests/factories/emails.py @@ -0,0 +1,87 @@ +"""Factories for email template context dicts for tests. + +Using Polyfactory TypedDictFactory to replace legacy FactoryBoy DictFactory. +""" + +from typing import TypedDict +from polyfactory.factories.typed_dict_factory import TypedDictFactory + + +class EmailContext(TypedDict): + """Type definition for email context.""" + username: str + verification_link: str + reset_link: str + confirmation_link: str + unsubscribe_link: str + subject: str + newsletter_content: str + + +class EmailContextFactory(TypedDictFactory[EmailContext]): + """Produce realistic email template context dicts for tests.""" + + __model__ = EmailContext + + @classmethod + def username(cls) -> str: + return cls.__faker__.user_name() + + @classmethod + def verification_link(cls) -> str: + return cls.__faker__.url() + + @classmethod + def reset_link(cls) -> str: + return cls.__faker__.url() + + @classmethod + def confirmation_link(cls) -> str: + return cls.__faker__.url() + + @classmethod + def unsubscribe_link(cls) -> str: + return cls.__faker__.url() + + @classmethod + def subject(cls) -> str: + return cls.__faker__.sentence(nb_words=5) + + @classmethod + def newsletter_content(cls) -> str: + return cls.__faker__.text(max_nb_chars=200) + + +class EmailData(TypedDict): + """Type definition for email data.""" + email: str + username: str + token: str + subject: str + body: str + + +class EmailDataFactory(TypedDictFactory[EmailData]): + """Produce test data for email sending functions.""" + + __model__ = EmailData + + @classmethod + def email(cls) -> str: + return cls.__faker__.email() + + @classmethod + def username(cls) -> str: + return cls.__faker__.user_name() + + @classmethod + def token(cls) -> str: + return str(cls.__faker__.uuid4()) + + @classmethod + def subject(cls) -> str: + return cls.__faker__.sentence(nb_words=5) + + @classmethod + def body(cls) -> str: + return cls.__faker__.text(max_nb_chars=200) diff --git a/backend/tests/factories/models.py b/backend/tests/factories/models.py new file mode 100644 index 0000000..892e9cf --- /dev/null +++ b/backend/tests/factories/models.py @@ -0,0 +1,214 @@ +"""Modern test factories using polyfactory for background data models. + +Polyfactory provides better Pydantic v2 support and native async capabilities. +""" + +from typing import Generic, TypeVar + +from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory +from sqlalchemy.ext.asyncio import AsyncSession + +from app.api.auth.models import User +from app.api.background_data.models import ( + Category, + CategoryMaterialLink, + CategoryProductTypeLink, + Material, + ProductType, + Taxonomy, + TaxonomyDomain, +) + +T = TypeVar("T") + + +class BaseModelFactory(Generic[T], SQLAlchemyFactory[T]): + """Base factory with custom create_async support for explicit session.""" + + __is_base_factory__ = True + __set_relationships__ = False # Skip relationship introspection to avoid SQLAlchemy/polyfactory conflicts + + @classmethod + async def create_async(cls, session: AsyncSession | None = None, **kwargs) -> T: + """Create a new instance, optionally using a provided session.""" + if session: + instance = cls.build(**kwargs) + session.add(instance) + await session.commit() + await session.refresh(instance) + return instance + return await super().create_async(**kwargs) + + +class UserFactory(BaseModelFactory[User]): + """Factory for creating User test instances.""" + + __model__ = User + + @classmethod + def email(cls) -> str: + return cls.__faker__.email() + + @classmethod + def hashed_password(cls) -> str: + return "not_really_hashed" + + @classmethod + def is_active(cls) -> bool: + return True + + @classmethod + def is_superuser(cls) -> bool: + return False + + @classmethod + def is_verified(cls) -> bool: + return True + + @classmethod + def username(cls) -> str: + return cls.__faker__.user_name() + + @classmethod + def organization(cls) -> None: + return None + + @classmethod + def organization_id(cls) -> None: + return None + + @classmethod + def owned_organization(cls) -> None: + return None + + @classmethod + def products(cls) -> list: + return [] + + @classmethod + def oauth_accounts(cls) -> list: + return [] + + +class TaxonomyFactory(BaseModelFactory[Taxonomy]): + """Factory for creating Taxonomy test instances.""" + + __model__ = Taxonomy + + @classmethod + def name(cls) -> str: + return cls.__faker__.catch_phrase() + + @classmethod + def version(cls) -> str: + return cls.__faker__.numerify(text="v#.#.#") + + @classmethod + def description(cls) -> str | None: + return cls.__faker__.text(max_nb_chars=200) if cls.__faker__.boolean() else None + + @classmethod + def domains(cls) -> set[TaxonomyDomain]: + # Return at least one domain + domains = [TaxonomyDomain.MATERIALS] + if cls.__faker__.boolean(): + domains.append(TaxonomyDomain.PRODUCTS) + return set(domains) + + @classmethod + def categories(cls) -> list[Category]: + return [] + + @classmethod + def source(cls) -> str | None: + return cls.__faker__.url() if cls.__faker__.boolean() else None + + +class CategoryFactory(BaseModelFactory[Category]): + """Factory for creating Category test instances.""" + + __model__ = Category + + @classmethod + def name(cls) -> str: + return cls.__faker__.word().title() + + @classmethod + def description(cls) -> str | None: + return cls.__faker__.sentence() if cls.__faker__.boolean() else None + + @classmethod + def external_id(cls) -> str | None: + return cls.__faker__.uuid4() if cls.__faker__.boolean() else None + + @classmethod + def supercategory_id(cls) -> int | None: + return None + + @classmethod + def supercategory(cls) -> None: + return None + + # taxonomy_id and supercategory_id should be set explicitly in tests + + +class MaterialFactory(BaseModelFactory[Material]): + """Factory for creating Material test instances.""" + + __model__ = Material + + @classmethod + def name(cls) -> str: + materials = ["Steel", "Aluminum", "Copper", "Titanium", "Carbon Fiber", "Glass", "Ceramic"] + return cls.__faker__.random_element(elements=materials) + + @classmethod + def description(cls) -> str | None: + return cls.__faker__.sentence() if cls.__faker__.boolean() else None + + @classmethod + def source(cls) -> str | None: + return cls.__faker__.url() if cls.__faker__.boolean() else None + + @classmethod + def density_kg_m3(cls) -> float | None: + return ( + round(cls.__faker__.pyfloat(min_value=100, max_value=20000), 2) + if cls.__faker__.boolean(chance_of_getting_true=80) + else None + ) + + @classmethod + def is_crm(cls) -> bool | None: + return cls.__faker__.boolean() if cls.__faker__.boolean(chance_of_getting_true=80) else None + + +class ProductTypeFactory(BaseModelFactory[ProductType]): + """Factory for creating ProductType test instances.""" + + __model__ = ProductType + + @classmethod + def name(cls) -> str: + product_types = ["Electronics", "Furniture", "Appliances", "Tools", "Packaging", "Automotive Parts"] + return cls.__faker__.random_element(elements=product_types) + + @classmethod + def description(cls) -> str | None: + return cls.__faker__.sentence() if cls.__faker__.boolean() else None + + +class CategoryMaterialLinkFactory(BaseModelFactory[CategoryMaterialLink]): + """Factory for creating CategoryMaterialLink instances.""" + + __model__ = CategoryMaterialLink + + # category_id and material_id should be set explicitly + + +class CategoryProductTypeLinkFactory(BaseModelFactory[CategoryProductTypeLink]): + """Factory for creating CategoryProductTypeLink instances.""" + + __model__ = CategoryProductTypeLink + + # category_id and product_type_id should be set explicitly diff --git a/backend/tests/fixtures/__init__.py b/backend/tests/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/fixtures/client.py b/backend/tests/fixtures/client.py new file mode 100644 index 0000000..df2d836 --- /dev/null +++ b/backend/tests/fixtures/client.py @@ -0,0 +1,64 @@ +"""HTTP Client fixtures for API testing.""" + +import httpx +import pytest +from collections.abc import AsyncGenerator +from httpx import ASGITransport +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.database import get_async_session +from app.main import app + + +@pytest.fixture +def test_app(): + """Provide fresh FastAPI app instance. + + Yields app with cleared dependency overrides after each test. + """ + yield app + app.dependency_overrides.clear() + + +@pytest.fixture +async def async_client(test_app, session: AsyncSession) -> AsyncGenerator[httpx.AsyncClient]: + """Provide async HTTP client for API testing. + + Uses httpx.AsyncClient for true async testing of ASGI application. + Automatically injects test database session. + """ + + async def override_get_session() -> AsyncGenerator[AsyncSession]: + yield session + + test_app.dependency_overrides[get_async_session] = override_get_session + + async with httpx.AsyncClient( + transport=ASGITransport(app=test_app), + base_url="http://test", + follow_redirects=True, + ) as client: + yield client + + test_app.dependency_overrides.clear() + + +@pytest.fixture +async def superuser(session: AsyncSession) -> "User": + """Create a superuser for testing.""" + from app.api.auth.models import User + from tests.factories.models import UserFactory + + user = await UserFactory.create_async(session=session, is_superuser=True, is_active=True) + return user + + +@pytest.fixture +async def superuser_client(async_client: httpx.AsyncClient, superuser: "User", test_app) -> AsyncGenerator[httpx.AsyncClient, None]: + """Provide an authenticated client with superuser privileges (via dependency override).""" + from app.api.auth.dependencies import current_active_superuser + + test_app.dependency_overrides[current_active_superuser] = lambda: superuser + yield async_client + # Cleanup override + test_app.dependency_overrides.pop(current_active_superuser, None) diff --git a/backend/tests/fixtures/data.py b/backend/tests/fixtures/data.py new file mode 100644 index 0000000..e2360b8 --- /dev/null +++ b/backend/tests/fixtures/data.py @@ -0,0 +1,94 @@ +"""Data fixtures for pre-populating test database.""" + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession +from app.api.background_data.models import Category, Material, ProductType, Taxonomy, TaxonomyDomain +from tests.factories.models import ( + CategoryFactory, + MaterialFactory, + ProductTypeFactory, + TaxonomyFactory, +) + + +@pytest.fixture +async def db_taxonomy(session: AsyncSession) -> Taxonomy: + """Create and return a test taxonomy in database.""" + taxonomy = Taxonomy( + name="Test Materials Taxonomy", + version="v1.0.0", + description="A test taxonomy for materials", + domains={TaxonomyDomain.MATERIALS}, + source="https://test.example.com", + ) + session.add(taxonomy) + await session.flush() + await session.refresh(taxonomy) + return taxonomy + + +@pytest.fixture +async def db_category(session: AsyncSession, db_taxonomy: Taxonomy) -> Category: + """Create and return a test category in database.""" + category = Category( + name="Test Category", + description="A test category", + taxonomy_id=db_taxonomy.id, + ) + session.add(category) + await session.flush() + await session.refresh(category) + return category + + +@pytest.fixture +async def db_material(session: AsyncSession) -> Material: + """Create and return a test material in database.""" + material = Material( + name="Test Material", + description="A test material", + density_kg_m3=7850.0, + is_crm=True, + ) + session.add(material) + await session.flush() + await session.refresh(material) + return material + + +@pytest.fixture +async def db_product_type(session: AsyncSession) -> ProductType: + """Create and return a test product type in database.""" + product_type = ProductType( + name="Test Product Type", + description="A test product type", + ) + session.add(product_type) + await session.flush() + await session.refresh(product_type) + return product_type + + +# Factory fixtures for convenient access +@pytest.fixture +def taxonomy_factory() -> type[TaxonomyFactory]: + """Provide TaxonomyFactory.""" + return TaxonomyFactory + + +@pytest.fixture +def category_factory() -> type[CategoryFactory]: + """Provide CategoryFactory.""" + return CategoryFactory + + +@pytest.fixture +def material_factory() -> type[MaterialFactory]: + """Provide MaterialFactory.""" + return MaterialFactory + + +@pytest.fixture +def product_type_factory() -> type[ProductTypeFactory]: + """Provide ProductTypeFactory.""" + return ProductTypeFactory diff --git a/backend/tests/fixtures/database.py b/backend/tests/fixtures/database.py new file mode 100644 index 0000000..8b91e0b --- /dev/null +++ b/backend/tests/fixtures/database.py @@ -0,0 +1,46 @@ +"""Database fixtures and helpers for testing.""" + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession +from sqlmodel import select + + +class DBOperations: + """Helper class for common database operations in tests.""" + + def __init__(self, session: AsyncSession): + self.session = session + + async def get_by_id(self, model, obj_id: int): + """Get model instance by ID.""" + return await self.session.get(model, obj_id) + + async def get_by_filter(self, model, **filters): + """Get single model instance by filters.""" + stmt = select(model).filter_by(**filters) + result = await self.session.execute(stmt) + return result.scalar_one_or_none() + + async def get_all(self, model, **filters): + """Get all model instances matching filters.""" + stmt = select(model).filter_by(**filters) + result = await self.session.execute(stmt) + return result.scalars().all() + + async def create(self, instance): + """Create instance and return it with ID.""" + self.session.add(instance) + await self.session.flush() + await self.session.refresh(instance) + return instance + + async def delete(self, instance): + """Delete instance.""" + await self.session.delete(instance) + await self.session.flush() + + +@pytest.fixture +def db_ops(session: AsyncSession) -> DBOperations: + """Provide database operations helper.""" + return DBOperations(session) diff --git a/backend/tests/fixtures/migrations.py b/backend/tests/fixtures/migrations.py new file mode 100644 index 0000000..219de90 --- /dev/null +++ b/backend/tests/fixtures/migrations.py @@ -0,0 +1,149 @@ +"""Database migration testing fixtures. + +Utilities for testing Alembic migrations, schema changes, and database evolution. +""" + +from pathlib import Path + +import pytest +from alembic import command +from alembic.config import Config +from app.core.config import settings +from sqlalchemy import Engine, create_engine, inspect, text + + +class MigrationHelper: + """Helper class for testing database migrations.""" + + def __init__(self, alembic_cfg: Config): + """Initialize migration helper with Alembic config.""" + self.alembic_cfg = alembic_cfg + self.sync_engine: Engine = create_engine( + settings.sync_database_url, + isolation_level="AUTOCOMMIT", + ) + + def upgrade(self, revision: str = "head") -> None: + """Upgrade database to specific revision. + + Args: + revision: Target revision (default: 'head' - latest) + """ + command.upgrade(self.alembic_cfg, revision) + + def downgrade(self, revision: str) -> None: + """Downgrade database to specific revision. + + Args: + revision: Target revision to downgrade to + """ + command.downgrade(self.alembic_cfg, revision) + + def current_revision(self) -> str: + """Get current database revision.""" + with self.sync_engine.connect() as connection: + result = connection.execute( + text("SELECT version_num FROM alembic_version ORDER BY version_num DESC LIMIT 1") + ) + row = result.first() + return row[0] if row else None + + def table_exists(self, table_name: str) -> bool: + """Check if table exists in database. + + Args: + table_name: Name of the table to check + + Returns: + True if table exists, False otherwise + """ + with self.sync_engine.connect() as connection: + inspector = inspect(connection) + return table_name in inspector.get_table_names() + + def column_exists(self, table_name: str, column_name: str) -> bool: + """Check if column exists in table. + + Args: + table_name: Table to check + column_name: Column to look for + + Returns: + True if column exists, False otherwise + """ + with self.sync_engine.connect() as connection: + inspector = inspect(connection) + if not self.table_exists(table_name): + return False + columns = [col["name"] for col in inspector.get_columns(table_name)] + return column_name in columns + + def get_table_columns(self, table_name: str) -> list[str]: + """Get list of column names for a table. + + Args: + table_name: Table to inspect + + Returns: + List of column names + """ + with self.sync_engine.connect() as connection: + inspector = inspect(connection) + if not self.table_exists(table_name): + return [] + return [col["name"] for col in inspector.get_columns(table_name)] + + def get_table_constraints(self, table_name: str) -> dict: + """Get constraints for a table (primary key, unique, foreign keys, checks). + + Args: + table_name: Table to inspect + + Returns: + Dictionary with constraint information + """ + with self.sync_engine.connect() as connection: + inspector = inspect(connection) + return { + "pk": inspector.get_pk_constraint(table_name), + "unique": inspector.get_unique_constraints(table_name), + "fk": inspector.get_foreign_keys(table_name), + "checks": inspector.get_check_constraints(table_name), + } + + def execute_sql(self, sql: str) -> list: + """Execute arbitrary SQL and return results. + + Args: + sql: SQL statement to execute + + Returns: + List of result rows + """ + with self.sync_engine.connect() as connection: + result = connection.execute(text(sql)) + return result.fetchall() + + +@pytest.fixture +def alembic_config() -> Config: + """Provide Alembic configuration for migration tests. + + Returns: + Configured Alembic Config object + """ + config = Config() + project_root: Path = Path(__file__).parents[2] # Navigate to backend/ + config.set_main_option("script_location", str(project_root / "alembic")) + config.set_main_option("sqlalchemy.url", settings.sync_database_url) + return config + + +@pytest.fixture +def migration_helper(alembic_config: Config) -> MigrationHelper: + """Provide migration testing helper. + + Returns: + MigrationHelper instance for testing migrations + """ + return MigrationHelper(alembic_config) diff --git a/backend/tests/integration/__init__.py b/backend/tests/integration/__init__.py new file mode 100644 index 0000000..c66cd71 --- /dev/null +++ b/backend/tests/integration/__init__.py @@ -0,0 +1 @@ +"""Integration tests package.""" diff --git a/backend/tests/integration/test_auth_models.py b/backend/tests/integration/test_auth_models.py new file mode 100644 index 0000000..fbcd178 --- /dev/null +++ b/backend/tests/integration/test_auth_models.py @@ -0,0 +1,488 @@ +"""Tests for authentication models and relationships. + +Tests validate User model creation, password handling, and ownership relationships. +""" + +from datetime import UTC, datetime, timedelta, timezone +from uuid import uuid4 + +import pytest +from pydantic import UUID4 +from sqlalchemy.exc import IntegrityError +from sqlmodel import select +from sqlmodel.ext.asyncio.session import AsyncSession + +from app.api.auth.models import Organization, OrganizationRole, User + + +@pytest.mark.unit +class TestUserModelBasics: + """Tests for basic User model functionality.""" + + def test_user_model_has_id_field(self): + """Verify User model has an id field.""" + assert hasattr(User, "id") + + def test_user_model_has_username_field(self): + """Verify User model has a username field.""" + assert hasattr(User, "username") + + def test_user_model_has_required_fields(self): + """Verify User model has required email and hashed_password fields.""" + # FastAPI-Users base class provides email and hashed_password + assert hasattr(User, "email") + assert hasattr(User, "hashed_password") + + def test_user_model_has_organization_relationship(self): + """Verify User model has organization relationship.""" + assert hasattr(User, "organization") + + def test_user_model_has_organization_id_foreign_key(self): + """Verify User model has organization_id foreign key.""" + assert hasattr(User, "organization_id") + + def test_user_model_has_organization_role(self): + """Verify User model has organization_role field.""" + assert hasattr(User, "organization_role") + + def test_user_model_has_products_relationship(self): + """Verify User model has products relationship.""" + assert hasattr(User, "products") + + def test_user_model_has_oauth_accounts(self): + """Verify User model has oauth_accounts relationship.""" + assert hasattr(User, "oauth_accounts") + + def test_user_model_has_timestamp_fields(self): + """Verify User model has created_at and updated_at fields.""" + assert hasattr(User, "created_at") + assert hasattr(User, "updated_at") + + def test_organization_role_enum_values(self): + """Verify OrganizationRole enum has correct values.""" + assert OrganizationRole.OWNER.value == "owner" + assert OrganizationRole.MEMBER.value == "member" + + +@pytest.mark.integration +class TestUserModelPersistence: + """Tests for persisting User model to database.""" + + @pytest.mark.asyncio + async def test_create_user_with_required_fields(self, session: AsyncSession): + """Verify creating user with required fields.""" + email = "test@example.com" + username = "testuser" + hashed_password = "hashed_password_value" + + user = User( + email=email, + username=username, + hashed_password=hashed_password, + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.id is not None + assert user.email == email + assert user.username == username + assert user.created_at is not None + assert user.updated_at is not None + + @pytest.mark.asyncio + async def test_create_user_without_username(self, session: AsyncSession): + """Verify creating user without username is allowed.""" + email = "test@example.com" + hashed_password = "hashed_password_value" + + user = User( + email=email, + hashed_password=hashed_password, + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.id is not None + assert user.email == email + assert user.username is None + + @pytest.mark.asyncio + async def test_user_password_stored_hashed(self, session: AsyncSession): + """Verify password is stored in hashed form.""" + email = "test@example.com" + hashed_password = "bcrypt_hashed_value$2b$12$..." + + user = User( + email=email, + hashed_password=hashed_password, + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + # Password stored as provided (should be hashed by the application before creating) + assert user.hashed_password == hashed_password + + @pytest.mark.asyncio + async def test_user_defaults_organization_id_to_none(self, session: AsyncSession): + """Verify user organization_id defaults to None.""" + user = User( + email="test@example.com", + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.organization_id is None + assert user.organization is None + + @pytest.mark.asyncio + async def test_user_defaults_organization_role_to_none(self, session: AsyncSession): + """Verify user organization_role defaults to None.""" + user = User( + email="test@example.com", + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.organization_role is None + + +@pytest.mark.integration +class TestUserModelTimestamps: + """Tests for User model timestamp fields.""" + + @pytest.mark.asyncio + async def test_created_at_set_on_insert(self, session: AsyncSession): + """Verify created_at is set when user is created.""" + before_create = datetime.now(UTC) + + user = User( + email="test@example.com", + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + after_create = datetime.now(UTC) + + assert user.created_at is not None + assert before_create <= user.created_at <= after_create + + @pytest.mark.asyncio + async def test_updated_at_set_on_insert(self, session: AsyncSession): + """Verify updated_at is set when user is created.""" + before_create = datetime.now(UTC) + + user = User( + email="test@example.com", + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + after_create = datetime.now(UTC) + + assert user.updated_at is not None + assert before_create <= user.updated_at <= after_create + + @pytest.mark.asyncio + async def test_timestamps_are_equal_on_creation(self, session: AsyncSession): + """Verify created_at and updated_at are equal on creation.""" + user = User( + email="test@example.com", + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + await session.refresh(user) + + # They should be very close (within 1 second) + assert abs((user.updated_at - user.created_at).total_seconds()) < 1 + + +@pytest.mark.integration +class TestUserQueryingAndRetrieval: + """Tests for querying and retrieving User models.""" + + @pytest.mark.asyncio + async def test_retrieve_user_by_id(self, session: AsyncSession): + """Verify user can be retrieved by ID.""" + user = User( + email="test@example.com", + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + + # Create new session to test retrieval + statement = select(User).where(User.id == user.id) + result = await session.execute(statement) + retrieved = result.unique().scalar_one_or_none() + + assert retrieved is not None + assert retrieved.id == user.id + assert retrieved.email == user.email + + @pytest.mark.asyncio + async def test_retrieve_user_by_email(self, session: AsyncSession): + """Verify user can be retrieved by email.""" + email = "test@example.com" + user = User( + email=email, + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + + statement = select(User).where(User.email == email) + result = await session.execute(statement) + retrieved = result.unique().scalar_one_or_none() + + assert retrieved is not None + assert retrieved.email == email + + @pytest.mark.asyncio + async def test_retrieve_user_by_username(self, session: AsyncSession): + """Verify user can be retrieved by username.""" + username = "testuser123" + user = User( + email="test@example.com", + username=username, + hashed_password="hashed", + ) + + session.add(user) + await session.commit() + + statement = select(User).where(User.username == username) + result = await session.execute(statement) + retrieved = result.unique().scalar_one_or_none() + + assert retrieved is not None + assert retrieved.username == username + + @pytest.mark.asyncio + async def test_nonexistent_user_returns_none(self, session: AsyncSession): + """Verify querying for nonexistent user returns None.""" + statement = select(User).where(User.email == "nonexistent@example.com") + result = await session.execute(statement) + retrieved = result.unique().scalar_one_or_none() + + assert retrieved is None + + @pytest.mark.asyncio + async def test_retrieve_multiple_users(self, session: AsyncSession): + """Verify multiple users can be retrieved.""" + users = [User(email=f"user{i}@example.com", hashed_password="hashed") for i in range(5)] + + for user in users: + session.add(user) + + await session.commit() + + statement = select(User) + result = await session.execute(statement) + retrieved = result.unique().scalars().all() + + assert len(retrieved) >= 5 + + +@pytest.mark.integration +class TestUserUniquenessConstraints: + """Tests for User model uniqueness constraints.""" + + @pytest.mark.asyncio + async def test_email_must_be_unique(self, session: AsyncSession): + """Verify email field is unique.""" + email = "unique@example.com" + + user1 = User(email=email, hashed_password="hashed1") + session.add(user1) + await session.commit() + + user2 = User(email=email, hashed_password="hashed2") + session.add(user2) + + from sqlalchemy.exc import IntegrityError + + with pytest.raises(IntegrityError): + await session.commit() + + @pytest.mark.asyncio + async def test_username_must_be_unique_when_provided(self, session: AsyncSession): + """Verify username field is unique when provided.""" + username = "uniqueuser" + + user1 = User( + email="user1@example.com", + username=username, + hashed_password="hashed1", + ) + session.add(user1) + await session.commit() + + user2 = User( + email="user2@example.com", + username=username, + hashed_password="hashed2", + ) + session.add(user2) + + from sqlalchemy.exc import IntegrityError + + with pytest.raises(IntegrityError): + await session.commit() + + @pytest.mark.asyncio + async def test_multiple_users_without_username_allowed(self, session: AsyncSession): + """Verify multiple users can have NULL username.""" + user1 = User( + email="user1@example.com", + hashed_password="hashed1", + ) + user2 = User( + email="user2@example.com", + hashed_password="hashed2", + ) + + session.add(user1) + session.add(user2) + + # Should not raise an error + await session.commit() + + # Verify both users were created + statement = select(User).where(User.username.is_(None)) + result = await session.execute(statement) + retrieved = result.unique().scalars().all() + + assert len(retrieved) >= 2 + + +@pytest.mark.integration +class TestUserOrganizationRelationship: + """Tests for User organization relationships.""" + + @pytest.mark.asyncio + async def test_user_can_be_assigned_to_organization(self, session: AsyncSession): + """Verify user can be assigned to an organization.""" + # Create an owner for the organization first + owner = User(email="owner@example.com", hashed_password="hashed") + session.add(owner) + await session.flush() + + org = Organization(name="Test Org", owner_id=owner.id) + session.add(org) + await session.flush() + + user = User( + email="test@example.com", + hashed_password="hashed", + organization_id=org.id, + organization_role=OrganizationRole.MEMBER, + ) + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.organization_id == org.id + assert user.organization_role == OrganizationRole.MEMBER + + @pytest.mark.asyncio + async def test_user_owner_role(self, session: AsyncSession): + """Verify user can have owner role.""" + # Create an owner for the organization first + owner = User(email="owner@example.com", hashed_password="hashed") + session.add(owner) + await session.flush() + + org = Organization(name="Test Org", owner_id=owner.id) + session.add(org) + await session.flush() + + user = User( + email="test@example.com", + hashed_password="hashed", + organization_id=org.id, + organization_role=OrganizationRole.OWNER, + ) + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.organization_role == OrganizationRole.OWNER + + @pytest.mark.asyncio + async def test_user_can_be_removed_from_organization(self, session: AsyncSession): + """Verify user can be removed from organization.""" + # Create an owner for the organization + owner = User(email="owner@example.com", hashed_password="hashed") + session.add(owner) + # Flush to get owner.id + await session.flush() + + org = Organization(name="Test Org", owner_id=owner.id) + session.add(org) + await session.flush() + + user = User( + email="test@example.com", + hashed_password="hashed", + organization_id=org.id, + organization_role=OrganizationRole.MEMBER, + ) + session.add(user) + await session.commit() + + # Remove from organization + user.organization_id = None + user.organization_role = None + session.add(user) + await session.commit() + await session.refresh(user) + + assert user.organization_id is None + assert user.organization_role is None + + +@pytest.mark.unit +class TestUserModelValidation: + """Tests for User model field validation.""" + + def test_user_email_is_required(self): + """Verify email is validated properly.""" + # SQLModel/SQLAlchemy validates the field definition + assert "email" in User.model_fields + + def test_user_password_is_required(self): + """Verify password is validated properly.""" + assert hasattr(User, "hashed_password") + + def test_organization_role_accepts_valid_enum_values(self): + """Verify organization_role enum values are correct.""" + valid_roles = [OrganizationRole.OWNER, OrganizationRole.MEMBER] + + assert all(isinstance(role, OrganizationRole) for role in valid_roles) + + def test_organization_role_string_values(self): + """Verify organization_role string values.""" + assert OrganizationRole.OWNER in [OrganizationRole.OWNER, OrganizationRole.MEMBER] + assert OrganizationRole.MEMBER in [OrganizationRole.OWNER, OrganizationRole.MEMBER] diff --git a/backend/tests/integration/test_background_data_models.py b/backend/tests/integration/test_background_data_models.py new file mode 100644 index 0000000..62c844d --- /dev/null +++ b/backend/tests/integration/test_background_data_models.py @@ -0,0 +1,289 @@ +"""Integration tests for background data models (with database).""" + +import pytest +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload +from sqlmodel import select + +from app.api.background_data.models import ( + Category, + CategoryMaterialLink, + CategoryProductTypeLink, + Material, + ProductType, + Taxonomy, + TaxonomyDomain, +) +from tests.fixtures.database import DBOperations + + +@pytest.mark.integration +class TestTaxonomyModel: + """Integration tests for Taxonomy model.""" + + async def test_create_taxonomy(self, session: AsyncSession): + """Test creating taxonomy in database.""" + taxonomy = Taxonomy( + name="Materials Taxonomy", + version="v1.0.0", + description="Test taxonomy", + domains={TaxonomyDomain.MATERIALS}, + source="https://example.com", + ) + session.add(taxonomy) + await session.flush() + await session.refresh(taxonomy) + + assert taxonomy.id is not None + assert taxonomy.name == "Materials Taxonomy" + assert taxonomy.created_at is not None + assert taxonomy.updated_at is not None + + async def test_taxonomy_str_representation(self, db_taxonomy: Taxonomy): + """Test Taxonomy __str__ method.""" + expected = f"{db_taxonomy.name} (id: {db_taxonomy.id})" + assert str(db_taxonomy) == expected + + async def test_taxonomy_with_multiple_domains(self, session: AsyncSession): + """Test taxonomy with multiple domains.""" + taxonomy = Taxonomy( + name="Multi-domain Taxonomy", + version="v1.0.0", + description="Test", + domains={TaxonomyDomain.MATERIALS, TaxonomyDomain.PRODUCTS}, + ) + session.add(taxonomy) + await session.flush() + await session.refresh(taxonomy) + + assert len(taxonomy.domains) == 2 + assert TaxonomyDomain.MATERIALS in taxonomy.domains + assert TaxonomyDomain.PRODUCTS in taxonomy.domains + + async def test_taxonomy_cascades_delete_categories(self, session: AsyncSession, db_taxonomy: Taxonomy): + """Test deleting taxonomy cascades to categories.""" + category = Category( + name="Test Category", + taxonomy_id=db_taxonomy.id, + ) + session.add(category) + await session.flush() + category_id = category.id + + # Delete taxonomy + await session.delete(db_taxonomy) + await session.flush() + + # Verify category was deleted + result = await session.get(Category, category_id) + assert result is None + + async def test_list_taxonomies(self, session: AsyncSession, db_ops: DBOperations): + """Test querying multiple taxonomies.""" + # Create multiple taxonomies + for i in range(3): + taxonomy = Taxonomy( + name=f"Taxonomy {i}", + version=f"v{i}.0.0", + domains={TaxonomyDomain.MATERIALS}, + ) + await db_ops.create(taxonomy) + + # Query all + taxonomies = await db_ops.get_all(Taxonomy) + assert len(taxonomies) >= 3 + + +@pytest.mark.integration +class TestCategoryModel: + """Integration tests for Category model.""" + + async def test_create_category(self, session: AsyncSession, db_taxonomy: Taxonomy): + """Test creating category in database.""" + category = Category( + name="Metals", + description="Metal materials", + external_id="EXT123", + taxonomy_id=db_taxonomy.id, + ) + session.add(category) + await session.flush() + await session.refresh(category) + + assert category.id is not None + assert category.name == "Metals" + assert category.external_id == "EXT123" + assert category.taxonomy_id == db_taxonomy.id + + async def test_category_requires_taxonomy(self, session: AsyncSession): + """Test category requires taxonomy_id (foreign key constraint).""" + category = Category(name="Invalid Category") + session.add(category) + + with pytest.raises(IntegrityError): + await session.flush() + + async def test_category_with_subcategories(self, session: AsyncSession, db_category: Category): + """Test self-referential relationship.""" + subcategory = Category( + name="Ferrous Metals", + description="Iron-based metals", + taxonomy_id=db_category.taxonomy_id, + supercategory_id=db_category.id, + ) + session.add(subcategory) + await session.flush() + await session.refresh(db_category) + await session.refresh(subcategory) + + assert subcategory.supercategory_id == db_category.id + assert len(db_category.subcategories) == 1 + assert db_category.subcategories[0].id == subcategory.id + + async def test_recursive_category_structure(self, session: AsyncSession, db_taxonomy: Taxonomy): + """Test multi-level category hierarchy.""" + # Create 3-level hierarchy: Metals -> Ferrous -> Steel + metals = Category(name="Metals", taxonomy_id=db_taxonomy.id) + session.add(metals) + await session.flush() + + ferrous = Category( + name="Ferrous", + taxonomy_id=db_taxonomy.id, + supercategory_id=metals.id, + ) + session.add(ferrous) + await session.flush() + + steel = Category( + name="Steel", + taxonomy_id=db_taxonomy.id, + supercategory_id=ferrous.id, + ) + session.add(steel) + await session.flush() + + # Verify structure + await session.refresh(metals) + assert len(metals.subcategories) == 1 + assert metals.subcategories[0].name == "Ferrous" + + +@pytest.mark.integration +class TestMaterialModel: + """Integration tests for Material model.""" + + async def test_create_material(self, session: AsyncSession): + """Test creating material in database.""" + material = Material( + name="Steel", + description="Iron-carbon alloy", + source="https://example.com/steel", + density_kg_m3=7850.0, + is_crm=False, + ) + session.add(material) + await session.flush() + await session.refresh(material) + + assert material.id is not None + assert material.name == "Steel" + assert material.density_kg_m3 == 7850.0 + + async def test_material_with_minimal_fields(self, session: AsyncSession): + """Test material with only required fields.""" + material = Material(name="Minimal Material") + session.add(material) + await session.flush() + await session.refresh(material) + + assert material.id is not None + assert material.description is None + assert material.density_kg_m3 is None + + +@pytest.mark.integration +class TestProductTypeModel: + """Integration tests for ProductType model.""" + + async def test_create_product_type(self, session: AsyncSession): + """Test creating product type in database.""" + product_type = ProductType( + name="Electronics", + description="Electronic products", + ) + session.add(product_type) + await session.flush() + await session.refresh(product_type) + + assert product_type.id is not None + assert product_type.name == "Electronics" + + +@pytest.mark.integration +class TestRelationships: + """Integration tests for model relationships.""" + + async def test_category_material_many_to_many( + self, session: AsyncSession, db_category: Category, db_material: Material + ): + """Test many-to-many relationship between Category and Material.""" + link = CategoryMaterialLink( + category_id=db_category.id, + material_id=db_material.id, + ) + session.add(link) + await session.flush() + + # Reload with relationships eagerly loaded + stmt = select(Category).where(Category.id == db_category.id).options(selectinload(Category.materials)) + result = await session.exec(stmt) + category = result.one() + + stmt = select(Material).where(Material.id == db_material.id).options(selectinload(Material.categories)) + result = await session.exec(stmt) + material = result.one() + + assert len(category.materials) == 1 + assert category.materials[0].id == db_material.id + assert len(material.categories) == 1 + assert material.categories[0].id == db_category.id + + async def test_category_product_type_many_to_many( + self, session: AsyncSession, db_category: Category, db_product_type: ProductType + ): + """Test many-to-many relationship between Category and ProductType.""" + link = CategoryProductTypeLink( + category_id=db_category.id, + product_type_id=db_product_type.id, + ) + session.add(link) + await session.flush() + + # Reload with relationships eagerly loaded + stmt = select(Category).where(Category.id == db_category.id).options(selectinload(Category.product_types)) + result = await session.exec(stmt) + category = result.one() + + assert len(category.product_types) == 1 + assert category.product_types[0].id == db_product_type.id + + async def test_taxonomy_categories_relationship(self, session: AsyncSession, db_taxonomy: Taxonomy): + """Test one-to-many relationship between Taxonomy and Categories.""" + # Create multiple categories + for i in range(3): + category = Category( + name=f"Category {i}", + taxonomy_id=db_taxonomy.id, + ) + session.add(category) + + await session.flush() + + # Reload with relationships eagerly loaded + stmt = select(Taxonomy).where(Taxonomy.id == db_taxonomy.id).options(selectinload(Taxonomy.categories)) + result = await session.exec(stmt) + taxonomy = result.one() + + assert len(taxonomy.categories) == 3 # 3 new categories created in this test diff --git a/backend/tests/integration/test_database_operations.py b/backend/tests/integration/test_database_operations.py new file mode 100644 index 0000000..14b6585 --- /dev/null +++ b/backend/tests/integration/test_database_operations.py @@ -0,0 +1,334 @@ +"""Integration tests for database operations and patterns. + +Tests database transactions, constraints, and isolation. +""" + +import pytest +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload +from sqlmodel import select + +from app.api.background_data.models import Category, Material, Taxonomy +from tests.fixtures.database import DBOperations + + +@pytest.mark.integration +class TestDatabaseTransactions: + """Test database transaction behavior and isolation.""" + + async def test_transaction_rollback_on_session_exit(self, session: AsyncSession): + """Test that changes roll back when session exits without commit.""" + # Create a material + material = Material( + name="Rollback Test Material", + description="This should be rolled back", + density_kg_m3=8000.0, + ) + session.add(material) + await session.flush() + material_id = material.id + + # In a fresh session, the material shouldn't exist + # (Because the first session will rollback when exiting fixture) + # This is verified implicitly by fixtures using nested transactions + + assert material_id is not None + + async def test_nested_transaction_isolation(self, session: AsyncSession, db_ops: DBOperations): + """Test that changes in one session don't affect another.""" + # Create first material + material1 = Material( + name="Material 1", + description="First material", + density_kg_m3=7850.0, + ) + await db_ops.create(material1) + + # Verify it exists + retrieved = await db_ops.get_by_filter(Material, name="Material 1") + assert retrieved is not None + assert retrieved.id == material1.id + + async def test_flush_vs_commit_behavior(self, session: AsyncSession): + """Test difference between flush and commit.""" + # Flush makes ID available but doesn't commit + material = Material( + name="Flush Test", + description="Test flush behavior", + density_kg_m3=8000.0, + ) + session.add(material) + await session.flush() + + # After flush, ID is available + assert material.id is not None + + # But this doesn't actually commit in test context + # (Our conftest mocks commit to only flush) + + async def test_refresh_after_write(self, session: AsyncSession): + """Test refreshing after write operations.""" + material = Material( + name="Refresh Test", + description="Test refresh", + density_kg_m3=8000.0, + ) + session.add(material) + await session.flush() + + # Refresh to ensure timestamps are populated + await session.refresh(material) + + assert material.created_at is not None + assert material.updated_at is not None + + +@pytest.mark.integration +class TestDatabaseConstraints: + """Test database constraints and integrity checks.""" + + # async def test_unique_constraint_violation(self, session: AsyncSession, db_ops: DBOperations): + # """Test that unique constraints are enforced.""" + # # Material name is not unique in model, so this test is invalid unless model changes. + # pass + + async def test_foreign_key_constraint(self, session: AsyncSession, db_taxonomy: Taxonomy): + """Test that foreign key constraints are enforced.""" + # Create category with valid taxonomy_id + category = Category( + name="Test Category", + description="A test category", + taxonomy_id=db_taxonomy.id, + ) + session.add(category) + await session.flush() + await session.refresh(category) + + # Verify relationship works + assert category.taxonomy_id == db_taxonomy.id + assert category.taxonomy == db_taxonomy + + async def test_null_constraint_enforcement(self, session: AsyncSession): + """Test that NOT NULL constraints are enforced.""" + # Try to create material without required name field + # (Depends on model definition - this is example pattern) + material = Material( + name="Valid Name", + # density_kg_m3 is required in model + density_kg_m3=7850.0, + ) + session.add(material) + await session.flush() + + assert material.id is not None + + +@pytest.mark.integration +class TestDatabaseQueries: + """Test various database query patterns.""" + + async def test_simple_select_all(self, session: AsyncSession, db_ops: DBOperations): + """Test selecting all records of a type.""" + # Create multiple materials + mat1 = Material(name="Mat1", density_kg_m3=7850.0) + mat2 = Material(name="Mat2", density_kg_m3=8000.0) + + for mat in [mat1, mat2]: + await db_ops.create(mat) + + # Query all + materials = await db_ops.get_all(Material) + assert len(materials) >= 2 + names = {m.name for m in materials} + assert "Mat1" in names + assert "Mat2" in names + + async def test_filtered_query(self, session: AsyncSession, db_ops: DBOperations): + """Test querying with filters.""" + # Create materials with different densities + heavy = Material(name="Heavy", density_kg_m3=10000.0) + light = Material(name="Light", density_kg_m3=2700.0) + + for mat in [heavy, light]: + await db_ops.create(mat) + + # Query with filter + materials = await db_ops.get_all(Material, name="Heavy") + assert len(materials) >= 1 + assert materials[0].density_kg_m3 == 10000.0 + + async def test_count_query(self, session: AsyncSession): + """Test counting records.""" + # Create multiple materials + for i in range(3): + material = Material( + name=f"Material {i}", + density_kg_m3=7850.0 + i * 100, + ) + session.add(material) + + await session.flush() + + # Count all materials + stmt = select(Material) + result = await session.execute(stmt) + materials = result.scalars().all() + + assert len(materials) >= 3 + + async def test_ordered_query(self, session: AsyncSession): + """Test query ordering.""" + # Create materials with different names + for name in ["Zebra", "Apple", "Banana"]: + material = Material( + name=name, + density_kg_m3=7850.0, + ) + session.add(material) + + await session.flush() + + # Query with ordering + stmt = select(Material).order_by(Material.name) + result = await session.execute(stmt) + materials = result.scalars().all() + + # Should be in alphabetical order + names = [m.name for m in materials if m.name in ["Zebra", "Apple", "Banana"]] + assert names == sorted(names) + + async def test_limited_query(self, session: AsyncSession): + """Test query with limit.""" + # Create multiple materials + for i in range(5): + material = Material( + name=f"Material {i}", + density_kg_m3=7850.0, + ) + session.add(material) + + await session.flush() + + # Query with limit + stmt = select(Material).limit(2) + result = await session.execute(stmt) + materials = result.scalars().all() + + assert len(materials) <= 2 + + +@pytest.mark.integration +class TestDatabaseRelationships: + """Test database relationship handling.""" + + async def test_one_to_many_relationship( + self, + session: AsyncSession, + db_taxonomy: Taxonomy, + db_ops: DBOperations, + ): + """Test one-to-many relationship (Taxonomy -> Categories).""" + # Create categories for taxonomy + cat1 = Category( + name="Category 1", + description="First category", + taxonomy_id=db_taxonomy.id, + ) + cat2 = Category( + name="Category 2", + description="Second category", + taxonomy_id=db_taxonomy.id, + ) + + for cat in [cat1, cat2]: + await db_ops.create(cat) + + # Verify relationship with explicit load + stmt = select(Taxonomy).where(Taxonomy.id == db_taxonomy.id).options(selectinload(Taxonomy.categories)) + result = await session.execute(stmt) + refreshed_taxonomy = result.scalar_one() + + assert len(refreshed_taxonomy.categories) == 2 + + async def test_relationship_cascade_delete_behavior( + self, + session: AsyncSession, + db_taxonomy: Taxonomy, + ): + """Test cascade delete behavior (model-dependent).""" + # Create category linked to taxonomy + category = Category( + name="Cascade Test Category", + description="Will be deleted with taxonomy", + taxonomy_id=db_taxonomy.id, + ) + session.add(category) + await session.flush() + category_id = category.id + + stmt = select(Taxonomy).where(Taxonomy.id == db_taxonomy.id).options(selectinload(Taxonomy.categories)) + result = await session.execute(stmt) + refreshed_taxonomy = result.scalar_one() + + assert len(refreshed_taxonomy.categories) >= 1 + + +@pytest.mark.integration +class TestDatabaseMutations: + """Test INSERT, UPDATE, DELETE operations.""" + + async def test_create_and_retrieve(self, session: AsyncSession, db_ops: DBOperations): + """Test creating and retrieving a record.""" + # Create + material = Material( + name="Test Material", + description="For testing", + density_kg_m3=7850.0, + ) + created = await db_ops.create(material) + + # Retrieve + retrieved = await db_ops.get_by_id(Material, created.id) + + assert retrieved is not None + assert retrieved.name == "Test Material" + + async def test_update_record(self, session: AsyncSession, db_ops: DBOperations): + """Test updating a record.""" + # Create + material = Material( + name="Original Name", + description="Original description", + density_kg_m3=7850.0, + ) + created = await db_ops.create(material) + + # Update + created.name = "Updated Name" + created.density_kg_m3 = 8000.0 + session.add(created) + await session.flush() + + # Verify update + retrieved = await db_ops.get_by_id(Material, created.id) + assert retrieved.name == "Updated Name" + assert retrieved.density_kg_m3 == 8000.0 + + async def test_delete_record(self, session: AsyncSession, db_ops: DBOperations): + """Test deleting a record.""" + # Create + material = Material( + name="To Delete", + description="Will be deleted", + density_kg_m3=7850.0, + ) + created = await db_ops.create(material) + created_id = created.id + + # Delete + await db_ops.delete(created) + + # Verify deletion + retrieved = await db_ops.get_by_id(Material, created_id) + assert retrieved is None diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py deleted file mode 100644 index 7444eb1..0000000 --- a/backend/tests/test_main.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Main test module for the application.""" - -from typing import TYPE_CHECKING - -from fastapi.testclient import TestClient - -if TYPE_CHECKING: - from httpx import Response - - -def test_read_units(client: TestClient) -> None: - """Test the units endpoint.""" - response: Response = client.get("/units") - assert response.status_code == 200 - assert response.json() == ["kg", "g", "m", "cm"] - - -def test_read_items(client: TestClient) -> None: - """Test the items endpoint.""" - response: Response = client.get("/file-storage/videos") - assert response.status_code == 200 - assert response.json() == [] diff --git a/backend/tests/test_migrations.py b/backend/tests/test_migrations.py new file mode 100644 index 0000000..28ed8e1 --- /dev/null +++ b/backend/tests/test_migrations.py @@ -0,0 +1,29 @@ +"""Test that all Alembic migrations run successfully.""" + +import logging + +import pytest + +logger = logging.getLogger(__name__) + + +@pytest.mark.asyncio +async def test_migrations_upgrade_head(setup_test_database): + """Test that all migrations can be upgraded to head without error.""" + # If we've reached here, migrations have already run successfully + # in the setup_test_database fixture, so this is a sanity check pass + assert True, "All migrations completed successfully" + + +@pytest.mark.asyncio +async def test_migrations_downgrade_upgrade(): + """Test migration downgrade and upgrade cycle. + + This is optional and tests the migration reversibility. + Only run if your migrations support downgrade. + """ + # Note: This requires migrations to have downgrade functions + # Uncomment if you want to test reversibility + # alembic_cfg = get_alembic_config() + # command.downgrade(alembic_cfg, "-1") # Downgrade one migration + # command.upgrade(alembic_cfg, "+1") # Upgrade one migration diff --git a/backend/tests/unit/__init__.py b/backend/tests/unit/__init__.py new file mode 100644 index 0000000..ea3f8b9 --- /dev/null +++ b/backend/tests/unit/__init__.py @@ -0,0 +1 @@ +"""Unit tests package.""" diff --git a/backend/tests/unit/auth/test_auth_utils.py b/backend/tests/unit/auth/test_auth_utils.py new file mode 100644 index 0000000..7d1f1cc --- /dev/null +++ b/backend/tests/unit/auth/test_auth_utils.py @@ -0,0 +1,208 @@ +import asyncio +import contextlib +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from fastapi import Request +from fastapi_users.exceptions import InvalidPasswordException, UserAlreadyExists +from redis.exceptions import ConnectionError as RedisConnectionError + +from app.api.auth.models import User +from app.api.auth.schemas import UserCreate +from app.api.auth.utils.email_validation import EmailChecker +from app.api.auth.utils.programmatic_user_crud import create_user + + +@pytest.fixture +def mock_redis(): + return AsyncMock() + + +class TestEmailChecker: + async def test_init_without_redis(self): + """Test initialization without Redis client.""" + checker = EmailChecker(redis_client=None) + + with patch("app.api.auth.utils.email_validation.DefaultChecker") as MockDefaultChecker: + mock_checker_instance = AsyncMock() + MockDefaultChecker.return_value = mock_checker_instance + + await checker.initialize() + + MockDefaultChecker.assert_called_once() + assert checker.checker == mock_checker_instance + mock_checker_instance.init_redis.assert_not_called() + mock_checker_instance.fetch_temp_email_domains.assert_called_once() + + await checker.close() + + async def test_init_with_redis(self, mock_redis): + """Test initialization with Redis client.""" + checker = EmailChecker(redis_client=mock_redis) + + with patch("app.api.auth.utils.email_validation.DefaultChecker") as MockDefaultChecker: + mock_checker_instance = AsyncMock() + MockDefaultChecker.return_value = mock_checker_instance + + await checker.initialize() + + MockDefaultChecker.assert_called_once() + assert checker.checker == mock_checker_instance + mock_checker_instance.init_redis.assert_called_once() + mock_checker_instance.fetch_temp_email_domains.assert_called_once() + + await checker.close() + + async def test_refresh_domains_success(self, mock_redis): + """Test successful domain refresh.""" + checker = EmailChecker(redis_client=mock_redis) + checker.checker = AsyncMock() + + await checker._refresh_domains() + + checker.checker.fetch_temp_email_domains.assert_called_once() + + async def test_refresh_domains_failure(self, mock_redis): + """Test domain refresh failure handles exceptions gracefully.""" + checker = EmailChecker(redis_client=mock_redis) + checker.checker = AsyncMock() + checker.checker.fetch_temp_email_domains.side_effect = RuntimeError("Refresh failed") + + # Should not raise exception + await checker._refresh_domains() + + checker.checker.fetch_temp_email_domains.assert_called_once() + + async def test_is_disposable_true(self, mock_redis): + """Test identifying disposable email.""" + checker = EmailChecker(redis_client=mock_redis) + checker.checker = AsyncMock() + checker.checker.is_disposable.return_value = True + + result = await checker.is_disposable("test@temp-mail.org") + + assert result is True + checker.checker.is_disposable.assert_called_with("test@temp-mail.org") + + async def test_is_disposable_false(self, mock_redis): + """Test identifying non-disposable email.""" + checker = EmailChecker(redis_client=mock_redis) + checker.checker = AsyncMock() + checker.checker.is_disposable.return_value = False + + result = await checker.is_disposable("user@example.com") + + assert result is False + + async def test_is_disposable_error_fail_open(self, mock_redis): + """Test error handling during check returns False (fail open).""" + checker = EmailChecker(redis_client=mock_redis) + checker.checker = AsyncMock() + checker.checker.is_disposable.side_effect = RedisConnectionError("Redis down") + + # When check fails, we should allow registration (return False) + result = await checker.is_disposable("user@example.com") + + assert result is False + + async def test_is_disposable_not_initialized(self, mock_redis): + """Test check when checker is not initialized.""" + checker = EmailChecker(redis_client=mock_redis) + checker.checker = None + + result = await checker.is_disposable("user@example.com") + + assert result is False + + async def test_close_cancels_task(self, mock_redis): + """Test close cancels the refresh task.""" + checker = EmailChecker(redis_client=mock_redis) + + # Mock the task to be awaitable + # Create a Future and verify it works when awaited + mock_task = asyncio.Future() + mock_task.set_result(None) # It needs a result if awaited + mock_task.cancel = MagicMock() + + checker._refresh_task = mock_task + mock_checker = AsyncMock() + checker.checker = mock_checker + + await checker.close() + + mock_task.cancel.assert_called_once() + mock_checker.close_connections.assert_called_once() + + +class TestProgrammaticUserCrud: + @pytest.fixture + def user_create(self): + return UserCreate(email="test@example.com", password="password123") + + @pytest.fixture + def mock_user_manager(self): + return AsyncMock() + + @pytest.fixture + def mock_session(self): + return AsyncMock() + + async def test_create_user_success(self, mock_session, user_create, mock_user_manager): + """Test successful user creation.""" + expected_user = User(id="uid", email=user_create.email) + mock_user_manager.create.return_value = expected_user + + # Mock the context manager + mock_context = AsyncMock() + mock_context.__aenter__.return_value = mock_user_manager + mock_context.__aexit__.return_value = None + + with patch( + "app.api.auth.utils.programmatic_user_crud.get_chained_async_user_manager_context", + return_value=mock_context, + ): + user = await create_user(mock_session, user_create, send_registration_email=True) + + assert user == expected_user + mock_user_manager.create.assert_called_once() + + # Verify request state was set + call_kwargs = mock_user_manager.create.call_args.kwargs + assert "request" in call_kwargs + request = call_kwargs["request"] + assert isinstance(request, Request) + assert request.state.send_registration_email is True + + async def test_create_user_already_exists(self, mock_session, user_create, mock_user_manager): + """Test user creation when user already exists.""" + mock_user_manager.create.side_effect = UserAlreadyExists() + + mock_context = AsyncMock() + mock_context.__aenter__.return_value = mock_user_manager + mock_context.__aexit__.return_value = None + + with patch( + "app.api.auth.utils.programmatic_user_crud.get_chained_async_user_manager_context", + return_value=mock_context, + ): + with pytest.raises(UserAlreadyExists) as exc: + await create_user(mock_session, user_create) + + assert f"User with email {user_create.email} already exists" in str(exc.value) + + async def test_create_user_invalid_password(self, mock_session, user_create, mock_user_manager): + """Test user creation with invalid password.""" + mock_user_manager.create.side_effect = InvalidPasswordException(reason="Too short") + + mock_context = AsyncMock() + mock_context.__aenter__.return_value = mock_user_manager + mock_context.__aexit__.return_value = None + + with patch( + "app.api.auth.utils.programmatic_user_crud.get_chained_async_user_manager_context", + return_value=mock_context, + ): + with pytest.raises(InvalidPasswordException) as exc: + await create_user(mock_session, user_create) + + assert "Password is invalid: Too short" in str(exc.value) diff --git a/backend/tests/unit/background_data/test_background_data_crud.py b/backend/tests/unit/background_data/test_background_data_crud.py new file mode 100644 index 0000000..e44e912 --- /dev/null +++ b/backend/tests/unit/background_data/test_background_data_crud.py @@ -0,0 +1,131 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from app.api.background_data.crud import validate_category_creation, validate_category_taxonomy_domains +from app.api.background_data.models import Category, Taxonomy, TaxonomyDomain + + +@pytest.fixture +def mock_session(): + return AsyncMock() + + +class TestCategoryValidation: + async def test_validate_category_creation_with_supercategory(self, mock_session): + """Test validation when supercategory is provided.""" + category_create = AsyncMock() + category_create.taxonomy_id = 99 # Should be ignored if supercategory provided + + super_category = Category(id=1, taxonomy_id=10, name="Super") + + with patch( + "app.api.background_data.crud.db_get_model_with_id_if_it_exists", return_value=super_category + ) as mock_get: + # Case 1: Matching taxonomy_id + result_id, result_cat = await validate_category_creation( + mock_session, category_create, taxonomy_id=10, supercategory_id=1 + ) + + assert result_id == 10 + assert result_cat == super_category + mock_get.assert_called_with(mock_session, Category, 1) + + async def test_validate_category_creation_supercategory_mismatch(self, mock_session): + """Test validation fails when supercategory taxonomy mismatches.""" + category_create = AsyncMock() + super_category = Category(id=1, taxonomy_id=10, name="Super") + + with patch("app.api.background_data.crud.db_get_model_with_id_if_it_exists", return_value=super_category): + # Case 2: Mismatched taxonomy_id + with pytest.raises(ValueError) as exc: + await validate_category_creation(mock_session, category_create, taxonomy_id=20, supercategory_id=1) + + assert "does not belong to taxonomy with id 20" in str(exc.value) + + async def test_validate_category_creation_top_level(self, mock_session): + """Test validation for top-level category info.""" + category_create = AsyncMock() + category_create.taxonomy_id = 10 + + mock_taxonomy = Taxonomy(id=10, name="Tax") + + with patch( + "app.api.background_data.crud.db_get_model_with_id_if_it_exists", return_value=mock_taxonomy + ) as mock_get: + result_id, result_cat = await validate_category_creation( + mock_session, category_create, taxonomy_id=None, supercategory_id=None + ) + + assert result_id == 10 + assert result_cat is None + mock_get.assert_called_with(mock_session, Taxonomy, 10) + + async def test_validate_category_creation_missing_taxonomy(self, mock_session): + """Test validation fails if no taxonomy ID for top-level.""" + category_create = AsyncMock() + category_create.taxonomy_id = None + + with pytest.raises(ValueError) as exc: + await validate_category_creation(mock_session, category_create, taxonomy_id=None, supercategory_id=None) + + assert "Taxonomy ID is required" in str(exc.value) + + +class TestTaxonomyDomainValidation: + async def test_validate_domains_success(self, mock_session): + """Test successful domain validation.""" + category_ids = {1, 2} + expected_domain = TaxonomyDomain.PRODUCTS + + # Mock DB response + cat1 = Category(id=1, taxonomy=Taxonomy(domains=[TaxonomyDomain.PRODUCTS])) + cat2 = Category(id=2, taxonomy=Taxonomy(domains=[TaxonomyDomain.PRODUCTS, TaxonomyDomain.MATERIALS])) + + # Use MagicMock for result so .all() is synchronous (but returns value) + mock_result = MagicMock() + mock_result.all.return_value = [cat1, cat2] + mock_session.exec.return_value = mock_result + + await validate_category_taxonomy_domains(mock_session, category_ids, expected_domain) + + # await db.exec(...) -> returns mock_result + # mock_result.all() -> returns [cat1, cat2] + # len() works on list + mock_session.exec.assert_called_once() + + async def test_validate_domains_missing_category(self, mock_session): + """Test validation fails when category is missing.""" + category_ids = {1, 2} + expected_domain = TaxonomyDomain.PRODUCTS + + # Only return one category + cat1 = Category(id=1, taxonomy=Taxonomy(domains=[TaxonomyDomain.PRODUCTS])) + + mock_result = MagicMock() + mock_result.all.return_value = [cat1] + mock_session.exec.return_value = mock_result + + with pytest.raises(ValueError) as exc: + await validate_category_taxonomy_domains(mock_session, category_ids, expected_domain) + + # Match fuzzy since set representation might differ + assert "not found" in str(exc.value) + assert "2" in str(exc.value) + + async def test_validate_domains_invalid_domain(self, mock_session): + """Test validation fails when category has wrong domain.""" + category_ids = {1} + expected_domain = TaxonomyDomain.PRODUCTS + + # Category has wrong domain + cat1 = Category(id=1, taxonomy=Taxonomy(domains=[TaxonomyDomain.MATERIALS])) + + mock_result = MagicMock() + mock_result.all.return_value = [cat1] + mock_session.exec.return_value = mock_result + + with pytest.raises(ValueError) as exc: + await validate_category_taxonomy_domains(mock_session, category_ids, expected_domain) + + assert "belong to taxonomies outside of domains" in str(exc.value) diff --git a/backend/tests/unit/data_collection/test_data_collection_crud.py b/backend/tests/unit/data_collection/test_data_collection_crud.py new file mode 100644 index 0000000..124fb4c --- /dev/null +++ b/backend/tests/unit/data_collection/test_data_collection_crud.py @@ -0,0 +1,121 @@ +from unittest.mock import AsyncMock, MagicMock, patch +from uuid import uuid4 + +import pytest + +from app.api.auth.models import User +from app.api.background_data.models import ProductType +from app.api.common.schemas.associations import MaterialProductLinkCreateWithinProductAndMaterial +from app.api.data_collection.crud import add_material_to_product, create_physical_properties, create_product +from app.api.data_collection.models import PhysicalProperties, Product +from app.api.data_collection.schemas import PhysicalPropertiesCreate, ProductCreateWithComponents + + +@pytest.fixture +def mock_session(): + session = AsyncMock() + # add and add_all are synchronous methods in SQLAlchemy + session.add = MagicMock() + session.add_all = MagicMock() + return session + + +class TestPhysicalPropertiesCrud: + async def test_create_physical_properties_success(self, mock_session): + """Test successful creation of physical properties.""" + product_id = 1 + props_create = PhysicalPropertiesCreate(weight_g=10.0, width_cm=5.0) + + # Mock product that exists and has no properties + product = Product(id=product_id, name="Test Product") + product.physical_properties = None + + with patch("app.api.data_collection.crud.db_get_model_with_id_if_it_exists", return_value=product) as mock_get: + result = await create_physical_properties(mock_session, props_create, product_id) + + assert isinstance(result, PhysicalProperties) + assert result.weight_g == 10.0 + assert result.product_id == product_id + + mock_session.add.assert_called_once() + mock_session.commit.assert_called_once() + mock_session.refresh.assert_called_once() + + async def test_create_physical_properties_already_exist(self, mock_session): + """Test error when product already has properties.""" + product_id = 1 + props_create = PhysicalPropertiesCreate(weight_g=10.0) + + # Mock product that already has properties + product = Product(id=product_id, name="Test Product") + product.physical_properties = PhysicalProperties(weight_g=5.0) + + with patch("app.api.data_collection.crud.db_get_model_with_id_if_it_exists", return_value=product): + with pytest.raises(ValueError) as exc: + await create_physical_properties(mock_session, props_create, product_id) + + assert "already has physical properties" in str(exc.value) + + +class TestProductCrud: + async def test_create_product_success(self, mock_session): + """Test successful product creation.""" + owner_id = uuid4() + # Product must have at least one material or component + product_create = ProductCreateWithComponents( + name="New Product", + product_type_id=1, + components=[], + bill_of_materials=[{"material_id": 1, "quantity": 1.0, "unit": "kg"}], + ) + + mock_type = ProductType(id=1, name="Type") + mock_user = User(id=owner_id, email="test@example.com") + + with patch("app.api.data_collection.crud.db_get_model_with_id_if_it_exists") as mock_get: + # Configure mock to return type then user + mock_get.side_effect = [mock_type, mock_user] + + # Use patch for material existence check as well + with patch("app.api.data_collection.crud.db_get_models_with_ids_if_they_exist"): + result = await create_product(mock_session, product_create, owner_id) + + assert isinstance(result, Product) + assert result.name == "New Product" + assert result.owner_id == owner_id + + mock_session.add.assert_called() + mock_session.commit.assert_called_once() + + async def test_add_material_to_product_success(self, mock_session): + """Test adding material to product.""" + product_id = 1 + material_id = 10 + link_create = MaterialProductLinkCreateWithinProductAndMaterial(quantity=5.0) + + db_product = Product(id=product_id, name="Product") + db_product.bill_of_materials = [] + + with ( + patch("app.api.data_collection.crud.db_get_model_with_id_if_it_exists", return_value=db_product), + patch("app.api.data_collection.crud.db_get_models_with_ids_if_they_exist"), + patch("app.api.data_collection.crud.add_materials_to_product") as mock_add_batch, + ): + expected_link = MagicMock() + mock_add_batch.return_value = [expected_link] + + result = await add_material_to_product( + mock_session, product_id, material_link=link_create, material_id=material_id + ) + + assert result == expected_link + mock_add_batch.assert_called_once() + + async def test_add_material_missing_id(self, mock_session): + """Test error when material ID is missing.""" + link_create = MaterialProductLinkCreateWithinProductAndMaterial(quantity=5.0) + + with pytest.raises(ValueError) as exc: + await add_material_to_product(mock_session, product_id=1, material_link=link_create, material_id=None) + + assert "Material ID is required" in str(exc.value) diff --git a/backend/tests/unit/emails/__init__.py b/backend/tests/unit/emails/__init__.py new file mode 100644 index 0000000..d04ba40 --- /dev/null +++ b/backend/tests/unit/emails/__init__.py @@ -0,0 +1 @@ +"""Tests related to email functionality.""" diff --git a/backend/tests/unit/emails/test_programmatic_emails.py b/backend/tests/unit/emails/test_programmatic_emails.py new file mode 100644 index 0000000..3cf8f11 --- /dev/null +++ b/backend/tests/unit/emails/test_programmatic_emails.py @@ -0,0 +1,243 @@ +"""Tests for programmatic email sending functionality.""" + +from typing import Any +from unittest.mock import AsyncMock, MagicMock +from urllib.parse import parse_qs, urlparse + +import pytest +from faker import Faker +from fastapi import BackgroundTasks + +from app.api.auth.utils.programmatic_emails import ( + generate_token_link, + send_post_verification_email, + send_registration_email, + send_reset_password_email, + send_verification_email, +) +from app.core.config import settings as core_settings + +fake = Faker() + + +@pytest.fixture +def email_data() -> dict[str, str]: + """Return common data for email tests.""" + return { + "email": fake.email(), + "username": fake.user_name(), + "token": fake.uuid4(), + } + + +@pytest.fixture +def mock_email_sender(mocker) -> AsyncMock: + """Mock the email sender.""" + return mocker.patch("app.api.auth.utils.programmatic_emails.fm.send_message", new_callable=AsyncMock) + + +### Token Link Generation Tests ### +def test_generate_token_link_default_base_url() -> None: + """Test token link generation with default base URL from core settings.""" + token = fake.uuid4() + route = "/verify" + + link = generate_token_link(token, route) + + parsed = urlparse(link) + query_params = parse_qs(parsed.query) + + assert link.startswith(str(core_settings.frontend_app_url)) + assert parsed.path == route + assert query_params["token"] == [token] + + +def test_generate_token_link_custom_base_url() -> None: + """Test token link generation with custom base URL.""" + token = fake.uuid4() + route = "/reset-password" + custom_base_url = fake.url() + + link = generate_token_link(token, route, base_url=custom_base_url) + + parsed = urlparse(link) + query_params = parse_qs(parsed.query) + + assert link.startswith(custom_base_url) + assert parsed.path == route + assert query_params["token"] == [token] + + +def test_generate_token_link_with_trailing_slash() -> None: + """Test that token links are generated correctly regardless of trailing slashes.""" + token = fake.uuid4() + route = "/verify" + base_url_with_slash = f"{fake.url()}//" + + link = generate_token_link(token, route, base_url=base_url_with_slash) + + # Should not have double slashes + assert "//" not in link.split("://")[1] + # Should still have the correct route + assert urlparse(link).path == route + + +### Registration Email Tests ### +@pytest.mark.asyncio +async def test_send_registration_email(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test registration email is sent.""" + await send_registration_email(email_data["email"], email_data["username"], email_data["token"]) + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +async def test_send_registration_email_no_username(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test registration email works without username.""" + await send_registration_email(email_data["email"], None, email_data["token"]) + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +async def test_send_registration_email_with_background_tasks(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test registration email queues task instead of sending immediately.""" + background_tasks = MagicMock(spec=BackgroundTasks) + + await send_registration_email( + email_data["email"], email_data["username"], email_data["token"], background_tasks=background_tasks + ) + + # When background_tasks is provided, it should queue, not send + background_tasks.add_task.assert_called_once() + mock_email_sender.assert_not_called() + + +### Password Reset Email Tests ### +@pytest.mark.asyncio +async def test_send_reset_password_email(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test password reset email is sent.""" + await send_reset_password_email(email_data["email"], email_data["username"], email_data["token"]) + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +async def test_send_reset_password_email_with_background_tasks(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test password reset email queues task when background_tasks provided.""" + background_tasks = MagicMock(spec=BackgroundTasks) + + await send_reset_password_email( + email_data["email"], email_data["username"], email_data["token"], background_tasks=background_tasks + ) + + background_tasks.add_task.assert_called_once() + mock_email_sender.assert_not_called() + + +### Verification Email Tests ### +@pytest.mark.asyncio +async def test_send_verification_email(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test verification email is sent.""" + await send_verification_email(email_data["email"], email_data["username"], email_data["token"]) + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +async def test_send_verification_email_with_background_tasks(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test verification email queues task when background_tasks provided.""" + background_tasks = MagicMock(spec=BackgroundTasks) + + await send_verification_email( + email_data["email"], email_data["username"], email_data["token"], background_tasks=background_tasks + ) + + background_tasks.add_task.assert_called_once() + mock_email_sender.assert_not_called() + + +### Post-Verification Email Tests ### +@pytest.mark.asyncio +async def test_send_post_verification_email(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test post-verification email is sent.""" + await send_post_verification_email(email_data["email"], email_data["username"]) + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +async def test_send_post_verification_email_no_username(email_data: dict, mock_email_sender: AsyncMock) -> None: + """Test post-verification email works without username.""" + await send_post_verification_email(email_data["email"], None) + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +async def test_send_post_verification_email_with_background_tasks( + email_data: dict, mock_email_sender: AsyncMock +) -> None: + """Test post-verification email queues task when background_tasks provided.""" + background_tasks = MagicMock(spec=BackgroundTasks) + + await send_post_verification_email(email_data["email"], email_data["username"], background_tasks=background_tasks) + + background_tasks.add_task.assert_called_once() + mock_email_sender.assert_not_called() + + +### Parametrized Integration Tests ### +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("email_func", "needs_token"), + [ + (send_registration_email, True), + (send_reset_password_email, True), + (send_verification_email, True), + (send_post_verification_email, False), + ], +) +async def test_all_email_functions_send_emails( + email_data: dict, + mock_email_sender: AsyncMock, + email_func: Any, + *, + needs_token: bool, +) -> None: + """Test that all email functions successfully send emails.""" + # Call function with appropriate arguments + if needs_token: + await email_func(email_data["email"], email_data["username"], email_data["token"]) + else: + await email_func(email_data["email"], email_data["username"]) + + # Verify email was sent + mock_email_sender.assert_called_once() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("email_func", "needs_token"), + [ + (send_registration_email, True), + (send_reset_password_email, True), + (send_verification_email, True), + (send_post_verification_email, False), + ], +) +async def test_all_email_functions_support_background_tasks( + email_data: dict, + mock_email_sender: AsyncMock, + email_func: Any, + *, + needs_token: bool, +) -> None: + """Test that all email functions support background tasks.""" + background_tasks = MagicMock(spec=BackgroundTasks) + + # Call function with background tasks + if needs_token: + await email_func( + email_data["email"], email_data["username"], email_data["token"], background_tasks=background_tasks + ) + else: + await email_func(email_data["email"], email_data["username"], background_tasks=background_tasks) + + # Verify task was queued, not sent immediately + background_tasks.add_task.assert_called_once() + mock_email_sender.assert_not_called() diff --git a/backend/tests/unit/test_auth_exceptions.py b/backend/tests/unit/test_auth_exceptions.py new file mode 100644 index 0000000..255a3c5 --- /dev/null +++ b/backend/tests/unit/test_auth_exceptions.py @@ -0,0 +1,465 @@ +"""Tests for authentication exceptions module. + +Tests validate exception hierarchy, HTTP status codes, and message formatting. +""" + +from uuid import uuid4 + +import pytest +from fastapi import status +from pydantic import UUID4 + +from app.api.auth.exceptions import ( + AlreadyMemberError, + AuthCRUDError, + DisposableEmailError, + OrganizationHasMembersError, + OrganizationNameExistsError, + UserDoesNotOwnOrgError, + UserHasNoOrgError, + UserIsNotMemberError, + UserNameAlreadyExistsError, + UserOwnershipError, + UserOwnsOrgError, +) +from app.api.common.exceptions import APIError + + +@pytest.mark.unit +class TestAuthCRUDErrorHierarchy: + """Test the exception class hierarchy.""" + + def test_auth_crud_error_is_api_error(self): + """Verify AuthCRUDError inherits from APIError.""" + assert issubclass(AuthCRUDError, APIError) + + def test_user_name_already_exists_error_is_auth_crud_error(self): + """Verify UserNameAlreadyExistsError inherits from AuthCRUDError.""" + assert issubclass(UserNameAlreadyExistsError, AuthCRUDError) + + def test_already_member_error_is_auth_crud_error(self): + """Verify AlreadyMemberError inherits from AuthCRUDError.""" + assert issubclass(AlreadyMemberError, AuthCRUDError) + + def test_user_ownership_error_is_api_error_not_auth_crud(self): + """Verify UserOwnershipError inherits from APIError directly, not AuthCRUDError.""" + assert issubclass(UserOwnershipError, APIError) + assert not issubclass(UserOwnershipError, AuthCRUDError) + + +@pytest.mark.unit +class TestUserNameAlreadyExistsError: + """Tests for UserNameAlreadyExistsError.""" + + def test_http_status_code_is_409_conflict(self): + """Verify UserNameAlreadyExistsError has 409 Conflict status.""" + assert UserNameAlreadyExistsError.http_status_code == status.HTTP_409_CONFLICT + + def test_error_message_includes_username(self): + """Verify error message includes the duplicate username.""" + username = "duplicate_user" + error = UserNameAlreadyExistsError(username=username) + assert username in error.message + assert "already taken" in error.message.lower() + + def test_error_message_with_special_characters(self): + """Verify error message handles usernames with special characters.""" + username = "user@example.com" + error = UserNameAlreadyExistsError(username=username) + assert username in error.message + + def test_error_message_with_unicode_username(self): + """Verify error message handles unicode usernames.""" + username = "用户名" # Chinese characters + error = UserNameAlreadyExistsError(username=username) + assert username in error.message + + +@pytest.mark.unit +class TestAlreadyMemberError: + """Tests for AlreadyMemberError.""" + + def test_http_status_code_is_409_conflict(self): + """Verify AlreadyMemberError has 409 Conflict status.""" + assert AlreadyMemberError.http_status_code == status.HTTP_409_CONFLICT + + def test_error_message_without_user_id(self): + """Verify error message without user_id uses personal phrasing.""" + error = AlreadyMemberError() + assert "You already belong to an organization" in error.message + + def test_error_message_with_user_id(self): + """Verify error message includes user_id when provided.""" + user_id = uuid4() + error = AlreadyMemberError(user_id=user_id) + assert str(user_id) in error.message + assert "already belongs to an organization" in error.message + + def test_error_message_with_user_id_and_details(self): + """Verify error message includes both user_id and details.""" + user_id = uuid4() + details = "User is an active member" + error = AlreadyMemberError(user_id=user_id, details=details) + assert str(user_id) in error.message + assert details in error.message + + def test_error_message_with_details_only(self): + """Verify error message includes details without user_id.""" + details = "Additional context" + error = AlreadyMemberError(details=details) + assert "You already belong to an organization" in error.message + assert details in error.message + + +@pytest.mark.unit +class TestUserOwnsOrgError: + """Tests for UserOwnsOrgError.""" + + def test_http_status_code_is_409_conflict(self): + """Verify UserOwnsOrgError has 409 Conflict status.""" + assert UserOwnsOrgError.http_status_code == status.HTTP_409_CONFLICT + + def test_error_message_without_user_id(self): + """Verify error message without user_id uses personal phrasing.""" + error = UserOwnsOrgError() + assert "You own an organization" in error.message + + def test_error_message_with_user_id(self): + """Verify error message includes user_id when provided.""" + user_id = uuid4() + error = UserOwnsOrgError(user_id=user_id) + assert str(user_id) in error.message + assert "owns an organization" in error.message + + def test_error_message_with_user_id_and_details(self): + """Verify error message includes both user_id and details.""" + user_id = uuid4() + details = "User must transfer ownership" + error = UserOwnsOrgError(user_id=user_id, details=details) + assert str(user_id) in error.message + assert details in error.message + + +@pytest.mark.unit +class TestUserHasNoOrgError: + """Tests for UserHasNoOrgError.""" + + def test_http_status_code_is_404_not_found(self): + """Verify UserHasNoOrgError has 404 Not Found status.""" + assert UserHasNoOrgError.http_status_code == status.HTTP_404_NOT_FOUND + + def test_error_message_without_user_id(self): + """Verify error message without user_id uses personal phrasing.""" + error = UserHasNoOrgError() + assert "You do not belong to an organization" in error.message + + def test_error_message_with_user_id(self): + """Verify error message includes user_id when provided.""" + user_id = uuid4() + error = UserHasNoOrgError(user_id=user_id) + assert str(user_id) in error.message + assert "does not belong to an organization" in error.message + + def test_error_message_with_user_id_and_details(self): + """Verify error message includes both user_id and details.""" + user_id = uuid4() + details = "User needs to join first" + error = UserHasNoOrgError(user_id=user_id, details=details) + assert str(user_id) in error.message + assert details in error.message + + +@pytest.mark.unit +class TestUserIsNotMemberError: + """Tests for UserIsNotMemberError.""" + + def test_http_status_code_is_403_forbidden(self): + """Verify UserIsNotMemberError has 403 Forbidden status.""" + assert UserIsNotMemberError.http_status_code == status.HTTP_403_FORBIDDEN + + def test_error_message_without_ids(self): + """Verify error message without IDs uses personal phrasing.""" + error = UserIsNotMemberError() + assert "You do not belong to this organization" in error.message + + def test_error_message_with_user_id_only(self): + """Verify error message with user_id only.""" + user_id = uuid4() + error = UserIsNotMemberError(user_id=user_id) + assert str(user_id) in error.message + assert "does not belong to the organization" in error.message + + def test_error_message_with_organization_id_only(self): + """Verify error message with organization_id only uses generic message.""" + org_id = uuid4() + error = UserIsNotMemberError(organization_id=org_id) + # When only org_id is provided (no user_id), uses generic personal message + assert "You do not belong to this organization" in error.message + # org_id is only included in message if BOTH user_id and org_id are provided + assert str(org_id) not in error.message + + def test_error_message_with_both_ids(self): + """Verify error message with both user_id and organization_id.""" + user_id = uuid4() + org_id = uuid4() + error = UserIsNotMemberError(user_id=user_id, organization_id=org_id) + assert str(user_id) in error.message + assert str(org_id) in error.message + + def test_error_message_with_ids_and_details(self): + """Verify error message with all three parameters.""" + user_id = uuid4() + org_id = uuid4() + details = "Membership denied" + error = UserIsNotMemberError(user_id=user_id, organization_id=org_id, details=details) + assert str(user_id) in error.message + assert str(org_id) in error.message + assert details in error.message + + +@pytest.mark.unit +class TestUserDoesNotOwnOrgError: + """Tests for UserDoesNotOwnOrgError.""" + + def test_http_status_code_is_403_forbidden(self): + """Verify UserDoesNotOwnOrgError has 403 Forbidden status.""" + assert UserDoesNotOwnOrgError.http_status_code == status.HTTP_403_FORBIDDEN + + def test_error_message_without_user_id(self): + """Verify error message without user_id uses personal phrasing.""" + error = UserDoesNotOwnOrgError() + assert "You do not own an organization" in error.message + + def test_error_message_with_user_id(self): + """Verify error message includes user_id when provided.""" + user_id = uuid4() + error = UserDoesNotOwnOrgError(user_id=user_id) + assert str(user_id) in error.message + assert "does not own an organization" in error.message + + def test_error_message_with_user_id_and_details(self): + """Verify error message includes both user_id and details.""" + user_id = uuid4() + details = "Owner privileges required" + error = UserDoesNotOwnOrgError(user_id=user_id, details=details) + assert str(user_id) in error.message + assert details in error.message + + +@pytest.mark.unit +class TestOrganizationHasMembersError: + """Tests for OrganizationHasMembersError.""" + + def test_http_status_code_is_409_conflict(self): + """Verify OrganizationHasMembersError has 409 Conflict status.""" + assert OrganizationHasMembersError.http_status_code == status.HTTP_409_CONFLICT + + def test_error_message_without_organization_id(self): + """Verify error message without organization_id.""" + error = OrganizationHasMembersError() + assert "has members and cannot be deleted" in error.message + assert "Transfer ownership or remove members first" in error.message + + def test_error_message_with_organization_id(self): + """Verify error message includes organization_id when provided.""" + org_id = uuid4() + error = OrganizationHasMembersError(organization_id=org_id) + assert str(org_id) in error.message + assert "has members and cannot be deleted" in error.message + + def test_error_message_includes_remediation_guidance(self): + """Verify error message includes remediation steps.""" + error = OrganizationHasMembersError() + assert "Transfer ownership" in error.message or "remove members" in error.message + + +@pytest.mark.unit +class TestOrganizationNameExistsError: + """Tests for OrganizationNameExistsError.""" + + def test_http_status_code_is_409_conflict(self): + """Verify OrganizationNameExistsError has 409 Conflict status.""" + assert OrganizationNameExistsError.http_status_code == status.HTTP_409_CONFLICT + + def test_default_error_message(self): + """Verify default error message when no message provided.""" + error = OrganizationNameExistsError() + assert "Organization with this name already exists" in error.message + + def test_custom_error_message(self): + """Verify custom error message can be provided.""" + custom_msg = "Custom organization error" + error = OrganizationNameExistsError(msg=custom_msg) + assert custom_msg in error.message + + +@pytest.mark.unit +class TestUserOwnershipError: + """Tests for UserOwnershipError.""" + + def test_http_status_code_is_403_forbidden(self): + """Verify UserOwnershipError has 403 Forbidden status.""" + assert UserOwnershipError.http_status_code == status.HTTP_403_FORBIDDEN + + def test_error_message_includes_model_name(self): + """Verify error message includes the model name.""" + # Using a mock model type that has get_api_model_name method + from unittest.mock import Mock + + mock_model = Mock() + mock_model.get_api_model_name.return_value.name_capital = "TestModel" + + user_id = uuid4() + model_id = uuid4() + error = UserOwnershipError(model_type=mock_model, model_id=model_id, user_id=user_id) + + assert "TestModel" in error.message + assert str(user_id) in error.message + assert str(model_id) in error.message + + def test_error_message_includes_user_id(self): + """Verify error message includes user_id.""" + from unittest.mock import Mock + + mock_model = Mock() + mock_model.get_api_model_name.return_value.name_capital = "DataSet" + + user_id = uuid4() + model_id = uuid4() + error = UserOwnershipError(model_type=mock_model, model_id=model_id, user_id=user_id) + + assert str(user_id) in error.message + assert "does not own" in error.message.lower() + + def test_error_message_includes_model_id(self): + """Verify error message includes model_id.""" + from unittest.mock import Mock + + mock_model = Mock() + mock_model.get_api_model_name.return_value.name_capital = "Project" + + user_id = uuid4() + model_id = uuid4() + error = UserOwnershipError(model_type=mock_model, model_id=model_id, user_id=user_id) + + assert str(model_id) in error.message + + +@pytest.mark.unit +class TestDisposableEmailError: + """Tests for DisposableEmailError.""" + + def test_http_status_code_is_400_bad_request(self): + """Verify DisposableEmailError has 400 Bad Request status.""" + assert DisposableEmailError.http_status_code == status.HTTP_400_BAD_REQUEST + + def test_error_message_includes_email(self): + """Verify error message includes the disposable email address.""" + email = "temp@tempmail.com" + error = DisposableEmailError(email=email) + assert email in error.message + assert "disposable email" in error.message.lower() + + def test_error_message_with_various_email_formats(self): + """Verify error message handles various email formats.""" + emails = [ + "user@10minutemail.com", + "test@guerrillemail.com", + "name.surname@throwaway.email", + ] + for email in emails: + error = DisposableEmailError(email=email) + assert email in error.message + assert "not allowed" in error.message.lower() + + +@pytest.mark.unit +class TestExceptionInheritanceChain: + """Tests for verifying the complete exception inheritance chain.""" + + def test_all_auth_crud_errors_inherit_from_api_error(self): + """Verify all AuthCRUDError subclasses ultimately inherit from APIError.""" + crud_error_subclasses = [ + UserNameAlreadyExistsError, + AlreadyMemberError, + UserOwnsOrgError, + UserHasNoOrgError, + UserIsNotMemberError, + UserDoesNotOwnOrgError, + OrganizationHasMembersError, + OrganizationNameExistsError, + DisposableEmailError, + ] + + for error_class in crud_error_subclasses: + assert issubclass(error_class, APIError), f"{error_class.__name__} must inherit from APIError" + + def test_exception_can_be_caught_as_api_error(self): + """Verify exceptions can be caught as APIError.""" + try: + raise UserNameAlreadyExistsError(username="test") + except APIError: + pass # Expected + else: + pytest.fail("UserNameAlreadyExistsError should be catchable as APIError") + + def test_exception_can_be_caught_as_auth_crud_error(self): + """Verify AuthCRUDError subclasses can be caught as AuthCRUDError.""" + try: + raise UserNameAlreadyExistsError(username="test") + except AuthCRUDError: + pass # Expected + else: + pytest.fail("UserNameAlreadyExistsError should be catchable as AuthCRUDError") + + +@pytest.mark.unit +class TestExceptionStatusCodes: + """Tests for verifying all status codes are correctly set.""" + + def test_409_conflict_errors(self): + """Verify all 409 Conflict errors have correct status code.""" + conflict_errors = [ + UserNameAlreadyExistsError("test"), + AlreadyMemberError(), + UserOwnsOrgError(), + OrganizationHasMembersError(), + OrganizationNameExistsError(), + ] + + for error in conflict_errors: + assert error.http_status_code == status.HTTP_409_CONFLICT + + def test_403_forbidden_errors(self): + """Verify all 403 Forbidden errors have correct status code.""" + forbidden_errors = [ + UserIsNotMemberError(), + UserDoesNotOwnOrgError(), + ] + + for error in forbidden_errors: + assert error.http_status_code == status.HTTP_403_FORBIDDEN + + def test_404_not_found_errors(self): + """Verify all 404 Not Found errors have correct status code.""" + error = UserHasNoOrgError() + assert error.http_status_code == status.HTTP_404_NOT_FOUND + + def test_400_bad_request_errors(self): + """Verify all 400 Bad Request errors have correct status code.""" + error = DisposableEmailError(email="test@tempmail.com") + assert error.http_status_code == status.HTTP_400_BAD_REQUEST + + def test_403_ownership_error(self): + """Verify UserOwnershipError has 403 Forbidden status code.""" + from unittest.mock import Mock + + mock_model = Mock() + mock_model.get_api_model_name.return_value.name_capital = "TestModel" + + error = UserOwnershipError( + model_type=mock_model, + model_id=uuid4(), + user_id=uuid4(), + ) + assert error.http_status_code == status.HTTP_403_FORBIDDEN diff --git a/backend/tests/unit/test_background_data_schemas.py b/backend/tests/unit/test_background_data_schemas.py new file mode 100644 index 0000000..05f7707 --- /dev/null +++ b/backend/tests/unit/test_background_data_schemas.py @@ -0,0 +1,163 @@ +"""Unit tests for background data models (no database required).""" + +import pytest +from pydantic import ValidationError + +from app.api.background_data.models import TaxonomyDomain +from app.api.background_data.schemas import ( + CategoryCreate, + CategoryUpdate, + MaterialCreate, + MaterialUpdate, + ProductTypeCreate, + TaxonomyCreate, + TaxonomyUpdate, +) + + +@pytest.mark.unit +class TestTaxonomySchemas: + """Test Taxonomy schema validation.""" + + def test_taxonomy_create_valid(self): + """Test creating valid TaxonomyCreate schema.""" + data = { + "name": "Test Taxonomy", + "version": "v1.0.0", + "description": "A test taxonomy", + "domains": {"materials"}, + "source": "https://example.com", + } + schema = TaxonomyCreate(**data) + + assert schema.name == "Test Taxonomy" + assert schema.version == "v1.0.0" + assert schema.domains == {TaxonomyDomain.MATERIALS} + + def test_taxonomy_create_name_too_short(self): + """Test TaxonomyCreate rejects name that's too short.""" + with pytest.raises(ValidationError) as exc_info: + TaxonomyCreate( + name="A", # Too short + version="v1.0.0", + domains={"materials"}, + ) + + errors = exc_info.value.errors() + assert any(e["loc"][0] == "name" for e in errors) + + def test_taxonomy_create_multiple_domains(self): + """Test taxonomy with multiple domains.""" + schema = TaxonomyCreate( + name="Multi-domain Taxonomy", + version="v1.0.0", + domains={"materials", "products"}, + ) + + assert len(schema.domains) == 2 + assert TaxonomyDomain.MATERIALS in schema.domains + assert TaxonomyDomain.PRODUCTS in schema.domains + + def test_taxonomy_update_partial(self): + """Test TaxonomyUpdate with partial data.""" + schema = TaxonomyUpdate(name="Updated Name", domains={"materials"}) + + assert schema.name == "Updated Name" + assert schema.version is None + assert schema.description is None + + +@pytest.mark.unit +class TestCategorySchemas: + """Test Category schema validation.""" + + def test_category_create_valid(self): + """Test creating valid CategoryCreate schema.""" + schema = CategoryCreate( + name="Test Category", + description="A test category", + taxonomy_id=1, + ) + + assert schema.name == "Test Category" + assert schema.taxonomy_id == 1 + + def test_category_create_minimal(self): + """Test CategoryCreate with only required fields.""" + schema = CategoryCreate(name="Minimal Category") + + assert schema.name == "Minimal Category" + assert schema.taxonomy_id is None + + def test_category_update_partial(self): + """Test CategoryUpdate with partial data.""" + schema = CategoryUpdate(name="Updated Category") + + assert schema.name == "Updated Category" + assert schema.description is None + + +@pytest.mark.unit +class TestMaterialSchemas: + """Test Material schema validation.""" + + def test_material_create_valid(self): + """Test creating valid MaterialCreate schema.""" + schema = MaterialCreate( + name="Steel", + description="Iron-carbon alloy", + density_kg_m3=7850.0, + is_crm=False, + ) + + assert schema.name == "Steel" + assert schema.density_kg_m3 == 7850.0 + assert schema.is_crm is False + + def test_material_create_negative_density_fails(self): + """Test MaterialCreate rejects negative density.""" + with pytest.raises(ValidationError) as exc_info: + MaterialCreate( + name="Invalid Material", + density_kg_m3=-100.0, + ) + + errors = exc_info.value.errors() + assert any(e["loc"][0] == "density_kg_m3" for e in errors) + + def test_material_create_zero_density_fails(self): + """Test MaterialCreate rejects zero density.""" + with pytest.raises(ValidationError): + MaterialCreate( + name="Invalid Material", + density_kg_m3=0.0, + ) + + def test_material_update_partial(self): + """Test MaterialUpdate with partial data.""" + schema = MaterialUpdate(density_kg_m3=8000.0) + + assert schema.density_kg_m3 == 8000.0 + assert schema.name is None + + +@pytest.mark.unit +class TestProductTypeSchemas: + """Test ProductType schema validation.""" + + def test_product_type_create_valid(self): + """Test creating valid Product TypeCreate schema.""" + schema = ProductTypeCreate( + name="Electronics", + description="Electronic products", + ) + + assert schema.name == "Electronics" + assert schema.description == "Electronic products" + + def test_product_type_create_minimal(self): + """Test ProductTypeCreate with only name.""" + schema = ProductTypeCreate(name="Minimal") + + assert schema.name == "Minimal" + assert schema.description is None diff --git a/backend/tests/unit/test_common_utils.py b/backend/tests/unit/test_common_utils.py new file mode 100644 index 0000000..3fd9dfc --- /dev/null +++ b/backend/tests/unit/test_common_utils.py @@ -0,0 +1,252 @@ +"""Unit tests for common utilities. + +Tests utilities, helpers, and common functions (no database required). +Demonstrates pytest-mock usage for mocking external dependencies. +""" + +import pytest + +from app.api.common.exceptions import APIError + + +@pytest.mark.unit +class TestAPIError: + """Test custom API error exception.""" + + def test_api_error_creation_message_only(self): + """Test creating APIError with just a message.""" + error = APIError(message="Test error") + + assert error.message == "Test error" + assert error.details is None + assert str(error) == "Test error" + + def test_api_error_creation_with_details(self): + """Test creating APIError with message and details.""" + error = APIError( + message="Validation failed", + details="Field 'email' is invalid", + ) + + assert error.message == "Validation failed" + assert error.details == "Field 'email' is invalid" + + def test_api_error_default_status_code(self): + """Test default HTTP status code is 500.""" + error = APIError(message="Internal error") + assert error.http_status_code == 500 + + def test_api_error_inheritable(self): + """Test that APIError can be subclassed with custom status codes.""" + + class NotFoundError(APIError): + http_status_code = 404 + + error = NotFoundError(message="Resource not found") + assert error.http_status_code == 404 + + def test_api_error_is_exception(self): + """Test that APIError is a proper Exception subclass.""" + error = APIError(message="Test") + assert isinstance(error, Exception) + + with pytest.raises(APIError): + raise error + + +@pytest.mark.unit +class TestMockingExamples: + """Demonstrate pytest-mock usage for testing patterns.""" + + def test_mock_simple_function(self, mocker): + """Example: Mock a simple function.""" + # Create a mock object + mock_func = mocker.MagicMock(return_value=42) + + # Call the mock + result = mock_func(1, 2, 3) + + # Assertions + assert result == 42 + mock_func.assert_called_once_with(1, 2, 3) + + def test_mock_function_side_effects(self, mocker): + """Example: Mock with side effects (exceptions, sequences).""" + # Mock that raises an exception + mock_func = mocker.MagicMock(side_effect=ValueError("Invalid value")) + + with pytest.raises(ValueError, match="Invalid value"): + mock_func() + + def test_mock_function_call_count(self, mocker): + """Example: Verify mock was called specific number of times.""" + mock_func = mocker.MagicMock() + + # Call multiple times + mock_func() + mock_func() + mock_func() + + # Verify call count + assert mock_func.call_count == 3 + + def test_patch_module_import(self, mocker): + """Example: Patch imports to simulate external dependencies.""" + # Mock an external module + mock_module = mocker.MagicMock() + mocker.patch.dict("sys.modules", {"fake_module": mock_module}) + + # Now code importing fake_module would get the mock + assert mock_module is not None + + def test_patch_class_method(self, mocker): + """Example: Patch a method on a class.""" + + class MyClass: + def method(self): + return "original" + + obj = MyClass() + original_result = obj.method() + assert original_result == "original" + + # Patch the method + mocker.patch.object(MyClass, "method", return_value="mocked") + obj2 = MyClass() + assert obj2.method() == "mocked" + + def test_spy_function_call(self, mocker): + """Example: Spy on function calls (wrap without replacing).""" + + class ForSpying: + def method(self, x): + return x * 2 + + obj = ForSpying() + spy = mocker.spy(obj, "method") + + result = obj.method(10) + + assert result == 20 + spy.assert_called_once_with(10) + + +@pytest.mark.unit +class TestValidationPatterns: + """Test examples for validation logic that doesn't require database.""" + + def test_string_length_validation(self): + """Example: Test string length validation.""" + + def validate_name(name: str, min_length: int = 1, max_length: int = 255) -> str: + """Validate name length.""" + if not name or len(name) < min_length: + raise ValueError(f"Name must be at least {min_length} character(s)") + if len(name) > max_length: + raise ValueError(f"Name cannot exceed {max_length} characters") + return name + + # Happy path + assert validate_name("Test") == "Test" + + # Error cases + with pytest.raises(ValueError, match="must be at least"): + validate_name("") + + with pytest.raises(ValueError, match="cannot exceed"): + validate_name("a" * 300) + + def test_enum_validation(self): + """Example: Test enum validation.""" + from enum import Enum + + class Status(str, Enum): + ACTIVE = "active" + INACTIVE = "inactive" + PENDING = "pending" + + def validate_status(status: str) -> Status: + """Validate and return Status enum.""" + try: + return Status(status) + except ValueError: + raise ValueError(f"Invalid status: {status}") + + # Happy path + assert validate_status("active") == Status.ACTIVE + + # Error case + with pytest.raises(ValueError, match="Invalid status"): + validate_status("invalid") + + def test_type_validation(self): + """Example: Test type validation.""" + + def validate_port(port) -> int: + """Validate port number.""" + if not isinstance(port, int): + raise TypeError(f"Port must be int, got {type(port).__name__}") + if not 1 <= port <= 65535: + raise ValueError(f"Port must be 1-65535, got {port}") + return port + + # Happy path + assert validate_port(8000) == 8000 + + # Type error + with pytest.raises(TypeError): + validate_port("8000") + + # Value error + with pytest.raises(ValueError, match="Port must be 1-65535"): + validate_port(99999) + + +@pytest.mark.unit +class TestAsyncUtilityPatterns: + """Examples of testing async utilities with pytest-mock.""" + + @pytest.mark.asyncio + async def test_async_mock_example(self, mocker): + """Example: Mock async functions.""" + # Create an async mock + mock_async_func = mocker.AsyncMock(return_value="async result") + + # Call it + result = await mock_async_func() + + # Verify + assert result == "async result" + mock_async_func.assert_called_once() + + @pytest.mark.asyncio + async def test_async_mock_with_side_effect(self, mocker): + """Example: Async mock that raises exceptions.""" + # Create async mock that raises + mock_func = mocker.AsyncMock(side_effect=RuntimeError("Async error")) + + # Verify it raises + with pytest.raises(RuntimeError, match="Async error"): + await mock_func() + + @pytest.mark.asyncio + async def test_async_context_manager_mock(self, mocker): + """Example: Mock async context managers.""" + + class AsyncResource: + async def __aenter__(self): + return "resource" + + async def __aexit__(self, *args): + pass + + # Create mock context manager + mock_resource = mocker.MagicMock() + mock_resource.__aenter__ = mocker.AsyncMock(return_value="mocked_resource") + mock_resource.__aexit__ = mocker.AsyncMock(return_value=None) + + # Use it + async with mock_resource as resource: + assert resource == "mocked_resource" + + mock_resource.__aenter__.assert_called_once() diff --git a/backend/tests/unit/test_core_config.py b/backend/tests/unit/test_core_config.py new file mode 100644 index 0000000..c36bb1c --- /dev/null +++ b/backend/tests/unit/test_core_config.py @@ -0,0 +1,283 @@ +"""Unit tests for core configuration loading and validation. + +Tests configuration defaults, environment variable parsing, and validation. +""" + +import pytest +from pydantic import BaseModel, Field, ValidationError + + +@pytest.mark.unit +class TestConfigurationPatterns: + """Test patterns for configuration validation.""" + + def test_config_with_defaults(self): + """Test configuration with sensible defaults.""" + + class AppConfig(BaseModel): + """App configuration with defaults.""" + + debug: bool = False + log_level: str = "INFO" + database_url: str = "sqlite:///app.db" + max_connections: int = 10 + + # Create with defaults + config = AppConfig() + assert config.debug is False + assert config.log_level == "INFO" + assert config.database_url == "sqlite:///app.db" + + def test_config_override_defaults(self): + """Test overriding default configuration values.""" + + class AppConfig(BaseModel): + """App configuration.""" + + debug: bool = False + port: int = 8000 + + # Override defaults + config = AppConfig(debug=True, port=9000) + assert config.debug is True + assert config.port == 9000 + + def test_config_validation_constraints(self): + """Test configuration validation constraints.""" + + class DatabaseConfig(BaseModel): + """Database configuration with constraints.""" + + host: str + port: int = Field(ge=1, le=65535) # Port range validation + min_connections: int = Field(ge=1) + max_connections: int = Field(ge=1) + + # Valid config + config = DatabaseConfig( + host="localhost", + port=5432, + min_connections=5, + max_connections=20, + ) + assert config.port == 5432 + + # Invalid port + with pytest.raises(ValidationError) as exc_info: + DatabaseConfig( + host="localhost", + port=99999, # Out of range + min_connections=1, + max_connections=10, + ) + errors = exc_info.value.errors() + assert any(e["loc"][0] == "port" for e in errors) + + def test_config_required_fields(self): + """Test that required fields are enforced.""" + + class ApiConfig(BaseModel): + """API configuration with required fields.""" + + api_key: str # Required, no default + api_secret: str # Required + timeout: int = 30 # Optional with default + + # Missing required field + with pytest.raises(ValidationError) as exc_info: + ApiConfig(api_key="key123") # Missing api_secret + + errors = exc_info.value.errors() + assert any(e["loc"][0] == "api_secret" for e in errors) + + def test_config_optional_fields(self): + """Test optional fields with None defaults.""" + + class OptionalConfig(BaseModel): + """Configuration with optional fields.""" + + required_field: str + optional_field: str | None = None + optional_with_default: str | None = "default" + + # Can be created without optional fields + config = OptionalConfig(required_field="test") + assert config.optional_field is None + assert config.optional_with_default == "default" + + def test_config_computed_fields(self): + """Test computed/derived fields in configuration.""" + from pydantic import computed_field + + class UrlConfig(BaseModel): + """Configuration with computed URL field.""" + + protocol: str = "https" + host: str + port: int = 443 + + @computed_field + @property + def url(self) -> str: + """Compute full URL.""" + return f"{self.protocol}://{self.host}:{self.port}" + + config = UrlConfig(host="example.com") + assert config.url == "https://example.com:443" + + config2 = UrlConfig(protocol="http", host="localhost", port=8000) + assert config2.url == "http://localhost:8000" + + def test_config_field_validation(self): + """Test custom field validation logic.""" + + class PasswordConfig(BaseModel): + """Configuration with password validation.""" + + password: str = Field(min_length=8) + + def __init__(self, **data): + super().__init__(**data) + # Custom validation + if not any(c.isupper() for c in self.password): + raise ValueError("Password must contain uppercase letter") + + # Valid password + config = PasswordConfig(password="MyPassword123") + assert config.password == "MyPassword123" + + # Too short + with pytest.raises(ValidationError): + PasswordConfig(password="short") + + # No uppercase + with pytest.raises(ValueError, match="uppercase"): + PasswordConfig(password="mypassword123") + + def test_config_environment_like_parsing(self): + """Test configuration parsing from dict like environment variables.""" + + class EnvConfig(BaseModel): + """Parse config from environment-like dict.""" + + database_url: str + redis_host: str = "localhost" + redis_port: int = 6379 + + # Simulate environment variable dict + env_dict = { + "database_url": "postgresql://user:pass@localhost/db", + "redis_host": "redis.example.com", + "redis_port": "6380", # String in env, should convert to int + } + + config = EnvConfig(**env_dict) + assert config.database_url == "postgresql://user:pass@localhost/db" + assert config.redis_host == "redis.example.com" + assert config.redis_port == 6380 # Converted to int + + def test_config_mode_validation(self): + """Test configuration modes (development, staging, production).""" + + class ModeConfig(BaseModel): + """Configuration based on mode.""" + + mode: str = "development" + debug: bool = False + + def __init__(self, **data): + super().__init__(**data) + # Auto-set debug based on mode + if self.mode == "development": + self.debug = True + elif self.mode == "production": + self.debug = False + + dev_config = ModeConfig(mode="development") + assert dev_config.debug is True + + prod_config = ModeConfig(mode="production") + assert prod_config.debug is False + + +@pytest.mark.unit +class TestConfigurationEdgeCases: + """Test edge cases and error conditions in configuration.""" + + def test_config_type_coercion(self): + """Test automatic type coercion.""" + + class TypeConfig(BaseModel): + count: int + ratio: float + enabled: bool + + # String to int + config = TypeConfig(count="42", ratio="3.14", enabled="true") + assert config.count == 42 + assert isinstance(config.count, int) + assert config.ratio == 3.14 + assert config.enabled is True + + def test_config_empty_strings(self): + """Test handling of empty strings.""" + + class StringConfig(BaseModel): + required_string: str + optional_string: str | None = None + + # Empty string for required field is allowed (pydantic default) + config = StringConfig(required_string="") + assert config.required_string == "" + + def test_config_whitespace_handling(self): + """Test whitespace handling in configuration.""" + + class NameConfig(BaseModel): + name: str + + # Whitespace is preserved + config = NameConfig(name=" test ") + assert config.name == " test " + + def test_config_case_sensitivity(self): + """Test that config keys are case-sensitive by default.""" + + class CaseConfig(BaseModel): + DatabaseUrl: str + + # Exact case match works + config = CaseConfig(DatabaseUrl="postgres://localhost") + assert config.DatabaseUrl == "postgres://localhost" + + # Wrong case fails + with pytest.raises(ValidationError): + CaseConfig(databaseUrl="postgres://localhost") + + def test_config_extra_fields_ignored(self): + """Test behavior with extra fields.""" + + class StrictConfig(BaseModel): + model_config = {"extra": "ignore"} + + name: str + + # Extra fields are silently ignored + config = StrictConfig(name="test", extra_field="ignored") + assert config.name == "test" + assert not hasattr(config, "extra_field") + + def test_config_extra_fields_error(self): + """Test error on extra fields when configured to forbid.""" + + class StrictConfig(BaseModel): + model_config = {"extra": "forbid"} + + name: str + + # Extra fields cause ValidationError + with pytest.raises(ValidationError) as exc_info: + StrictConfig(name="test", extra_field="not allowed") + + errors = exc_info.value.errors() + assert any(e["type"] == "extra_forbidden" for e in errors) diff --git a/backend/tests/unit/test_data_collection_schemas.py b/backend/tests/unit/test_data_collection_schemas.py new file mode 100644 index 0000000..d57f582 --- /dev/null +++ b/backend/tests/unit/test_data_collection_schemas.py @@ -0,0 +1,539 @@ +"""Tests for data collection schema validation. + +Tests validate Pydantic schemas for creating, reading, and updating products, +physical properties, and circularity properties using ProductCreateBaseProduct +and related schemas. +""" + +from datetime import UTC, datetime, timedelta +from uuid import uuid4 + +import pytest +from pydantic import ValidationError + +from app.api.data_collection.schemas import ( + CircularityPropertiesCreate, + CircularityPropertiesRead, + CircularityPropertiesUpdate, + PhysicalPropertiesCreate, + PhysicalPropertiesRead, + PhysicalPropertiesUpdate, + ProductCreateBaseProduct, + ValidDateTime, + ensure_timezone, + not_too_old, +) + + +@pytest.mark.unit +class TestValidatorsCommon: + """Tests for common validators used across schemas.""" + + def test_ensure_timezone_with_aware_datetime(self): + """Verify ensure_timezone accepts timezone-aware datetime.""" + dt = datetime.now(UTC) + result = ensure_timezone(dt) + assert result == dt + assert result.tzinfo is not None + + def test_ensure_timezone_rejects_naive_datetime(self): + """Verify ensure_timezone rejects naive datetime.""" + dt = datetime.now() # No timezone + with pytest.raises(ValueError) as exc_info: + ensure_timezone(dt) + assert "timezone" in str(exc_info.value).lower() + + def test_not_too_old_recent_datetime(self): + """Verify not_too_old accepts recent datetime.""" + dt = datetime.now(UTC) - timedelta(days=30) + result = not_too_old(dt) + assert result == dt + + def test_not_too_old_rejects_old_datetime(self): + """Verify not_too_old rejects datetime older than 365 days.""" + dt = datetime.now(UTC) - timedelta(days=366) + with pytest.raises(ValueError) as exc_info: + not_too_old(dt) + assert "365" in str(exc_info.value) or "days" in str(exc_info.value).lower() + + def test_not_too_old_accepts_boundary_date(self): + """Verify not_too_old accepts datetime within 365 days.""" + # Use a date 364 days in the past (safely within boundary) + dt = datetime.now(UTC) - timedelta(days=364) + result = not_too_old(dt) + assert result == dt + + def test_not_too_old_with_custom_delta(self): + """Verify not_too_old respects custom time delta.""" + custom_delta = timedelta(days=30) + old_dt = datetime.now(UTC) - timedelta(days=61) + with pytest.raises(ValueError): + not_too_old(old_dt, time_delta=custom_delta) + + +@pytest.mark.unit +class TestPhysicalPropertiesCreate: + """Tests for PhysicalPropertiesCreate schema.""" + + def test_create_with_all_fields(self): + """Verify creating physical properties with all fields.""" + data = { + "weight_g": 20000.0, + "height_cm": 150.0, + "width_cm": 70.0, + "depth_cm": 50.0, + } + props = PhysicalPropertiesCreate(**data) + + assert props.weight_g == 20000.0 + assert props.height_cm == 150.0 + assert props.width_cm == 70.0 + assert props.depth_cm == 50.0 + + def test_create_with_partial_fields(self): + """Verify creating physical properties with only some fields.""" + data = {"weight_g": 5000.0} + props = PhysicalPropertiesCreate(**data) + + assert props.weight_g == 5000.0 + assert props.height_cm is None + + def test_create_with_no_fields(self): + """Verify creating physical properties with no fields.""" + props = PhysicalPropertiesCreate() + + assert props.weight_g is None + assert props.height_cm is None + + def test_weight_must_be_positive(self): + """Verify weight must be positive.""" + data = {"weight_g": -1000.0} + with pytest.raises(ValidationError): + PhysicalPropertiesCreate(**data) + + def test_height_must_be_positive(self): + """Verify height must be positive.""" + data = {"height_cm": 0.0} + with pytest.raises(ValidationError): + PhysicalPropertiesCreate(**data) + + def test_width_must_be_positive(self): + """Verify width must be positive.""" + data = {"width_cm": -100.0} + with pytest.raises(ValidationError): + PhysicalPropertiesCreate(**data) + + def test_depth_must_be_positive(self): + """Verify depth must be positive.""" + data = {"depth_cm": -50.0} + with pytest.raises(ValidationError): + PhysicalPropertiesCreate(**data) + + def test_fractional_dimensions(self): + """Verify fractional dimensions are accepted.""" + data = { + "height_cm": 10.5, + "width_cm": 20.75, + "depth_cm": 5.25, + } + props = PhysicalPropertiesCreate(**data) + + assert props.height_cm == 10.5 + assert props.width_cm == 20.75 + + +@pytest.mark.unit +class TestPhysicalPropertiesRead: + """Tests for PhysicalPropertiesRead schema.""" + + def test_read_with_all_fields(self): + """Verify read schema accepts all fields with id.""" + data = { + "id": 1, + "weight_g": 20000.0, + "height_cm": 150.0, + "width_cm": 70.0, + "depth_cm": 50.0, + "created_at": datetime.now(UTC), + "updated_at": datetime.now(UTC), + } + props = PhysicalPropertiesRead(**data) + + assert props.id == 1 + assert props.weight_g == 20000.0 + + def test_read_requires_id(self): + """Verify read schema requires id field.""" + data = { + "weight_g": 20000.0, + "created_at": datetime.now(UTC), + "updated_at": datetime.now(UTC), + } + with pytest.raises(ValidationError): + PhysicalPropertiesRead(**data) + + +@pytest.mark.unit +class TestPhysicalPropertiesUpdate: + """Tests for PhysicalPropertiesUpdate schema.""" + + def test_update_single_field(self): + """Verify updating single field.""" + data = {"weight_g": 15000.0} + props = PhysicalPropertiesUpdate(**data) + + assert props.weight_g == 15000.0 + assert props.height_cm is None + + def test_update_multiple_fields(self): + """Verify updating multiple fields.""" + data = { + "weight_g": 15000.0, + "height_cm": 120.0, + } + props = PhysicalPropertiesUpdate(**data) + + assert props.weight_g == 15000.0 + assert props.height_cm == 120.0 + + def test_update_no_fields(self): + """Verify updating with no fields is allowed.""" + props = PhysicalPropertiesUpdate() + + assert props.weight_g is None + + +@pytest.mark.unit +class TestCircularityPropertiesCreate: + """Tests for CircularityPropertiesCreate schema.""" + + def test_create_with_all_fields(self): + """Verify creating circularity properties with all fields.""" + data = { + "recyclability_observation": "Can be recycled", + "recyclability_comment": "Recyclable", + "recyclability_reference": "ISO 14040", + "repairability_observation": "Can be repaired", + "repairability_comment": "Repairable", + "repairability_reference": "ISO 20887", + "remanufacturability_observation": "Can be remanufactured", + "remanufacturability_comment": "Remanufacturable", + "remanufacturability_reference": "UNEP 2018", + } + props = CircularityPropertiesCreate(**data) + + assert props.recyclability_observation == "Can be recycled" + assert props.repairability_comment == "Repairable" + + def test_create_with_no_fields(self): + """Verify creating circularity properties with no fields.""" + props = CircularityPropertiesCreate() + + assert props.recyclability_observation is None + assert props.repairability_comment is None + + def test_observation_max_length_500(self): + """Verify observation fields max length is 500.""" + long_text = "a" * 501 + data = {"recyclability_observation": long_text} + + with pytest.raises(ValidationError): + CircularityPropertiesCreate(**data) + + def test_comment_max_length_100(self): + """Verify comment fields max length is 100.""" + long_text = "a" * 101 + data = {"recyclability_comment": long_text} + + with pytest.raises(ValidationError): + CircularityPropertiesCreate(**data) + + def test_observation_exact_max_length(self): + """Verify exactly at max length is accepted.""" + text_500 = "a" * 500 + data = {"recyclability_observation": text_500} + props = CircularityPropertiesCreate(**data) + + assert len(props.recyclability_observation) == 500 + + +@pytest.mark.unit +class TestCircularityPropertiesRead: + """Tests for CircularityPropertiesRead schema.""" + + def test_read_with_all_fields(self): + """Verify read schema accepts all fields.""" + data = { + "id": 1, + "recyclability_observation": "Recyclable", + "created_at": datetime.now(UTC), + "updated_at": datetime.now(UTC), + } + props = CircularityPropertiesRead(**data) + + assert props.id == 1 + assert props.recyclability_observation == "Recyclable" + + +@pytest.mark.unit +class TestCircularityPropertiesUpdate: + """Tests for CircularityPropertiesUpdate schema.""" + + def test_update_single_field(self): + """Verify updating single field.""" + data = {"recyclability_observation": "Updated"} + props = CircularityPropertiesUpdate(**data) + + assert props.recyclability_observation == "Updated" + + def test_update_no_fields(self): + """Verify updating with no fields is allowed.""" + props = CircularityPropertiesUpdate() + + assert props.recyclability_observation is None + + +@pytest.mark.unit +class TestProductCreateBaseProductSchema: + """Tests for ProductCreateBaseProduct schema.""" + + def test_create_with_required_fields(self): + """Verify creating product with required fields.""" + data = {"name": "Test Product"} + product = ProductCreateBaseProduct(**data) + + assert product.name == "Test Product" + + def test_name_min_length_2(self): + """Verify product name must be at least 2 characters.""" + data = {"name": "A"} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_name_max_length_100(self): + """Verify product name max length is 100.""" + long_name = "a" * 101 + data = {"name": long_name} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_create_with_optional_fields(self): + """Verify creating product with optional fields.""" + data = { + "name": "Test Product", + "description": "A test product", + "brand": "TestBrand", + "model": "Model X", + } + product = ProductCreateBaseProduct(**data) + + assert product.description == "A test product" + assert product.brand == "TestBrand" + + def test_description_max_length(self): + """Verify description max length is 500.""" + long_desc = "a" * 501 + data = {"name": "Test", "description": long_desc} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_brand_max_length(self): + """Verify brand max length is 100.""" + long_brand = "a" * 101 + data = {"name": "Test", "brand": long_brand} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_model_max_length(self): + """Verify model max length is 100.""" + long_model = "a" * 101 + data = {"name": "Test", "model": long_model} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_dismantling_notes_max_length(self): + """Verify dismantling notes max length is 500.""" + long_notes = "a" * 501 + data = {"name": "Test", "dismantling_notes": long_notes} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_dismantling_time_start_validation(self): + """Verify dismantling_time_start must be in past.""" + future_time = datetime.now(UTC) + timedelta(days=1) + data = {"name": "Test", "dismantling_time_start": future_time} + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_dismantling_time_end_after_start(self): + """Verify dismantling_time_end must be after dismantling_time_start.""" + start_time = datetime.now(UTC) - timedelta(hours=2) + end_time = start_time - timedelta(hours=1) + data = { + "name": "Test", + "dismantling_time_start": start_time, + "dismantling_time_end": end_time, + } + with pytest.raises(ValidationError): + ProductCreateBaseProduct(**data) + + def test_name_with_special_characters(self): + """Verify product name accepts special characters.""" + data = {"name": "Test-Product_#1 (v2.0)"} + product = ProductCreateBaseProduct(**data) + + assert product.name == "Test-Product_#1 (v2.0)" + + def test_name_with_unicode(self): + """Verify product name accepts unicode characters.""" + data = {"name": "产品名称 Product 製品"} + product = ProductCreateBaseProduct(**data) + + assert "产品" in product.name + + def test_create_with_physical_properties(self): + """Verify creating product with physical properties.""" + data = { + "name": "Product with Props", + "physical_properties": { + "weight_g": 5000.0, + "height_cm": 100.0, + }, + } + product = ProductCreateBaseProduct(**data) + + assert product.physical_properties is not None + assert product.physical_properties.weight_g == 5000.0 + + def test_create_with_circularity_properties(self): + """Verify creating product with circularity properties.""" + data = { + "name": "Product", + "circularity_properties": { + "recyclability_observation": "Highly recyclable", + }, + } + product = ProductCreateBaseProduct(**data) + + assert product.circularity_properties is not None + assert "recyclable" in product.circularity_properties.recyclability_observation.lower() + + def test_create_with_product_type(self): + """Verify creating product with product_type_id.""" + data = { + "name": "Product", + "product_type_id": 123, + } + product = ProductCreateBaseProduct(**data) + + assert product.product_type_id == 123 + + def test_videos_default_to_empty_list(self): + """Verify videos default to empty list.""" + data = {"name": "Product"} + product = ProductCreateBaseProduct(**data) + + assert product.videos == [] + + def test_bill_of_materials_default_to_empty_list(self): + """Verify bill_of_materials default to empty list.""" + data = {"name": "Product"} + product = ProductCreateBaseProduct(**data) + + assert product.bill_of_materials == [] + + +@pytest.mark.unit +class TestValidDatetimeType: + """Tests for ValidDateTime custom type.""" + + def test_valid_recent_past_datetime(self): + """Verify ValidDateTime accepts recent past datetime.""" + dt = datetime.now(UTC) - timedelta(days=30) + from pydantic import BaseModel + + class TestModel(BaseModel): + event_time: ValidDateTime + + model = TestModel(event_time=dt) + assert model.event_time == dt + + def test_valid_datetime_rejects_future(self): + """Verify ValidDateTime rejects future datetime.""" + dt = datetime.now(UTC) + timedelta(hours=1) + from pydantic import BaseModel + + class TestModel(BaseModel): + event_time: ValidDateTime + + with pytest.raises(ValidationError): + TestModel(event_time=dt) + + def test_valid_datetime_requires_timezone(self): + """Verify ValidDateTime requires timezone-aware datetime.""" + dt = datetime.now() # Naive datetime + from pydantic import BaseModel + + class TestModel(BaseModel): + event_time: ValidDateTime + + with pytest.raises(ValidationError): + TestModel(event_time=dt) + + def test_valid_datetime_rejects_too_old(self): + """Verify ValidDateTime rejects datetime older than 365 days.""" + dt = datetime.now(UTC) - timedelta(days=400) + from pydantic import BaseModel + + class TestModel(BaseModel): + event_time: ValidDateTime + + with pytest.raises(ValidationError): + TestModel(event_time=dt) + + +@pytest.mark.unit +class TestSchemaEdgeCases: + """Tests for schema edge cases and boundary conditions.""" + + def test_zero_weight_rejected(self): + """Verify zero weight is rejected.""" + data = {"weight_g": 0.0} + with pytest.raises(ValidationError): + PhysicalPropertiesCreate(**data) + + def test_negative_dimensions_rejected(self): + """Verify negative dimensions are rejected.""" + for field in ["height_cm", "width_cm", "depth_cm"]: + data = {field: -10.0} + with pytest.raises(ValidationError): + PhysicalPropertiesCreate(**data) + + def test_large_weight_values(self): + """Verify large weight values are accepted.""" + data = {"weight_g": 1000000.0} # 1 mega-gram + props = PhysicalPropertiesCreate(**data) + assert props.weight_g == 1000000.0 + + def test_large_dimension_values(self): + """Verify large dimension values are accepted.""" + data = { + "height_cm": 100000.0, + "width_cm": 50000.0, + "depth_cm": 25000.0, + } + props = PhysicalPropertiesCreate(**data) + assert props.height_cm == 100000.0 + + def test_mixed_optional_required_fields(self): + """Verify mixing optional and required fields.""" + data = { + "name": "Product", + "description": None, + "brand": "BrandName", + "model": None, + } + product = ProductCreateBaseProduct(**data) + + assert product.brand == "BrandName" + assert product.model is None diff --git a/backend/tests/unit/test_ownership_validation.py b/backend/tests/unit/test_ownership_validation.py new file mode 100644 index 0000000..408e836 --- /dev/null +++ b/backend/tests/unit/test_ownership_validation.py @@ -0,0 +1,530 @@ +"""Tests for user ownership validation utilities. + +Tests validate that get_user_owned_object correctly enforces user ownership +and raises appropriate exceptions when access is denied. +""" + +from unittest.mock import AsyncMock, MagicMock, patch +from uuid import uuid4 + +import pytest +from pydantic import UUID4 +from sqlmodel.ext.asyncio.session import AsyncSession + +from app.api.auth.exceptions import UserOwnershipError +from app.api.auth.models import User +from app.api.common.crud.exceptions import DependentModelOwnershipError +from app.api.common.utils.ownership import get_user_owned_object + + +@pytest.mark.unit +class TestGetUserOwnedObjectSuccess: + """Tests for successful get_user_owned_object calls.""" + + @pytest.mark.asyncio + async def test_returns_object_when_user_owns_it(self, mocker): + """Verify function returns object when user owns it.""" + user_id = uuid4() + model_id = uuid4() + expected_object = MagicMock() + + # Mock the get_nested_model_by_id to return the object + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=expected_object, + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + result = await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + assert result == expected_object + mock_get_nested.assert_called_once() + + @pytest.mark.asyncio + async def test_passes_correct_parameters_to_get_nested_model(self, mocker): + """Verify correct parameters are passed to get_nested_model_by_id.""" + user_id = uuid4() + model_id = uuid4() + expected_object = MagicMock() + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=expected_object, + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + # Verify call with default user_fk="owner_id" + call_args = mock_get_nested.call_args + assert call_args.kwargs["parent_model"] == User + assert call_args.kwargs["parent_id"] == user_id + assert call_args.kwargs["dependent_model"] == mock_model + assert call_args.kwargs["dependent_id"] == model_id + assert call_args.kwargs["parent_fk_name"] == "owner_id" + + @pytest.mark.asyncio + async def test_uses_custom_user_fk_parameter(self, mocker): + """Verify custom user_fk parameter is passed through.""" + user_id = uuid4() + model_id = uuid4() + custom_fk = "custom_owner_field" + expected_object = MagicMock() + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=expected_object, + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + user_fk=custom_fk, + ) + + call_args = mock_get_nested.call_args + assert call_args.kwargs["parent_fk_name"] == custom_fk + + +@pytest.mark.unit +class TestGetUserOwnedObjectFailure: + """Tests for get_user_owned_object error handling.""" + + @pytest.mark.asyncio + async def test_raises_user_ownership_error_on_dependent_model_error(self, mocker): + """Verify UserOwnershipError is raised when DependentModelOwnershipError occurs.""" + user_id = uuid4() + model_id = uuid4() + + # Mock get_nested_model_by_id to raise DependentModelOwnershipError + mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + side_effect=DependentModelOwnershipError( + dependent_model=MagicMock(), + dependent_id=model_id, + parent_model=User, + parent_id=user_id, + ), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + mock_model.get_api_model_name.return_value.name_capital = "TestModel" + + with pytest.raises(UserOwnershipError) as exc_info: + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + error = exc_info.value + assert error.http_status_code == 403 + assert str(user_id) in error.message + assert str(model_id) in error.message + + @pytest.mark.asyncio + async def test_error_message_contains_model_name(self, mocker): + """Verify error message includes the model name.""" + user_id = uuid4() + model_id = uuid4() + + mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + side_effect=DependentModelOwnershipError( + dependent_model=MagicMock(), + dependent_id=model_id, + parent_model=User, + parent_id=user_id, + ), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + model_name = "DataCollection" + mock_model.get_api_model_name.return_value.name_capital = model_name + + with pytest.raises(UserOwnershipError) as exc_info: + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + assert model_name in exc_info.value.message + + @pytest.mark.asyncio + async def test_error_contains_forbidden_status_code(self, mocker): + """Verify UserOwnershipError has 403 Forbidden status code.""" + user_id = uuid4() + model_id = uuid4() + + mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + side_effect=DependentModelOwnershipError( + dependent_model=MagicMock(), + dependent_id=model_id, + parent_model=User, + parent_id=user_id, + ), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + mock_model.get_api_model_name.return_value.name_capital = "Model" + + with pytest.raises(UserOwnershipError) as exc_info: + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + assert exc_info.value.http_status_code == 403 + + +@pytest.mark.unit +class TestGetUserOwnedObjectParameterVariations: + """Tests for various parameter combinations.""" + + @pytest.mark.asyncio + async def test_with_uuid4_ids(self, mocker): + """Verify function works with various UUID4 IDs.""" + uuid_ids = [uuid4() for _ in range(3)] + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=MagicMock(), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + for user_id in uuid_ids: + for model_id in uuid_ids: + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + assert mock_get_nested.call_count == len(uuid_ids) ** 2 + + @pytest.mark.asyncio + async def test_with_integer_model_id(self, mocker): + """Verify function works with integer model IDs.""" + user_id = uuid4() + model_id = 12345 + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=MagicMock(), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + call_args = mock_get_nested.call_args + assert call_args.kwargs["dependent_id"] == model_id + + @pytest.mark.asyncio + async def test_with_string_user_fk(self, mocker): + """Verify function works with different string user_fk values.""" + user_id = uuid4() + model_id = uuid4() + fk_values = ["owner_id", "created_by_id", "responsible_user_id", "author_id"] + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=MagicMock(), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + for fk_name in fk_values: + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + user_fk=fk_name, + ) + + assert mock_get_nested.call_count == len(fk_values) + + # Verify each call used different user_fk + for i, fk_name in enumerate(fk_values): + call_args = mock_get_nested.call_args_list[i] + assert call_args.kwargs["parent_fk_name"] == fk_name + + +@pytest.mark.unit +class TestGetUserOwnedObjectIntegration: + """Tests for integration aspects of ownership validation.""" + + @pytest.mark.asyncio + async def test_chain_of_responsibility_flow(self, mocker): + """Verify correct flow: valid object -> returned, invalid -> UserOwnershipError.""" + user_id = uuid4() + model_id = uuid4() + expected_object = MagicMock() + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + mock_model.get_api_model_name.return_value.name_capital = "TestResource" + + # First call: valid ownership + mock_get_nested.return_value = expected_object + result = await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + assert result == expected_object + + # Second call: invalid ownership + mock_get_nested.side_effect = DependentModelOwnershipError( + dependent_model=mock_model, + dependent_id=model_id, + parent_model=User, + parent_id=user_id, + ) + + with pytest.raises(UserOwnershipError): + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + @pytest.mark.asyncio + async def test_preserves_exception_chain(self, mocker): + """Verify exception chain suppression with 'from None'.""" + user_id = uuid4() + model_id = uuid4() + + original_error = DependentModelOwnershipError( + dependent_model=MagicMock(), + dependent_id=model_id, + parent_model=User, + parent_id=user_id, + ) + + mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + side_effect=original_error, + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + mock_model.get_api_model_name.return_value.name_capital = "ModelName" + + with pytest.raises(UserOwnershipError) as exc_info: + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + # The exception should have __cause__ set to None (from None) + assert exc_info.value.__cause__ is None + assert exc_info.value.__context__ is original_error + + @pytest.mark.asyncio + async def test_async_context_is_maintained(self, mocker): + """Verify async execution context is maintained.""" + user_id = uuid4() + model_id = uuid4() + + async_call_counter = AsyncMock(return_value=None) + + async def mock_get_nested(*args, **kwargs): + await async_call_counter() + return MagicMock() + + mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + side_effect=mock_get_nested, + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + async_call_counter.assert_called_once() + + @pytest.mark.asyncio + async def test_database_session_not_modified(self, mocker): + """Verify database session is passed through without modification.""" + user_id = uuid4() + model_id = uuid4() + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=MagicMock(), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + # Verify the exact same db instance was passed + call_args = mock_get_nested.call_args + assert call_args.kwargs["db"] is db + + +@pytest.mark.unit +class TestGetUserOwnedObjectEdgeCases: + """Tests for edge cases and boundary conditions.""" + + @pytest.mark.asyncio + async def test_with_many_consecutive_calls(self, mocker): + """Verify function handles many consecutive calls correctly.""" + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=MagicMock(), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + + for _ in range(100): + user_id = uuid4() + model_id = uuid4() + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + assert mock_get_nested.call_count == 100 + + @pytest.mark.asyncio + async def test_error_on_first_call(self, mocker): + """Verify error handling on first call.""" + user_id = uuid4() + model_id = uuid4() + + mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + side_effect=DependentModelOwnershipError( + dependent_model=MagicMock(), + dependent_id=model_id, + parent_model=User, + parent_id=user_id, + ), + ) + + db = AsyncMock(spec=AsyncSession) + mock_model = MagicMock() + mock_model.get_api_model_name.return_value.name_capital = "Model" + + with pytest.raises(UserOwnershipError): + await get_user_owned_object( + db=db, + model=mock_model, + model_id=model_id, + owner_id=user_id, + ) + + @pytest.mark.asyncio + async def test_same_user_and_model_ids_different_models(self, mocker): + """Verify function works correctly with multiple different model types.""" + user_id = uuid4() + model_id = uuid4() + + mock_get_nested = mocker.patch( + "app.api.common.utils.ownership.get_nested_model_by_id", + new_callable=AsyncMock, + return_value=MagicMock(), + ) + + db = AsyncMock(spec=AsyncSession) + + # Different model types + model_types = [ + MagicMock(name="ModelA"), + MagicMock(name="ModelB"), + MagicMock(name="ModelC"), + ] + + for model_type in model_types: + await get_user_owned_object( + db=db, + model=model_type, + model_id=model_id, + owner_id=user_id, + ) + + # Verify all calls were made with different models but same IDs + assert mock_get_nested.call_count == 3 + for i, call in enumerate(mock_get_nested.call_args_list): + assert call.kwargs["dependent_model"] == model_types[i] + assert call.kwargs["dependent_id"] == model_id + assert call.kwargs["parent_id"] == user_id diff --git a/backend/tests/unit/test_validation_patterns.py b/backend/tests/unit/test_validation_patterns.py new file mode 100644 index 0000000..cda4f54 --- /dev/null +++ b/backend/tests/unit/test_validation_patterns.py @@ -0,0 +1,385 @@ +"""Unit tests for schema validation patterns across the application. + +Tests comprehensive validation patterns for schemas using Pydantic. +Demonstrates how to test constraints, validators, and error cases. +""" + +from datetime import date +from decimal import Decimal + +import pytest +from pydantic import BaseModel, EmailStr, Field, ValidationError, field_validator + + +@pytest.mark.unit +class TestFieldValidators: + """Test various field validator patterns.""" + + def test_custom_field_validator_email(self): + """Test email validation.""" + + class ContactSchema(BaseModel): + email: str + + @field_validator("email") + @classmethod + def validate_email(cls, v: str) -> str: + if "@" not in v: + raise ValueError("Invalid email format") + return v.lower() + + # Valid email + schema = ContactSchema(email="Test@Example.COM") + assert schema.email == "test@example.com" + + # Invalid email + with pytest.raises(ValidationError) as exc_info: + ContactSchema(email="invalid") + errors = exc_info.value.errors() + assert any(e["loc"][0] == "email" for e in errors) + + def test_field_validator_with_dependencies(self): + """Test validator that depends on multiple fields.""" + + class DateRangeSchema(BaseModel): + start_date: date + end_date: date + + @field_validator("end_date") + @classmethod + def validate_date_range(cls, v: date, info) -> date: + start = info.data.get("start_date") + if start and v < start: + raise ValueError("end_date must be after start_date") + return v + + # Valid range + schema = DateRangeSchema( + start_date=date(2024, 1, 1), + end_date=date(2024, 12, 31), + ) + assert schema.start_date < schema.end_date + + # Invalid range + with pytest.raises(ValidationError) as exc_info: + DateRangeSchema( + start_date=date(2024, 12, 31), + end_date=date(2024, 1, 1), + ) + errors = exc_info.value.errors() + assert any(e["loc"][0] == "end_date" for e in errors) + + def test_field_validator_uppercase_conversion(self): + """Test validator that transforms data.""" + + class CodeSchema(BaseModel): + code: str = Field(min_length=3, max_length=10) + + @field_validator("code") + @classmethod + def normalize_code(cls, v: str) -> str: + return v.upper().strip() + + schema = CodeSchema(code=" abc ") + assert schema.code == "ABC" + assert len(schema.code) == 3 + + def test_multiple_validators_on_field(self): + """Test multiple validators on single field.""" + + class PercentageSchema(BaseModel): + percentage: float = Field(ge=0, le=100) + + @field_validator("percentage") + @classmethod + def round_percentage(cls, v: float) -> float: + return round(v, 2) + + # Valid with rounding + schema = PercentageSchema(percentage=75.123) + assert schema.percentage == 75.12 + + # Out of range + with pytest.raises(ValidationError): + PercentageSchema(percentage=150) + + +@pytest.mark.unit +class TestComplexFieldTypes: + """Test validation of complex field types.""" + + def test_decimal_field_validation(self): + """Test Decimal field validation.""" + + class PriceSchema(BaseModel): + price: Decimal = Field(decimal_places=2, max_digits=10) + + # Valid price + schema = PriceSchema(price="19.99") + assert isinstance(schema.price, Decimal) + assert schema.price == Decimal("19.99") + + # Too many decimal places + with pytest.raises(ValidationError): + PriceSchema(price="19.999") + + def test_list_field_validation(self): + """Test list field validation with constraints.""" + + class TagsSchema(BaseModel): + tags: list[str] = Field(min_length=1, max_length=5) + + # Valid list + schema = TagsSchema(tags=["python", "testing"]) + assert len(schema.tags) == 2 + + # Empty list + with pytest.raises(ValidationError): + TagsSchema(tags=[]) + + # Too many items + with pytest.raises(ValidationError): + TagsSchema(tags=["a", "b", "c", "d", "e", "f"]) + + def test_optional_field_validation(self): + """Test optional fields with None validation.""" + + class OptionalSchema(BaseModel): + required_field: str + optional_field: str | None = None + optional_with_default: int | None = 42 + + # With optional fields + schema = OptionalSchema(required_field="test") + assert schema.optional_field is None + assert schema.optional_with_default == 42 + + # With optional fields provided + schema2 = OptionalSchema( + required_field="test", + optional_field="provided", + optional_with_default=100, + ) + assert schema2.optional_field == "provided" + assert schema2.optional_with_default == 100 + + def test_nested_model_validation(self): + """Test validation of nested Pydantic models.""" + + class AddressSchema(BaseModel): + street: str + city: str + country: str = "US" + + class PersonSchema(BaseModel): + name: str + address: AddressSchema + + # Valid nested + schema = PersonSchema( + name="John", + address={"street": "123 Main St", "city": "Boston"}, + ) + assert schema.address.city == "Boston" + assert schema.address.country == "US" + + # Invalid nested + with pytest.raises(ValidationError) as exc_info: + PersonSchema( + name="John", + address={"street": "123 Main St"}, # Missing city + ) + errors = exc_info.value.errors() + assert any("address" in str(e["loc"]) for e in errors) + + def test_list_of_nested_models(self): + """Test validation of lists of nested models.""" + + class ItemSchema(BaseModel): + id: int + name: str + + class OrderSchema(BaseModel): + items: list[ItemSchema] + + # Valid list of nested models + schema = OrderSchema( + items=[ + {"id": 1, "name": "Item 1"}, + {"id": 2, "name": "Item 2"}, + ] + ) + assert len(schema.items) == 2 + assert schema.items[0].name == "Item 1" + + # Invalid nested item + with pytest.raises(ValidationError): + OrderSchema( + items=[ + {"id": 1, "name": "Item 1"}, + {"id": 2}, # Missing name + ] + ) + + +@pytest.mark.unit +class TestErrorHandling: + """Test error handling and validation error details.""" + + def test_validation_error_contains_field_info(self): + """Test that ValidationError contains field information.""" + + class StrictSchema(BaseModel): + email: EmailStr + age: int = Field(ge=0, le=150) + + with pytest.raises(ValidationError) as exc_info: + StrictSchema(email="invalid", age=200) + + errors = exc_info.value.errors() + assert len(errors) == 2 + + # Check that field names are in errors + error_fields = {e["loc"][0] for e in errors} + assert "email" in error_fields + assert "age" in error_fields + + def test_validation_error_messages(self): + """Test that error messages are helpful.""" + + class MessageSchema(BaseModel): + text: str = Field(min_length=5, max_length=100) + + with pytest.raises(ValidationError) as exc_info: + MessageSchema(text="hi") + + errors = exc_info.value.errors() + error_messages = [e["msg"] for e in errors] + # Should contain length constraint info + assert any("String should have at least 5 characters" in str(msg) for msg in error_messages) + + def test_multiple_validation_errors_collected(self): + """Test that all validation errors are collected, not just first.""" + + class MultiSchema(BaseModel): + name: str = Field(min_length=1) + age: int = Field(ge=0, le=150) + email: EmailStr + + # Multiple errors should all be reported + with pytest.raises(ValidationError) as exc_info: + MultiSchema(name="", age=999, email="invalid") + + errors = exc_info.value.errors() + error_fields = {e["loc"][0] for e in errors} + assert "name" in error_fields + assert "age" in error_fields + assert "email" in error_fields + + +@pytest.mark.unit +class TestEnumValidation: + """Test validation of enum fields.""" + + def test_enum_string_validation(self): + """Test string enum validation.""" + from enum import Enum + + class StatusEnum(str, Enum): + ACTIVE = "active" + INACTIVE = "inactive" + PENDING = "pending" + + class StatusSchema(BaseModel): + status: StatusEnum + + # Valid enum value + schema = StatusSchema(status="active") + assert schema.status == StatusEnum.ACTIVE + + # Invalid enum value + with pytest.raises(ValidationError): + StatusSchema(status="invalid") + + def test_enum_int_validation(self): + """Test integer enum validation.""" + from enum import Enum + + class LevelEnum(int, Enum): + LOW = 1 + MEDIUM = 2 + HIGH = 3 + + class LevelSchema(BaseModel): + level: LevelEnum + + # Valid enum value + schema = LevelSchema(level=2) + assert schema.level == LevelEnum.MEDIUM + + # Invalid enum value + with pytest.raises(ValidationError): + LevelSchema(level=99) + + +@pytest.mark.unit +class TestConditionalValidation: + """Test conditional validation logic.""" + + def test_required_if_another_field_present(self): + """Test field is required only if another field is present.""" + + class ConditionalSchema(BaseModel): + has_discount: bool = False + discount_code: str | None = None + + @field_validator("discount_code") + @classmethod + def validate_discount_code(cls, v: str | None, info) -> str | None: + has_discount = info.data.get("has_discount") + if has_discount and not v: + raise ValueError("discount_code required when has_discount is True") + return v + + # Valid: no discount, no code needed + schema = ConditionalSchema(has_discount=False) + assert schema.discount_code is None + + # Valid: has discount with code + schema2 = ConditionalSchema(has_discount=True, discount_code="SAVE10") + assert schema2.discount_code == "SAVE10" + + # Invalid: has discount but no code + with pytest.raises(ValidationError): + ConditionalSchema(has_discount=True, discount_code=None) + + def test_mutually_exclusive_fields(self): + """Test mutually exclusive fields validation.""" + + class MutualSchema(BaseModel): + payment_method: str + credit_card: str | None = None + bank_account: str | None = None + + @field_validator("bank_account") + @classmethod + def validate_mutually_exclusive(cls, v: str | None, info) -> str | None: + credit_card = info.data.get("credit_card") + if v and credit_card: + raise ValueError("Cannot specify both credit_card and bank_account") + return v + + # Valid: only credit card + schema = MutualSchema( + payment_method="card", + credit_card="1234", + ) + assert schema.credit_card == "1234" + + # Invalid: both specified + with pytest.raises(ValidationError): + MutualSchema( + payment_method="mixed", + credit_card="1234", + bank_account="5678", + ) diff --git a/backend/uv.lock b/backend/uv.lock index 5d16955..ad8a0cd 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -2,8 +2,12 @@ version = 1 revision = 3 requires-python = ">=3.13" resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version < '3.14'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] [[package]] @@ -17,16 +21,16 @@ wheels = [ [[package]] name = "alembic" -version = "1.17.0" +version = "1.18.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mako" }, { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/45/6f4555f2039f364c3ce31399529dcf48dd60726ff3715ad67f547d87dfd2/alembic-1.17.0.tar.gz", hash = "sha256:4652a0b3e19616b57d652b82bfa5e38bf5dbea0813eed971612671cb9e90c0fe", size = 1975526, upload-time = "2025-10-11T18:40:13.585Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/1f/38e29b06bfed7818ebba1f84904afdc8153ef7b6c7e0d8f3bc6643f5989c/alembic-1.17.0-py3-none-any.whl", hash = "sha256:80523bc437d41b35c5db7e525ad9d908f79de65c27d6a5a5eab6df348a352d99", size = 247449, upload-time = "2025-10-11T18:40:16.288Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, ] [[package]] @@ -44,15 +48,24 @@ wheels = [ [[package]] name = "alembic-postgresql-enum" -version = "1.8.0" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/04/e465cb5c051fb056b7fadda7667b3e1fb4d32d7f19533e3bbff071c73788/alembic_postgresql_enum-1.8.0.tar.gz", hash = "sha256:132cd5fdc4a2a0b6498f3d89ea1c7b2a5ddc3281ddd84edae7259ec4c0a215a0", size = 15858, upload-time = "2025-07-20T12:25:50.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/69/1c9b6dbcb99d2eb1b59807fb6e14717d9686bfc567b2d2740cb1d1be055f/alembic_postgresql_enum-1.9.0.tar.gz", hash = "sha256:5ce76d0fca97e7e11b56ca416aa367aaaf308ff90f3ed5d38a6f160357bebecd", size = 18444, upload-time = "2026-02-04T18:25:55.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/d5/3a15fadb3468082a93c1752ad45a47a739d4fc482472d03d9ad73dc11937/alembic_postgresql_enum-1.9.0-py3-none-any.whl", hash = "sha256:308666d6f1b154ec4dd10b8599e606711bc7eff050f7fce2e77e435734da5d76", size = 27674, upload-time = "2026-02-04T18:25:53.915Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/80/4e6e841f9a0403b520b8f28650c2cdf5905e25bd4ff403b43daec580fed3/alembic_postgresql_enum-1.8.0-py3-none-any.whl", hash = "sha256:0e62833f8d1aca2c58fa09cae1d4a52472fb32d2dde32b68c84515fffcf401d5", size = 23697, upload-time = "2025-07-20T12:25:49.048Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, ] [[package]] @@ -66,27 +79,26 @@ wheels = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, - { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] [[package]] name = "argon2-cffi" -version = "23.1.0" +version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798, upload-time = "2023-08-15T14:13:12.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124, upload-time = "2023-08-15T14:13:10.752Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, ] [[package]] @@ -134,96 +146,150 @@ wheels = [ [[package]] name = "asyncpg" -version = "0.30.0" +version = "0.31.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" }, - { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" }, - { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" }, + { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" }, + { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" }, + { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" }, + { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" }, + { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" }, + { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" }, + { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" }, + { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" }, + { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" }, + { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" }, ] [[package]] name = "bcrypt" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, - { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, - { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, - { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, - { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, - { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, - { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, - { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, - { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, - { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, - { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, - { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, - { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, - { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, - { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" }, + { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" }, + { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" }, + { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" }, + { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, + { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, + { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, + { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, + { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[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 = "boto3" -version = "1.40.49" +version = "1.42.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/5b/165dbfc6de77774b0dac5582ac8a7aa92652d61215871ff4c88854864fb0/boto3-1.40.49.tar.gz", hash = "sha256:ea37d133548fbae543092ada61aeb08bced8f9aecd2e96e803dc8237459a80a0", size = 111572, upload-time = "2025-10-09T19:21:49.295Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/fe/3363024b6dda5968401f45d8b345ed95ce4fd536d58f799988b4b28184ad/boto3-1.42.47.tar.gz", hash = "sha256:74812a2e29de7c2bd19e446d765cb887394f20f1517388484b51891a410f33b2", size = 112884, upload-time = "2026-02-11T20:49:49.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/07/9b622ec8691911e3420c9872a50a9d333d4880d217e9eb25b327193099dc/boto3-1.40.49-py3-none-any.whl", hash = "sha256:64eb7af5f66998b34ad629786ff4a7f81d74c2d4ef9e42f69d99499dbee46d07", size = 139345, upload-time = "2025-10-09T19:21:46.886Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/884e30adab2339ce5cce7b800f5fa619254d36e89e50a8cf39a5524edc35/boto3-1.42.47-py3-none-any.whl", hash = "sha256:ed881ed246027028af566acbb80f008aa619be4d3fdbcc4ad3c75dbe8c34bfaf", size = 140608, upload-time = "2026-02-11T20:49:47.664Z" }, ] [[package]] name = "botocore" -version = "1.40.49" +version = "1.42.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/6a/eb7503536552bbd3388b2607bc7a64e59d4f988336406b51a69d29f17ed2/botocore-1.40.49.tar.gz", hash = "sha256:fe8d4cbcc22de84c20190ae728c46b931bafeb40fce247010fb071c31b6532b5", size = 14415240, upload-time = "2025-10-09T19:21:37.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/a6/d15f5dfe990abd76dbdb2105a7697e0d948e04c41dfd97c058bc76c7cebd/botocore-1.42.47.tar.gz", hash = "sha256:c26e190c1b4d863ba7b44dc68cc574d8eb862ddae5f0fe3472801daee12a0378", size = 14952255, upload-time = "2026-02-11T20:49:40.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/7b/dce396a3f7078e0432d40a9778602cbf0785ca91e7bcb64e05f19dfb5662/botocore-1.40.49-py3-none-any.whl", hash = "sha256:bf1089d0e77e4fc2e195d81c519b194ab62a4d4dd3e7113ee4e2bf903b0b75ab", size = 14085172, upload-time = "2025-10-09T19:21:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/54/5e/50e3a59b243894088eeb949a654fb21d9ab7d0d703034470de016828d85a/botocore-1.42.47-py3-none-any.whl", hash = "sha256:c60f5feaf189423e17755aca3f1d672b7466620dd2032440b32aaac64ae8cac8", size = 14625351, upload-time = "2026-02-11T20:49:36.143Z" }, ] [[package]] @@ -237,11 +303,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.10.5" +version = "2026.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] [[package]] @@ -332,14 +398,14 @@ wheels = [ [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -365,119 +431,124 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, - { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, - { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, - { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, - { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, - { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, - { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, - { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, - { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, - { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, - { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, - { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, - { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, - { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, - { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, - { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, - { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, - { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, - { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, - { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, - { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, - { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, - { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, - { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, - { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] name = "cryptography" -version = "46.0.2" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4a/9b/e301418629f7bfdf72db9e80ad6ed9d1b83c487c471803eaa6464c511a01/cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe", size = 749293, upload-time = "2025-10-01T00:29:11.856Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/98/7a8df8c19a335c8028414738490fc3955c0cecbfdd37fcc1b9c3d04bd561/cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663", size = 7261255, upload-time = "2025-10-01T00:27:22.947Z" }, - { url = "https://files.pythonhosted.org/packages/c6/38/b2adb2aa1baa6706adc3eb746691edd6f90a656a9a65c3509e274d15a2b8/cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02", size = 4297596, upload-time = "2025-10-01T00:27:25.258Z" }, - { url = "https://files.pythonhosted.org/packages/e4/27/0f190ada240003119488ae66c897b5e97149292988f556aef4a6a2a57595/cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135", size = 4450899, upload-time = "2025-10-01T00:27:27.458Z" }, - { url = "https://files.pythonhosted.org/packages/85/d5/e4744105ab02fdf6bb58ba9a816e23b7a633255987310b4187d6745533db/cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92", size = 4300382, upload-time = "2025-10-01T00:27:29.091Z" }, - { url = "https://files.pythonhosted.org/packages/33/fb/bf9571065c18c04818cb07de90c43fc042c7977c68e5de6876049559c72f/cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659", size = 4017347, upload-time = "2025-10-01T00:27:30.767Z" }, - { url = "https://files.pythonhosted.org/packages/35/72/fc51856b9b16155ca071080e1a3ad0c3a8e86616daf7eb018d9565b99baa/cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa", size = 4983500, upload-time = "2025-10-01T00:27:32.741Z" }, - { url = "https://files.pythonhosted.org/packages/c1/53/0f51e926799025e31746d454ab2e36f8c3f0d41592bc65cb9840368d3275/cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08", size = 4482591, upload-time = "2025-10-01T00:27:34.869Z" }, - { url = "https://files.pythonhosted.org/packages/86/96/4302af40b23ab8aa360862251fb8fc450b2a06ff24bc5e261c2007f27014/cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5", size = 4300019, upload-time = "2025-10-01T00:27:37.029Z" }, - { url = "https://files.pythonhosted.org/packages/9b/59/0be12c7fcc4c5e34fe2b665a75bc20958473047a30d095a7657c218fa9e8/cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc", size = 4950006, upload-time = "2025-10-01T00:27:40.272Z" }, - { url = "https://files.pythonhosted.org/packages/55/1d/42fda47b0111834b49e31590ae14fd020594d5e4dadd639bce89ad790fba/cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b", size = 4482088, upload-time = "2025-10-01T00:27:42.668Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/60f583f69aa1602c2bdc7022dae86a0d2b837276182f8c1ec825feb9b874/cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1", size = 4425599, upload-time = "2025-10-01T00:27:44.616Z" }, - { url = "https://files.pythonhosted.org/packages/d1/57/d8d4134cd27e6e94cf44adb3f3489f935bde85f3a5508e1b5b43095b917d/cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b", size = 4697458, upload-time = "2025-10-01T00:27:46.209Z" }, - { url = "https://files.pythonhosted.org/packages/d1/2b/531e37408573e1da33adfb4c58875013ee8ac7d548d1548967d94a0ae5c4/cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee", size = 3056077, upload-time = "2025-10-01T00:27:48.424Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cd/2f83cafd47ed2dc5a3a9c783ff5d764e9e70d3a160e0df9a9dcd639414ce/cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb", size = 3512585, upload-time = "2025-10-01T00:27:50.521Z" }, - { url = "https://files.pythonhosted.org/packages/00/36/676f94e10bfaa5c5b86c469ff46d3e0663c5dc89542f7afbadac241a3ee4/cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470", size = 2927474, upload-time = "2025-10-01T00:27:52.91Z" }, - { url = "https://files.pythonhosted.org/packages/6f/cc/47fc6223a341f26d103cb6da2216805e08a37d3b52bee7f3b2aee8066f95/cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8", size = 7198626, upload-time = "2025-10-01T00:27:54.8Z" }, - { url = "https://files.pythonhosted.org/packages/93/22/d66a8591207c28bbe4ac7afa25c4656dc19dc0db29a219f9809205639ede/cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a", size = 4287584, upload-time = "2025-10-01T00:27:57.018Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/fac3ab6302b928e0398c269eddab5978e6c1c50b2b77bb5365ffa8633b37/cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b", size = 4433796, upload-time = "2025-10-01T00:27:58.631Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d8/24392e5d3c58e2d83f98fe5a2322ae343360ec5b5b93fe18bc52e47298f5/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20", size = 4292126, upload-time = "2025-10-01T00:28:00.643Z" }, - { url = "https://files.pythonhosted.org/packages/ed/38/3d9f9359b84c16c49a5a336ee8be8d322072a09fac17e737f3bb11f1ce64/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73", size = 3993056, upload-time = "2025-10-01T00:28:02.8Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a3/4c44fce0d49a4703cc94bfbe705adebf7ab36efe978053742957bc7ec324/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee", size = 4967604, upload-time = "2025-10-01T00:28:04.783Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/49d73218747c8cac16bb8318a5513fde3129e06a018af3bc4dc722aa4a98/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2", size = 4465367, upload-time = "2025-10-01T00:28:06.864Z" }, - { url = "https://files.pythonhosted.org/packages/1b/64/9afa7d2ee742f55ca6285a54386ed2778556a4ed8871571cb1c1bfd8db9e/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f", size = 4291678, upload-time = "2025-10-01T00:28:08.982Z" }, - { url = "https://files.pythonhosted.org/packages/50/48/1696d5ea9623a7b72ace87608f6899ca3c331709ac7ebf80740abb8ac673/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e", size = 4931366, upload-time = "2025-10-01T00:28:10.74Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/9dfc778401a334db3b24435ee0733dd005aefb74afe036e2d154547cb917/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36", size = 4464738, upload-time = "2025-10-01T00:28:12.491Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b1/abcde62072b8f3fd414e191a6238ce55a0050e9738090dc6cded24c12036/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a", size = 4419305, upload-time = "2025-10-01T00:28:14.145Z" }, - { url = "https://files.pythonhosted.org/packages/c7/1f/3d2228492f9391395ca34c677e8f2571fb5370fe13dc48c1014f8c509864/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c", size = 4681201, upload-time = "2025-10-01T00:28:15.951Z" }, - { url = "https://files.pythonhosted.org/packages/de/77/b687745804a93a55054f391528fcfc76c3d6bfd082ce9fb62c12f0d29fc1/cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1", size = 3022492, upload-time = "2025-10-01T00:28:17.643Z" }, - { url = "https://files.pythonhosted.org/packages/60/a5/8d498ef2996e583de0bef1dcc5e70186376f00883ae27bf2133f490adf21/cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9", size = 3496215, upload-time = "2025-10-01T00:28:19.272Z" }, - { url = "https://files.pythonhosted.org/packages/56/db/ee67aaef459a2706bc302b15889a1a8126ebe66877bab1487ae6ad00f33d/cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0", size = 2919255, upload-time = "2025-10-01T00:28:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/d5/bb/fa95abcf147a1b0bb94d95f53fbb09da77b24c776c5d87d36f3d94521d2c/cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685", size = 7248090, upload-time = "2025-10-01T00:28:22.846Z" }, - { url = "https://files.pythonhosted.org/packages/b7/66/f42071ce0e3ffbfa80a88feadb209c779fda92a23fbc1e14f74ebf72ef6b/cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b", size = 4293123, upload-time = "2025-10-01T00:28:25.072Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/1fdbd2e5c1ba822828d250e5a966622ef00185e476d1cd2726b6dd135e53/cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1", size = 4439524, upload-time = "2025-10-01T00:28:26.808Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c1/5e4989a7d102d4306053770d60f978c7b6b1ea2ff8c06e0265e305b23516/cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c", size = 4297264, upload-time = "2025-10-01T00:28:29.327Z" }, - { url = "https://files.pythonhosted.org/packages/28/78/b56f847d220cb1d6d6aef5a390e116ad603ce13a0945a3386a33abc80385/cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af", size = 4011872, upload-time = "2025-10-01T00:28:31.479Z" }, - { url = "https://files.pythonhosted.org/packages/e1/80/2971f214b066b888944f7b57761bf709ee3f2cf805619a18b18cab9b263c/cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b", size = 4978458, upload-time = "2025-10-01T00:28:33.267Z" }, - { url = "https://files.pythonhosted.org/packages/a5/84/0cb0a2beaa4f1cbe63ebec4e97cd7e0e9f835d0ba5ee143ed2523a1e0016/cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21", size = 4472195, upload-time = "2025-10-01T00:28:36.039Z" }, - { url = "https://files.pythonhosted.org/packages/30/8b/2b542ddbf78835c7cd67b6fa79e95560023481213a060b92352a61a10efe/cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6", size = 4296791, upload-time = "2025-10-01T00:28:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/78/12/9065b40201b4f4876e93b9b94d91feb18de9150d60bd842a16a21565007f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023", size = 4939629, upload-time = "2025-10-01T00:28:39.654Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9e/6507dc048c1b1530d372c483dfd34e7709fc542765015425f0442b08547f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e", size = 4471988, upload-time = "2025-10-01T00:28:41.822Z" }, - { url = "https://files.pythonhosted.org/packages/b1/86/d025584a5f7d5c5ec8d3633dbcdce83a0cd579f1141ceada7817a4c26934/cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90", size = 4422989, upload-time = "2025-10-01T00:28:43.608Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/536370418b38a15a61bbe413006b79dfc3d2b4b0eafceb5581983f973c15/cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be", size = 4685578, upload-time = "2025-10-01T00:28:45.361Z" }, - { url = "https://files.pythonhosted.org/packages/15/52/ea7e2b1910f547baed566c866fbb86de2402e501a89ecb4871ea7f169a81/cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c", size = 3036711, upload-time = "2025-10-01T00:28:47.096Z" }, - { url = "https://files.pythonhosted.org/packages/71/9e/171f40f9c70a873e73c2efcdbe91e1d4b1777a03398fa1c4af3c56a2477a/cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62", size = 3500007, upload-time = "2025-10-01T00:28:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/3e/7c/15ad426257615f9be8caf7f97990cf3dcbb5b8dd7ed7e0db581a1c4759dd/cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1", size = 2918153, upload-time = "2025-10-01T00:28:51.003Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, ] [[package]] @@ -489,17 +560,35 @@ 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 = "docopt-ng" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/50/8d6806cf13138127692ae6ff79ddeb4e25eb3b0bcc3c1bd033e7e04531a9/docopt_ng-0.9.0.tar.gz", hash = "sha256:91c6da10b5bb6f2e9e25345829fb8278c78af019f6fc40887ad49b060483b1d7", size = 32264, upload-time = "2023-05-30T20:46:25.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/4a/c3b77fc1a24510b08918b43a473410c0168f6e657118807015f1f1edceea/docopt_ng-0.9.0-py3-none-any.whl", hash = "sha256:bfe4c8b03f9fca424c24ee0b4ffa84bf7391cb18c29ce0f6a8227a3b01b81ff9", size = 16689, upload-time = "2023-05-30T20:46:45.294Z" }, +] + +[[package]] +name = "dotmap" +version = "1.3.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/68/c186606e4f2bf731abd18044ea201e70c3c244bf468f41368820d197fca5/dotmap-1.3.30.tar.gz", hash = "sha256:5821a7933f075fb47563417c0e92e0b7c031158b4c9a6a7e56163479b658b368", size = 12391, upload-time = "2022-04-06T16:26:49.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/f9/976d6813c160d6c89196d81e9466dca1503d20e609d8751f3536daf37ec6/dotmap-1.3.30-py3-none-any.whl", hash = "sha256:bd9fa15286ea2ad899a4d1dc2445ed85a1ae884a42effb87c89a6ecce71243c6", size = 11464, upload-time = "2022-04-06T16:26:47.103Z" }, +] + [[package]] name = "email-validator" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, ] [[package]] @@ -525,28 +614,30 @@ wheels = [ [[package]] name = "faker" -version = "37.11.0" +version = "40.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "tzdata" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/4b/ca43f6bbcef63deb8ac01201af306388670a172587169aab3b192f7490f0/faker-37.11.0.tar.gz", hash = "sha256:22969803849ba0618be8eee2dd01d0d9e2cd3b75e6ff1a291fa9abcdb34da5e6", size = 1935301, upload-time = "2025-10-07T14:49:01.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/7e/dccb7013c9f3d66f2e379383600629fec75e4da2698548bdbf2041ea4b51/faker-40.4.0.tar.gz", hash = "sha256:76f8e74a3df28c3e2ec2caafa956e19e37a132fdc7ea067bc41783affcfee364", size = 1952221, upload-time = "2026-02-06T23:30:15.515Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/46/8f4097b55e43af39e8e71e1f7aec59ff7398bca54d975c30889bc844719d/faker-37.11.0-py3-none-any.whl", hash = "sha256:1508d2da94dfd1e0087b36f386126d84f8583b3de19ac18e392a2831a6676c57", size = 1975525, upload-time = "2025-10-07T14:48:58.29Z" }, + { url = "https://files.pythonhosted.org/packages/ac/63/58efa67c10fb27810d34351b7a10f85f109a7f7e2a07dc3773952459c47b/faker-40.4.0-py3-none-any.whl", hash = "sha256:486d43c67ebbb136bc932406418744f9a0bdf2c07f77703ea78b58b77e9aa443", size = 1987060, upload-time = "2026-02-06T23:30:13.44Z" }, ] [[package]] name = "fastapi" -version = "0.118.2" +version = "0.128.8" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/ad/31a59efecca3b584440cafac6f69634f4661295c858912c2b2905280a089/fastapi-0.118.2.tar.gz", hash = "sha256:d5388dbe76d97cb6ccd2c93b4dd981608062ebf6335280edfa9a11af82443e18", size = 311963, upload-time = "2025-10-08T14:52:17.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/72/0df5c58c954742f31a7054e2dd1143bae0b408b7f36b59b85f928f9b456c/fastapi-0.128.8.tar.gz", hash = "sha256:3171f9f328c4a218f0a8d2ba8310ac3a55d1ee12c28c949650288aee25966007", size = 375523, upload-time = "2026-02-11T15:19:36.69Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/7c/97d033faf771c9fe960c7b51eb78ab266bfa64cbc917601978963f0c3c7b/fastapi-0.118.2-py3-none-any.whl", hash = "sha256:d1f842612e6a305f95abe784b7f8d3215477742e7c67a16fccd20bd79db68150", size = 97954, upload-time = "2025-10-08T14:52:16.166Z" }, + { url = "https://files.pythonhosted.org/packages/9f/37/37b07e276f8923c69a5df266bfcb5bac4ba8b55dfe4a126720f8c48681d1/fastapi-0.128.8-py3-none-any.whl", hash = "sha256:5618f492d0fe973a778f8fec97723f598aa9deee495040a8d51aaf3cf123ecf1", size = 103630, upload-time = "2026-02-11T15:19:35.209Z" }, ] [package.optional-dependencies] @@ -555,22 +646,24 @@ standard = [ { name = "fastapi-cli", extra = ["standard"] }, { name = "httpx" }, { name = "jinja2" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, { name = "python-multipart" }, { name = "uvicorn", extra = ["standard"] }, ] [[package]] name = "fastapi-cli" -version = "0.0.13" +version = "0.0.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich-toolkit" }, { name = "typer" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/4e/3f61850012473b097fc5297d681bd85788e186fadb8555b67baf4c7707f4/fastapi_cli-0.0.13.tar.gz", hash = "sha256:312addf3f57ba7139457cf0d345c03e2170cc5a034057488259c33cd7e494529", size = 17780, upload-time = "2025-09-20T16:37:31.089Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/5a/500ec4deaa9a5d6bc7909cbd7b252fa37fe80d418c55a65ce5ed11c53505/fastapi_cli-0.0.21.tar.gz", hash = "sha256:457134b8f3e08d2d203a18db923a18bbc1a01d9de36fbe1fa7905c4d02a0e5c0", size = 19664, upload-time = "2026-02-11T15:27:59.65Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/36/7432750f3638324b055496d2c952000bea824259fca70df5577a6a3c172f/fastapi_cli-0.0.13-py3-none-any.whl", hash = "sha256:219b73ccfde7622559cef1d43197da928516acb4f21f2ec69128c4b90057baba", size = 11142, upload-time = "2025-09-20T16:37:29.695Z" }, + { url = "https://files.pythonhosted.org/packages/de/cf/d1f3ea2a1661d80c62c7b1537184ec28ec832eefb7ad1ff3047813d19452/fastapi_cli-0.0.21-py3-none-any.whl", hash = "sha256:57c6e043694c68618eee04d00b4d93213c37f5a854b369d2871a77dfeff57e91", size = 12391, upload-time = "2026-02-11T15:27:58.181Z" }, ] [package.optional-dependencies] @@ -581,9 +674,10 @@ standard = [ [[package]] name = "fastapi-cloud-cli" -version = "0.3.1" +version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "fastar" }, { name = "httpx" }, { name = "pydantic", extra = ["email"] }, { name = "rich-toolkit" }, @@ -592,9 +686,9 @@ dependencies = [ { name = "typer" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/48/0f14d8555b750dc8c04382804e4214f1d7f55298127f3a0237ba566e69dd/fastapi_cloud_cli-0.3.1.tar.gz", hash = "sha256:8c7226c36e92e92d0c89827e8f56dbf164ab2de4444bd33aa26b6c3f7675db69", size = 24080, upload-time = "2025-10-09T11:32:58.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/15/6c3d85d63964340fde6f36cc80f3f365d35f371e6a918d68ff3a3d588ef2/fastapi_cloud_cli-0.11.0.tar.gz", hash = "sha256:ecc83a5db106be35af528eccb01aa9bced1d29783efd48c8c1c831cf111eea99", size = 36170, upload-time = "2026-01-15T09:51:33.681Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/79/7f5a5e5513e6a737e5fb089d9c59c74d4d24dc24d581d3aa519b326bedda/fastapi_cloud_cli-0.3.1-py3-none-any.whl", hash = "sha256:7d1a98a77791a9d0757886b2ffbf11bcc6b3be93210dd15064be10b216bf7e00", size = 19711, upload-time = "2025-10-09T11:32:57.118Z" }, + { url = "https://files.pythonhosted.org/packages/1a/07/60f79270a3320780be7e2ae8a1740cb98a692920b569ba420b97bcc6e175/fastapi_cloud_cli-0.11.0-py3-none-any.whl", hash = "sha256:76857b0f09d918acfcb50ade34682ba3b2079ca0c43fda10215de301f185a7f8", size = 26884, upload-time = "2026-01-15T09:51:34.471Z" }, ] [[package]] @@ -610,18 +704,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/88/afc022ad64d12f730141fc50758ecf9d60de5fed11335dc16e3127617f05/fastapi_filter-2.0.1-py3-none-any.whl", hash = "sha256:711d48707ec62f7c9e12a7713fc0f6a99858a9e3741b4d108102d5599e77197d", size = 11586, upload-time = "2024-12-07T17:30:05.375Z" }, ] +[[package]] +name = "fastapi-mail" +version = "1.2.6" +source = { git = "https://github.com/simonvanlierde/fastapi-mail?rev=6c6f04a7afaf3cdced82764009a2f1f2a3c3ee6c#6c6f04a7afaf3cdced82764009a2f1f2a3c3ee6c" } +dependencies = [ + { name = "aiosmtplib" }, + { name = "blinker" }, + { name = "cryptography" }, + { name = "email-validator" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "regex" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] + [[package]] name = "fastapi-pagination" -version = "0.14.3" +version = "0.15.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastapi" }, { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/df/b8a227a621713ed0133a737dee91066beb09e8769ff875225319da4a3a26/fastapi_pagination-0.14.3.tar.gz", hash = "sha256:be8e81e21235c0758cbdd2f0e597c65bcb82a85062e2b99a9474418d23006791", size = 568147, upload-time = "2025-10-08T10:58:01.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/36/4314836683bec1b33195bbaf2d74e1515cfcbb7e7ef5431ef515b864a5d0/fastapi_pagination-0.15.10.tar.gz", hash = "sha256:0ba7d4f795059a91a9e89358af129f2114876452c1defaf198ea8e3419e9a3cd", size = 575160, upload-time = "2026-02-08T13:13:40.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/6a/0b6804e1c20013855379fe58e02206e9cc7f7131653d8daad1af6be67851/fastapi_pagination-0.14.3-py3-none-any.whl", hash = "sha256:e87350b64010fd3b2df840218b1f65a21eec6078238cd3a1794c2468a03ea45f", size = 52559, upload-time = "2025-10-08T10:58:00.428Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/cce73569317fdba138c315b980c39c6a035baa0ea5867d12276f1d312cff/fastapi_pagination-0.15.10-py3-none-any.whl", hash = "sha256:d50071ebc93b519391f16ff6c3ba9e3603bd659963fe6774ba2f4d5037e17fd8", size = 60798, upload-time = "2026-02-08T13:13:41.972Z" }, ] [[package]] @@ -638,7 +749,7 @@ wheels = [ [[package]] name = "fastapi-users" -version = "14.0.1" +version = "15.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "email-validator" }, @@ -648,9 +759,9 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/26/7fe4e6a4f60d9cde2b95f58ba45ff03219b62bd03bea75d914b723ecfa2a/fastapi_users-14.0.1.tar.gz", hash = "sha256:8c032b3a75c6fb2b1f5eab8ffce5321176e9916efe1fe93e7c15ee55f0b02236", size = 120315, upload-time = "2025-01-04T13:20:05.95Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/52/fadeae2c8435fb457a9cd91e402639fa5c9a25b16e6d204e043bf00cd875/fastapi_users-15.0.4.tar.gz", hash = "sha256:62657a4323de929cd98697b0fbdea77773ef271a6b57ef359080b9f773ebe144", size = 121394, upload-time = "2026-02-05T09:36:41.194Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/52/2821d3e95a92567d38f98a33d1ef89302aa3448866bf45ff19a48a5f28f8/fastapi_users-14.0.1-py3-none-any.whl", hash = "sha256:074df59676dccf79412d2880bdcb661ab1fabc2ecec1f043b4e6a23be97ed9e1", size = 38717, upload-time = "2025-01-04T13:20:04.441Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/5fb2a18227ccbd5138515f21fc4fa8abcd9982238de43511d7f941e708db/fastapi_users-15.0.4-py3-none-any.whl", hash = "sha256:30940894825e1dd7b86f6013e4bc75eccc25ae8ce5261d1b180f6411bb28aff4", size = 39037, upload-time = "2026-02-05T09:36:42.195Z" }, ] [package.optional-dependencies] @@ -684,18 +795,71 @@ dependencies = [ { name = "sqlmodel" }, ] +[[package]] +name = "fastar" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/e7/f89d54fb04104114dd0552836dc2b47914f416cc0e200b409dd04a33de5e/fastar-0.8.0.tar.gz", hash = "sha256:f4d4d68dbf1c4c2808f0e730fac5843493fc849f70fe3ad3af60dfbaf68b9a12", size = 68524, upload-time = "2025-11-26T02:36:00.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/a5/79ecba3646e22d03eef1a66fb7fc156567213e2e4ab9faab3bbd4489e483/fastar-0.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a3253a06845462ca2196024c7a18f5c0ba4de1532ab1c4bad23a40b332a06a6a", size = 706112, upload-time = "2025-11-26T02:34:39.237Z" }, + { url = "https://files.pythonhosted.org/packages/0a/03/4f883bce878218a8676c2d7ca09b50c856a5470bb3b7f63baf9521ea6995/fastar-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5cbeb3ebfa0980c68ff8b126295cc6b208ccd81b638aebc5a723d810a7a0e5d2", size = 628954, upload-time = "2025-11-26T02:34:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f1/892e471f156b03d10ba48ace9384f5a896702a54506137462545f38e40b8/fastar-0.8.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1c0d5956b917daac77d333d48b3f0f3ff927b8039d5b32d8125462782369f761", size = 868685, upload-time = "2025-11-26T02:33:53.077Z" }, + { url = "https://files.pythonhosted.org/packages/39/ba/e24915045852e30014ec6840446975c03f4234d1c9270394b51d3ad18394/fastar-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27b404db2b786b65912927ce7f3790964a4bcbde42cdd13091b82a89cd655e1c", size = 765044, upload-time = "2025-11-26T02:32:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/1aa11ac21a99984864c2fca4994e094319ff3a2046e7a0343c39317bd5b9/fastar-0.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0902fc89dcf1e7f07b8563032a4159fe2b835e4c16942c76fd63451d0e5f76a3", size = 764322, upload-time = "2025-11-26T02:33:03.859Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f0/4b91902af39fe2d3bae7c85c6d789586b9fbcf618d7fdb3d37323915906d/fastar-0.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:069347e2f0f7a8b99bbac8cd1bc0e06c7b4a31dc964fc60d84b95eab3d869dc1", size = 931016, upload-time = "2025-11-26T02:33:19.902Z" }, + { url = "https://files.pythonhosted.org/packages/c9/97/8fc43a5a9c0a2dc195730f6f7a0f367d171282cd8be2511d0e87c6d2dad0/fastar-0.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd135306f6bfe9a835918280e0eb440b70ab303e0187d90ab51ca86e143f70d", size = 821308, upload-time = "2025-11-26T02:33:34.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e9/058615b63a7fd27965e8c5966f393ed0c169f7ff5012e1674f21684de3ba/fastar-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d06d6897f43c27154b5f2d0eb930a43a81b7eec73f6f0b0114814d4a10ab38", size = 821171, upload-time = "2025-11-26T02:34:08.498Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cf/69e16a17961570a755c37ffb5b5aa7610d2e77807625f537989da66f2a9d/fastar-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a922f8439231fa0c32b15e8d70ff6d415619b9d40492029dabbc14a0c53b5f18", size = 986227, upload-time = "2025-11-26T02:34:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/fb/83/2100192372e59b56f4ace37d7d9cabda511afd71b5febad1643d1c334271/fastar-0.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a739abd51eb766384b4caff83050888e80cd75bbcfec61e6d1e64875f94e4a40", size = 1039395, upload-time = "2025-11-26T02:35:12.166Z" }, + { url = "https://files.pythonhosted.org/packages/75/15/cdd03aca972f55872efbb7cf7540c3fa7b97a75d626303a3ea46932163dc/fastar-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a65f419d808b23ac89d5cd1b13a2f340f15bc5d1d9af79f39fdb77bba48ff1b", size = 1044766, upload-time = "2025-11-26T02:35:29.62Z" }, + { url = "https://files.pythonhosted.org/packages/3d/29/945e69e4e2652329ace545999334ec31f1431fbae3abb0105587e11af2ae/fastar-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7bb2ae6c0cce58f0db1c9f20495e7557cca2c1ee9c69bbd90eafd54f139171c5", size = 994740, upload-time = "2025-11-26T02:35:47.887Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5d/dbfe28f8cd1eb484bba0c62e5259b2cf6fea229d6ef43e05c06b5a78c034/fastar-0.8.0-cp313-cp313-win32.whl", hash = "sha256:b28753e0d18a643272597cb16d39f1053842aa43131ad3e260c03a2417d38401", size = 455990, upload-time = "2025-11-26T02:36:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/e1/01/e965740bd36e60ef4c5aa2cbe42b6c4eb1dc3551009238a97c2e5e96bd23/fastar-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:620e5d737dce8321d49a5ebb7997f1fd0047cde3512082c27dc66d6ac8c1927a", size = 490227, upload-time = "2025-11-26T02:36:14.363Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/c99202719b83e5249f26902ae53a05aea67d840eeb242019322f20fc171c/fastar-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:c4c4bd08df563120cd33e854fe0a93b81579e8571b11f9b7da9e84c37da2d6b6", size = 461078, upload-time = "2025-11-26T02:36:04.94Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9573b87a0ef07580ed111e7230259aec31bb33ca3667963ebee77022ec61/fastar-0.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:50b36ce654ba44b0e13fae607ae17ee6e1597b69f71df1bee64bb8328d881dfc", size = 706041, upload-time = "2025-11-26T02:34:40.638Z" }, + { url = "https://files.pythonhosted.org/packages/4a/19/f95444a1d4f375333af49300aa75ee93afa3335c0e40fda528e460ed859c/fastar-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63a892762683d7ab00df0227d5ea9677c62ff2cde9b875e666c0be569ed940f3", size = 628617, upload-time = "2025-11-26T02:34:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c9/b51481b38b7e3f16ef2b9e233b1a3623386c939d745d6e41bbd389eaae30/fastar-0.8.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4ae6a145c1bff592644bde13f2115e0239f4b7babaf506d14e7d208483cf01a5", size = 869299, upload-time = "2025-11-26T02:33:54.274Z" }, + { url = "https://files.pythonhosted.org/packages/bf/02/3ba1267ee5ba7314e29c431cf82eaa68586f2c40cdfa08be3632b7d07619/fastar-0.8.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae0ff7c0a1c7e1428404b81faee8aebef466bfd0be25bfe4dabf5d535c68741", size = 764667, upload-time = "2025-11-26T02:32:49.606Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/bf33530fd015b5d7c2cc69e0bce4a38d736754a6955487005aab1af6adcd/fastar-0.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbfd87dbd217b45c898b2dbcd0169aae534b2c1c5cbe3119510881f6a5ac8ef5", size = 763993, upload-time = "2025-11-26T02:33:05.782Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/9564d24e7cea6321a8d921c6d2a457044a476ef197aa4708e179d3d97f0d/fastar-0.8.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5abd99fcba83ef28c8fe6ae2927edc79053db43a0457a962ed85c9bf150d37", size = 930153, upload-time = "2025-11-26T02:33:21.53Z" }, + { url = "https://files.pythonhosted.org/packages/35/b1/6f57fcd8d6e192cfebf97e58eb27751640ad93784c857b79039e84387b51/fastar-0.8.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91d4c685620c3a9d6b5ae091dbabab4f98b20049b7ecc7976e19cc9016c0d5d6", size = 821177, upload-time = "2025-11-26T02:33:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b3/78/9e004ea9f3aa7466f5ddb6f9518780e1d2f0ed3ca55f093632982598bace/fastar-0.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77c2f2cad76e9dc7b6701297adb1eba87d0485944b416fc2ccf5516c01219a3", size = 820652, upload-time = "2025-11-26T02:34:09.776Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/b604ed536544005c9f1aee7c4c74b00150db3d8d535cd8232dc20f947063/fastar-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e7f07c4a3dada7757a8fc430a5b4a29e6ef696d2212747213f57086ffd970316", size = 985961, upload-time = "2025-11-26T02:34:56.401Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7b/fa9d4d96a5d494bdb8699363bb9de8178c0c21a02e1d89cd6f913d127018/fastar-0.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:90c0c3fe55105c0aed8a83135dbdeb31e683455dbd326a1c48fa44c378b85616", size = 1039316, upload-time = "2025-11-26T02:35:13.807Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f9/8462789243bc3f33e8401378ec6d54de4e20cfa60c96a0e15e3e9d1389bb/fastar-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fb9ee51e5bffe0dab3d3126d3a4fac8d8f7235cedcb4b8e74936087ce1c157f3", size = 1045028, upload-time = "2025-11-26T02:35:31.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/71/9abb128777e616127194b509e98fcda3db797d76288c1a8c23dd22afc14f/fastar-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e380b1e8d30317f52406c43b11e98d11e1d68723bbd031e18049ea3497b59a6d", size = 994677, upload-time = "2025-11-26T02:35:49.391Z" }, + { url = "https://files.pythonhosted.org/packages/de/c1/b81b3f194853d7ad232a67a1d768f5f51a016f165cfb56cb31b31bbc6177/fastar-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1c4ffc06e9c4a8ca498c07e094670d8d8c0d25b17ca6465b9774da44ea997ab1", size = 456687, upload-time = "2025-11-26T02:36:30.205Z" }, + { url = "https://files.pythonhosted.org/packages/cb/87/9e0cd4768a98181d56f0cdbab2363404cc15deb93f4aad3b99cd2761bbaa/fastar-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:5517a8ad4726267c57a3e0e2a44430b782e00b230bf51c55b5728e758bb3a692", size = 490578, upload-time = "2025-11-26T02:36:16.218Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/580a76cf91847654f2ad6520e956e93218f778540975bc4190d363f709e2/fastar-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:58030551046ff4a8616931e52a36c83545ff05996db5beb6e0cd2b7e748aa309", size = 461473, upload-time = "2025-11-26T02:36:06.373Z" }, + { url = "https://files.pythonhosted.org/packages/58/4c/bdb5c6efe934f68708529c8c9d4055ebef5c4be370621966438f658b29bd/fastar-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:1e7d29b6bfecb29db126a08baf3c04a5ab667f6cea2b7067d3e623a67729c4a6", size = 705570, upload-time = "2025-11-26T02:34:42.01Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/f01ac7e71d5a37621bd13598a26e948a12b85ca8042f7ee1a0a8c9f59cda/fastar-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05eb7b96940f9526b485f1d0b02393839f0f61cac4b1f60024984f8b326d2640", size = 627761, upload-time = "2025-11-26T02:34:26.152Z" }, + { url = "https://files.pythonhosted.org/packages/06/45/6df0ecda86ea9d2e95053c1a655d153dee55fc121b6e13ea6d1e246a50b6/fastar-0.8.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:619352d8ac011794e2345c462189dc02ba634750d23cd9d86a9267dd71b1f278", size = 869414, upload-time = "2025-11-26T02:33:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b2/72/486421f5a8c0c377cc82e7a50c8a8ea899a6ec2aa72bde8f09fb667a2dc8/fastar-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74ebfecef3fe6d7a90355fac1402fd30636988332a1d33f3e80019a10782bb24", size = 763863, upload-time = "2025-11-26T02:32:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/39f654dbb41a3867fb1f2c8081c014d8f1d32ea10585d84cacbef0b32995/fastar-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2975aca5a639e26a3ab0d23b4b0628d6dd6d521146c3c11486d782be621a35aa", size = 763065, upload-time = "2025-11-26T02:33:07.274Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bd/c011a34fb3534c4c3301f7c87c4ffd7e47f6113c904c092ddc8a59a303ea/fastar-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afc438eaed8ff0dcdd9308268be5cb38c1db7e94c3ccca7c498ca13a4a4535a3", size = 930530, upload-time = "2025-11-26T02:33:23.117Z" }, + { url = "https://files.pythonhosted.org/packages/55/9d/aa6e887a7033c571b1064429222bbe09adc9a3c1e04f3d1788ba5838ebd5/fastar-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ced0a5399cc0a84a858ef0a31ca2d0c24d3bbec4bcda506a9192d8119f3590a", size = 820572, upload-time = "2025-11-26T02:33:37.542Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9c/7a3a2278a1052e1a5d98646de7c095a00cffd2492b3b84ce730e2f1cd93a/fastar-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec9b23da8c4c039da3fe2e358973c66976a0c8508aa06d6626b4403cb5666c19", size = 820649, upload-time = "2025-11-26T02:34:11.108Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d38edc1f4438cd047e56137c26d94783ffade42e1b3bde620ccf17b771ef/fastar-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dfba078fcd53478032fd0ceed56960ec6b7ff0511cfc013a8a3a4307e3a7bac4", size = 985653, upload-time = "2025-11-26T02:34:57.884Z" }, + { url = "https://files.pythonhosted.org/packages/69/d9/2147d0c19757e165cd62d41cec3f7b38fad2ad68ab784978b5f81716c7ea/fastar-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ade56c94c14be356d295fecb47a3fcd473dd43a8803ead2e2b5b9e58feb6dcfa", size = 1038140, upload-time = "2025-11-26T02:35:15.778Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1d/ec4c717ffb8a308871e9602ec3197d957e238dc0227127ac573ec9bca952/fastar-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e48d938f9366db5e59441728f70b7f6c1ccfab7eff84f96f9b7e689b07786c52", size = 1045195, upload-time = "2025-11-26T02:35:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/637334dc8c8f3bb391388b064ae13f0ad9402bc5a6c3e77b8887d0c31921/fastar-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:79c441dc1482ff51a54fb3f57ae6f7bb3d2cff88fa2cc5d196c519f8aab64a56", size = 994686, upload-time = "2025-11-26T02:35:51.392Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e2/dfa19a4b260b8ab3581b7484dcb80c09b25324f4daa6b6ae1c7640d1607a/fastar-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:187f61dc739afe45ac8e47ed7fd1adc45d52eac110cf27d579155720507d6fbe", size = 455767, upload-time = "2025-11-26T02:36:34.758Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/df65c72afc1297797b255f90c4778b5d6f1f0f80282a134d5ab610310ed9/fastar-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:40e9d763cf8bf85ce2fa256e010aa795c0fe3d3bd1326d5c3084e6ce7857127e", size = 489971, upload-time = "2025-11-26T02:36:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/85/11/0aa8455af26f0ae89e42be67f3a874255ee5d7f0f026fc86e8d56f76b428/fastar-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e59673307b6a08210987059a2bdea2614fe26e3335d0e5d1a3d95f49a05b1418", size = 460467, upload-time = "2025-11-26T02:36:07.978Z" }, +] + [[package]] name = "filelock" -version = "3.20.0" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] name = "google-api-core" -version = "2.26.0" +version = "2.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -704,14 +868,14 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/ea/e7b6ac3c7b557b728c2d0181010548cbbdd338e9002513420c5a354fa8df/google_api_core-2.26.0.tar.gz", hash = "sha256:e6e6d78bd6cf757f4aee41dcc85b07f485fbb069d5daa3afb126defba1e91a62", size = 166369, upload-time = "2025-10-08T21:37:38.39Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/10/05572d33273292bac49c2d1785925f7bc3ff2fe50e3044cf1062c1dde32e/google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7", size = 177828, upload-time = "2026-01-08T22:21:39.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/ad/f73cf9fe9bd95918502b270e3ddb8764e4c900b3bbd7782b90c56fac14bb/google_api_core-2.26.0-py3-none-any.whl", hash = "sha256:2b204bd0da2c81f918e3582c48458e24c11771f987f6258e6e227212af78f3ed", size = 162505, upload-time = "2025-10-08T21:37:36.651Z" }, + { url = "https://files.pythonhosted.org/packages/77/b6/85c4d21067220b9a78cfb81f516f9725ea6befc1544ec9bd2c1acd97c324/google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9", size = 173906, upload-time = "2026-01-08T22:21:36.093Z" }, ] [[package]] name = "google-api-python-client" -version = "2.184.0" +version = "2.190.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -720,72 +884,82 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/30/8b3a626ccf84ca43da62d77e2d40d70bedc6387951cc5104011cddce34e0/google_api_python_client-2.184.0.tar.gz", hash = "sha256:ef2a3330ad058cdfc8a558d199c051c3356f6ed012436c3ad3d08b67891b039f", size = 13694120, upload-time = "2025-10-01T21:13:48.961Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/4ab3e3516b93bb50ed7814738ea61d49cba3f72f4e331dc9518ae2731e92/google_api_python_client-2.190.0.tar.gz", hash = "sha256:5357f34552e3724d80d2604c8fa146766e0a9d6bb0afada886fafed9feafeef6", size = 14111143, upload-time = "2026-02-12T00:38:03.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/38/d25ae1565103a545cf18207a5dec09a6d39ad88e5b0399a2430e9edb0550/google_api_python_client-2.184.0-py3-none-any.whl", hash = "sha256:15a18d02f42de99416921c77be235d12ead474e474a1abc348b01a2b92633fa4", size = 14260480, upload-time = "2025-10-01T21:13:46.037Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/223d5f4b0b987669ffeb3eadd7e9f85ece633aa7fd3246f1e2f6238e1e05/google_api_python_client-2.190.0-py3-none-any.whl", hash = "sha256:d9b5266758f96c39b8c21d9bbfeb4e58c14dbfba3c931f7c5a8d7fdcd292dd57", size = 14682070, upload-time = "2026-02-12T00:38:00.974Z" }, ] [[package]] name = "google-auth" -version = "2.41.1" +version = "2.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cachetools" }, + { name = "cryptography" }, { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/af/5129ce5b2f9688d2fa49b463e544972a7c82b0fdb50980dafee92e121d9f/google_auth-2.41.1.tar.gz", hash = "sha256:b76b7b1f9e61f0cb7e88870d14f6a94aeef248959ef6992670efee37709cbfd2", size = 292284, upload-time = "2025-09-30T22:51:26.363Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/a4/7319a2a8add4cc352be9e3efeff5e2aacee917c85ca2fa1647e29089983c/google_auth-2.41.1-py2.py3-none-any.whl", hash = "sha256:754843be95575b9a19c604a848a41be03f7f2afd8c019f716dc1f51ee41c639d", size = 221302, upload-time = "2025-09-30T22:51:24.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, ] [[package]] name = "google-auth-httplib2" -version = "0.2.0" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, { name = "httplib2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134, upload-time = "2025-12-15T22:13:51.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529, upload-time = "2025-12-15T22:13:51.048Z" }, ] [[package]] name = "googleapis-common-protos" -version = "1.70.0" +version = "1.72.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, ] [[package]] name = "greenlet" -version = "3.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, - { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, - { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, - { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, - { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, - { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" }, + { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, + { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, ] [[package]] @@ -812,14 +986,14 @@ wheels = [ [[package]] name = "httplib2" -version = "0.31.0" +version = "0.31.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/77/6653db69c1f7ecfe5e3f9726fdadc981794656fcd7d98c4209fecfea9993/httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c", size = 250759, upload-time = "2025-09-11T12:16:03.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/a2/0d269db0f6163be503775dc8b6a6fa15820cc9fdc866f6ba608d86b721f2/httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24", size = 91148, upload-time = "2025-09-11T12:16:01.803Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, ] [[package]] @@ -894,11 +1068,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -924,11 +1098,11 @@ wheels = [ [[package]] name = "jmespath" -version = "1.0.1" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] [[package]] @@ -954,11 +1128,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.9" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -1035,64 +1209,69 @@ wheels = [ ] [[package]] -name = "nodeenv" -version = "1.9.1" +name = "mjml" +version = "0.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "docopt-ng" }, + { name = "dotmap" }, + { name = "jinja2" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/e3/ab5a6fedd48eb2122030e9b4eb6fa560696eba6ec7a652dc92bdca22459d/mjml-0.12.0.tar.gz", hash = "sha256:614397e624b115d78f9064d0f8aabceda3b522785eca8c2818ee03ffa8fdbf37", size = 72684, upload-time = "2025-12-27T19:32:42.584Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/a2/34/389deb78f9a4f86945532572f417ea5ec68227cc4f67d3140998cfaceafb/mjml-0.12.0-py3-none-any.whl", hash = "sha256:2329aa6b31237ce7309c5605e049d5c110a841218220f6af80bd9731040947da", size = 66884, upload-time = "2025-12-27T19:32:41.344Z" }, ] [[package]] name = "numpy" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" }, - { url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" }, - { url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" }, - { url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" }, - { url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" }, - { url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" }, - { url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" }, - { url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" }, - { url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" }, - { url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" }, - { url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" }, - { url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" }, - { url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" }, - { url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" }, - { url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" }, - { url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" }, - { url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" }, - { url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" }, - { url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" }, - { url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" }, - { url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" }, - { url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" }, - { url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" }, - { url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" }, - { url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" }, - { url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" }, - { url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" }, - { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, ] [[package]] @@ -1109,120 +1288,128 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pandas" -version = "2.3.3" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, + { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, + { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, + { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, + { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, + { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, + { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, + { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, + { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, ] [[package]] name = "paracelsus" -version = "0.12.0" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "packaging" }, { name = "pydot" }, { name = "sqlalchemy" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/fe/84f4d06ac3e6038384847dc1d5c8b956f61b780f69509d177107b550c7b9/paracelsus-0.12.0.tar.gz", hash = "sha256:f1d8f584ebc445db99a2906f97ff55f36ae663c104320dd4a6b5b78b4fa24dce", size = 83664, upload-time = "2025-10-07T12:45:41.112Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/cc/d545a19967c3bdeba92ca1d8a736576b96b4610154f3bd6dbf01a198e2c3/paracelsus-0.15.0.tar.gz", hash = "sha256:b850b56417eef7b5e301b09ba7d44655f3c76de8681699b93ef6ae410afeb278", size = 92053, upload-time = "2026-01-04T21:38:25.508Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/60/9062e4072c16750b6b01bac9c55b329b249ee7c970d61c128049be197d7a/paracelsus-0.12.0-py3-none-any.whl", hash = "sha256:01f5a508174d06a86d53374215a0c85962498361ac3f0bd3450023760d3b3836", size = 81236, upload-time = "2025-10-07T12:45:39.929Z" }, + { url = "https://files.pythonhosted.org/packages/18/70/3fa8dad530ae181b0a30f9874bababaa3d3781f9ef6c87aeaeed79b3c954/paracelsus-0.15.0-py3-none-any.whl", hash = "sha256:0ed0f97fb5ec09e379e45c1a95e280b1c40ee42af3c77f59f03998477a73fde2", size = 19606, upload-time = "2026-01-04T21:38:24.284Z" }, ] [[package]] name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, - { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, - { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, ] [[package]] @@ -1236,40 +1423,41 @@ wheels = [ [[package]] name = "proto-plus" -version = "1.26.1" +version = "1.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/02/8832cde80e7380c600fbf55090b6ab7b62bd6825dbedde6d6657c15a1f8e/proto_plus-1.27.1.tar.gz", hash = "sha256:912a7460446625b792f6448bade9e55cd4e41e6ac10e27009ef71a7f317fa147", size = 56929, upload-time = "2026-02-02T17:34:49.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, + { url = "https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl", hash = "sha256:e4643061f3a4d0de092d62aa4ad09fa4756b2cbb89d4627f3985018216f9fefc", size = 50480, upload-time = "2026-02-02T17:34:47.339Z" }, ] [[package]] name = "protobuf" -version = "6.32.1" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, - { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, - { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, - { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, - { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] name = "psycopg" -version = "3.2.10" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/f1/0258a123c045afaf3c3b60c22ccff077bceeb24b8dc2c593270899353bd0/psycopg-3.2.10.tar.gz", hash = "sha256:0bce99269d16ed18401683a8569b2c5abd94f72f8364856d56c0389bcd50972a", size = 160380, upload-time = "2025-09-08T09:13:37.775Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/1a/7d9ef4fdc13ef7f15b934c393edc97a35c281bb7d3c3329fbfcbe915a7c2/psycopg-3.3.2.tar.gz", hash = "sha256:707a67975ee214d200511177a6a80e56e654754c9afca06a7194ea6bbfde9ca7", size = 165630, upload-time = "2025-12-06T17:34:53.899Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/90/422ffbbeeb9418c795dae2a768db860401446af0c6768bc061ce22325f58/psycopg-3.2.10-py3-none-any.whl", hash = "sha256:ab5caf09a9ec42e314a21f5216dbcceac528e0e05142e42eea83a3b28b320ac3", size = 206586, upload-time = "2025-09-08T09:07:50.121Z" }, + { url = "https://files.pythonhosted.org/packages/8c/51/2779ccdf9305981a06b21a6b27e8547c948d85c41c76ff434192784a4c93/psycopg-3.3.2-py3-none-any.whl", hash = "sha256:3e94bc5f4690247d734599af56e51bae8e0db8e4311ea413f801fef82b14a99b", size = 212774, upload-time = "2025-12-06T17:31:41.414Z" }, ] [package.optional-dependencies] @@ -1279,36 +1467,40 @@ binary = [ [[package]] name = "psycopg-binary" -version = "3.2.10" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/80/db840f7ebf948ab05b4793ad34d4da6ad251829d6c02714445ae8b5f1403/psycopg_binary-3.2.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:55b14f2402be027fe1568bc6c4d75ac34628ff5442a70f74137dadf99f738e3b", size = 3982057, upload-time = "2025-09-08T09:10:28.725Z" }, - { url = "https://files.pythonhosted.org/packages/2d/53/39308328bb8388b1ec3501a16128c5ada405f217c6d91b3d921b9f3c5604/psycopg_binary-3.2.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:43d803fb4e108a67c78ba58f3e6855437ca25d56504cae7ebbfbd8fce9b59247", size = 4066830, upload-time = "2025-09-08T09:10:34.083Z" }, - { url = "https://files.pythonhosted.org/packages/e7/5a/18e6f41b40c71197479468cb18703b2999c6e4ab06f9c05df3bf416a55d7/psycopg_binary-3.2.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:470594d303928ab72a1ffd179c9c7bde9d00f76711d6b0c28f8a46ddf56d9807", size = 4610747, upload-time = "2025-09-08T09:10:39.697Z" }, - { url = "https://files.pythonhosted.org/packages/be/ab/9198fed279aca238c245553ec16504179d21aad049958a2865d0aa797db4/psycopg_binary-3.2.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a1d4e4d309049e3cb61269652a3ca56cb598da30ecd7eb8cea561e0d18bc1a43", size = 4700301, upload-time = "2025-09-08T09:10:44.715Z" }, - { url = "https://files.pythonhosted.org/packages/fc/0d/59024313b5e6c5da3e2a016103494c609d73a95157a86317e0f600c8acb3/psycopg_binary-3.2.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a92ff1c2cd79b3966d6a87e26ceb222ecd5581b5ae4b58961f126af806a861ed", size = 4392679, upload-time = "2025-09-08T09:10:49.106Z" }, - { url = "https://files.pythonhosted.org/packages/ff/47/21ef15d8a66e3a7a76a177f885173d27f0c5cbe39f5dd6eda9832d6b4e19/psycopg_binary-3.2.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac0365398947879c9827b319217096be727da16c94422e0eb3cf98c930643162", size = 3857881, upload-time = "2025-09-08T09:10:56.75Z" }, - { url = "https://files.pythonhosted.org/packages/af/35/c5e5402ccd40016f15d708bbf343b8cf107a58f8ae34d14dc178fdea4fd4/psycopg_binary-3.2.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:42ee399c2613b470a87084ed79b06d9d277f19b0457c10e03a4aef7059097abc", size = 3531135, upload-time = "2025-09-08T09:11:03.346Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e2/9b82946859001fe5e546c8749991b8b3b283f40d51bdc897d7a8e13e0a5e/psycopg_binary-3.2.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2028073fc12cd70ba003309d1439c0c4afab4a7eee7653b8c91213064fffe12b", size = 3581813, upload-time = "2025-09-08T09:11:08.76Z" }, - { url = "https://files.pythonhosted.org/packages/c5/91/c10cfccb75464adb4781486e0014ecd7c2ad6decf6cbe0afd8db65ac2bc9/psycopg_binary-3.2.10-cp313-cp313-win_amd64.whl", hash = "sha256:8390db6d2010ffcaf7f2b42339a2da620a7125d37029c1f9b72dfb04a8e7be6f", size = 2881466, upload-time = "2025-09-08T09:11:14.078Z" }, - { url = "https://files.pythonhosted.org/packages/fd/89/b0702ba0d007cc787dd7a205212c8c8cae229d1e7214c8e27bdd3b13d33e/psycopg_binary-3.2.10-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b34c278a58aa79562afe7f45e0455b1f4cad5974fc3d5674cc5f1f9f57e97fc5", size = 3981253, upload-time = "2025-09-08T09:11:19.864Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c9/e51ac72ac34d1d8ea7fd861008ad8de60e56997f5bd3fbae7536570f6f58/psycopg_binary-3.2.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810f65b9ef1fe9dddb5c05937884ea9563aaf4e1a2c3d138205231ed5f439511", size = 4067542, upload-time = "2025-09-08T09:11:25.366Z" }, - { url = "https://files.pythonhosted.org/packages/d6/27/49625c79ae89959a070c1fb63ebb5c6eed426fa09e15086b6f5b626fcdc2/psycopg_binary-3.2.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8923487c3898c65e1450847e15d734bb2e6adbd2e79d2d1dd5ad829a1306bdc0", size = 4615338, upload-time = "2025-09-08T09:11:31.079Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0d/9fdb5482f50f56303770ea8a3b1c1f32105762da731c7e2a4f425e0b3887/psycopg_binary-3.2.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7950ff79df7a453ac8a7d7a74694055b6c15905b0a2b6e3c99eb59c51a3f9bf7", size = 4703401, upload-time = "2025-09-08T09:11:38.718Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f3/eb2f75ca2c090bf1d0c90d6da29ef340876fe4533bcfc072a9fd94dd52b4/psycopg_binary-3.2.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c2b95e83fda70ed2b0b4fadd8538572e4a4d987b721823981862d1ab56cc760", size = 4393458, upload-time = "2025-09-08T09:11:44.114Z" }, - { url = "https://files.pythonhosted.org/packages/20/2e/887abe0591b2f1c1af31164b9efb46c5763e4418f403503bc9fbddaa02ef/psycopg_binary-3.2.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20384985fbc650c09a547a13c6d7f91bb42020d38ceafd2b68b7fc4a48a1f160", size = 3863733, upload-time = "2025-09-08T09:11:49.237Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8c/9446e3a84187220a98657ef778518f9b44eba55b1f6c3e8300d229ec9930/psycopg_binary-3.2.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1f6982609b8ff8fcd67299b67cd5787da1876f3bb28fedd547262cfa8ddedf94", size = 3535121, upload-time = "2025-09-08T09:11:53.887Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e1/f0382c956bfaa951a0dbd4d5a354acf093ef7e5219996958143dfd2bf37d/psycopg_binary-3.2.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bf30dcf6aaaa8d4779a20d2158bdf81cc8e84ce8eee595d748a7671c70c7b890", size = 3584235, upload-time = "2025-09-08T09:12:01.118Z" }, - { url = "https://files.pythonhosted.org/packages/5a/dd/464bd739bacb3b745a1c93bc15f20f0b1e27f0a64ec693367794b398673b/psycopg_binary-3.2.10-cp314-cp314-win_amd64.whl", hash = "sha256:d5c6a66a76022af41970bf19f51bc6bf87bd10165783dd1d40484bfd87d6b382", size = 2973554, upload-time = "2025-09-08T09:12:05.884Z" }, +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/73/7ca7cb22b9ac7393fb5de7d28ca97e8347c375c8498b3bff2c99c1f38038/psycopg_binary-3.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc5a189e89cbfff174588665bb18d28d2d0428366cc9dae5864afcaa2e57380b", size = 4579068, upload-time = "2025-12-06T17:33:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/f5/42/0cf38ff6c62c792fc5b55398a853a77663210ebd51ed6f0c4a05b06f95a6/psycopg_binary-3.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:083c2e182be433f290dc2c516fd72b9b47054fcd305cce791e0a50d9e93e06f2", size = 4657520, upload-time = "2025-12-06T17:33:42.536Z" }, + { url = "https://files.pythonhosted.org/packages/3b/60/df846bc84cbf2231e01b0fff48b09841fe486fa177665e50f4995b1bfa44/psycopg_binary-3.3.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:ac230e3643d1c436a2dfb59ca84357dfc6862c9f372fc5dbd96bafecae581f9f", size = 5452086, upload-time = "2025-12-06T17:33:46.54Z" }, + { url = "https://files.pythonhosted.org/packages/ab/85/30c846a00db86b1b53fd5bfd4b4edfbd0c00de8f2c75dd105610bd7568fc/psycopg_binary-3.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d8c899a540f6c7585cee53cddc929dd4d2db90fd828e37f5d4017b63acbc1a5d", size = 5131125, upload-time = "2025-12-06T17:33:50.413Z" }, + { url = "https://files.pythonhosted.org/packages/6d/15/9968732013373f36f8a2a3fb76104dffc8efd9db78709caa5ae1a87b1f80/psycopg_binary-3.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50ff10ab8c0abdb5a5451b9315538865b50ba64c907742a1385fdf5f5772b73e", size = 6722914, upload-time = "2025-12-06T17:33:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ba/29e361fe02143ac5ff5a1ca3e45697344cfbebe2eaf8c4e7eec164bff9a0/psycopg_binary-3.3.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:23d2594af848c1fd3d874a9364bef50730124e72df7bb145a20cb45e728c50ed", size = 4966081, upload-time = "2025-12-06T17:33:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/99/45/1be90c8f1a1a237046903e91202fb06708745c179f220b361d6333ed7641/psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea4fe6b4ead3bbbe27244ea224fcd1f53cb119afc38b71a2f3ce570149a03e30", size = 4493332, upload-time = "2025-12-06T17:34:02.011Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/bbdc07d5f0a5e90c617abd624368182aa131485e18038b2c6c85fc054aed/psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:742ce48cde825b8e52fb1a658253d6d1ff66d152081cbc76aa45e2986534858d", size = 4170781, upload-time = "2025-12-06T17:34:05.298Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2a/0d45e4f4da2bd78c3237ffa03475ef3751f69a81919c54a6e610eb1a7c96/psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e22bf6b54df994aff37ab52695d635f1ef73155e781eee1f5fa75bc08b58c8da", size = 3910544, upload-time = "2025-12-06T17:34:08.251Z" }, + { url = "https://files.pythonhosted.org/packages/3a/62/a8e0f092f4dbef9a94b032fb71e214cf0a375010692fbe7493a766339e47/psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8db9034cde3bcdafc66980f0130813f5c5d19e74b3f2a19fb3cfbc25ad113121", size = 4220070, upload-time = "2025-12-06T17:34:11.392Z" }, + { url = "https://files.pythonhosted.org/packages/09/e6/5fc8d8aff8afa114bb4a94a0341b9309311e8bf3ab32d816032f8b984d4e/psycopg_binary-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:df65174c7cf6b05ea273ce955927d3270b3a6e27b0b12762b009ce6082b8d3fc", size = 3540922, upload-time = "2025-12-06T17:34:14.88Z" }, + { url = "https://files.pythonhosted.org/packages/bd/75/ad18c0b97b852aba286d06befb398cc6d383e9dfd0a518369af275a5a526/psycopg_binary-3.3.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9ca24062cd9b2270e4d77576042e9cc2b1d543f09da5aba1f1a3d016cea28390", size = 4596371, upload-time = "2025-12-06T17:34:18.007Z" }, + { url = "https://files.pythonhosted.org/packages/5a/79/91649d94c8d89f84af5da7c9d474bfba35b08eb8f492ca3422b08f0a6427/psycopg_binary-3.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c749770da0947bc972e512f35366dd4950c0e34afad89e60b9787a37e97cb443", size = 4675139, upload-time = "2025-12-06T17:34:21.374Z" }, + { url = "https://files.pythonhosted.org/packages/56/ac/b26e004880f054549ec9396594e1ffe435810b0673e428e619ed722e4244/psycopg_binary-3.3.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03b7cd73fb8c45d272a34ae7249713e32492891492681e3cf11dff9531cf37e9", size = 5456120, upload-time = "2025-12-06T17:34:25.102Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/410681dccd6f2999fb115cc248521ec50dd2b0aba66ae8de7e81efdebbee/psycopg_binary-3.3.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:43b130e3b6edcb5ee856c7167ccb8561b473308c870ed83978ae478613764f1c", size = 5133484, upload-time = "2025-12-06T17:34:28.933Z" }, + { url = "https://files.pythonhosted.org/packages/66/30/ebbab99ea2cfa099d7b11b742ce13415d44f800555bfa4ad2911dc645b71/psycopg_binary-3.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c1feba5a8c617922321aef945865334e468337b8fc5c73074f5e63143013b5a", size = 6731818, upload-time = "2025-12-06T17:34:33.094Z" }, + { url = "https://files.pythonhosted.org/packages/70/02/d260646253b7ad805d60e0de47f9b811d6544078452579466a098598b6f4/psycopg_binary-3.3.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cabb2a554d9a0a6bf84037d86ca91782f087dfff2a61298d0b00c19c0bc43f6d", size = 4983859, upload-time = "2025-12-06T17:34:36.457Z" }, + { url = "https://files.pythonhosted.org/packages/72/8d/e778d7bad1a7910aa36281f092bd85c5702f508fd9bb0ea2020ffbb6585c/psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74bc306c4b4df35b09bc8cecf806b271e1c5d708f7900145e4e54a2e5dedfed0", size = 4516388, upload-time = "2025-12-06T17:34:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f1/64e82098722e2ab3521797584caf515284be09c1e08a872551b6edbb0074/psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d79b0093f0fbf7a962d6a46ae292dc056c65d16a8ee9361f3cfbafd4c197ab14", size = 4192382, upload-time = "2025-12-06T17:34:43.279Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d0/c20f4e668e89494972e551c31be2a0016e3f50d552d7ae9ac07086407599/psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1586e220be05547c77afc326741dd41cc7fba38a81f9931f616ae98865439678", size = 3928660, upload-time = "2025-12-06T17:34:46.757Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/99746c171de22539fd5eb1c9ca21dc805b54cfae502d7451d237d1dbc349/psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:458696a5fa5dad5b6fb5d5862c22454434ce4fe1cf66ca6c0de5f904cbc1ae3e", size = 4239169, upload-time = "2025-12-06T17:34:49.751Z" }, + { url = "https://files.pythonhosted.org/packages/72/f7/212343c1c9cfac35fd943c527af85e9091d633176e2a407a0797856ff7b9/psycopg_binary-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:04bb2de4ba69d6f8395b446ede795e8884c040ec71d01dd07ac2b2d18d4153d1", size = 3642122, upload-time = "2025-12-06T17:34:52.506Z" }, ] [[package]] name = "pwdlib" -version = "0.2.1" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/a0/9daed437a6226f632a25d98d65d60ba02bdafa920c90dcb6454c611ead6c/pwdlib-0.2.1.tar.gz", hash = "sha256:9a1d8a8fa09a2f7ebf208265e55d7d008103cbdc82b9e4902ffdd1ade91add5e", size = 11699, upload-time = "2024-08-19T06:48:59.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810, upload-time = "2025-10-25T12:44:24.395Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/f3/0dae5078a486f0fdf4d4a1121e103bc42694a9da9bea7b0f2c63f29cfbd3/pwdlib-0.2.1-py3-none-any.whl", hash = "sha256:1823dc6f22eae472b540e889ecf57fd424051d6a4023ec0bcf7f0de2d9d7ef8c", size = 8082, upload-time = "2024-08-19T06:49:00.997Z" }, + { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633, upload-time = "2025-10-25T12:44:23.406Z" }, ] [package.optional-dependencies] @@ -1321,11 +1513,11 @@ bcrypt = [ [[package]] name = "pyasn1" -version = "0.6.1" +version = "0.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, ] [[package]] @@ -1342,16 +1534,16 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pydantic" -version = "2.12.0" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1359,9 +1551,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/da/b8a7ee04378a53f6fefefc0c5e05570a3ebfdfa0523a878bcd3b475683ee/pydantic-2.12.0.tar.gz", hash = "sha256:c1a077e6270dbfb37bfd8b498b3981e2bb18f68103720e51fa6c306a5a9af563", size = 814760, upload-time = "2025-10-07T15:58:03.467Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/9d/d5c855424e2e5b6b626fbc6ec514d8e655a600377ce283008b115abb7445/pydantic-2.12.0-py3-none-any.whl", hash = "sha256:f6a1da352d42790537e95e83a8bdfb91c7efbae63ffd0b86fa823899e807116f", size = 459730, upload-time = "2025-10-07T15:58:01.576Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [package.optional-dependencies] @@ -1371,86 +1563,94 @@ email = [ [[package]] name = "pydantic-core" -version = "2.41.1" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/14/12b4a0d2b0b10d8e1d9a24ad94e7bbb43335eaf29c0c4e57860e8a30734a/pydantic_core-2.41.1.tar.gz", hash = "sha256:1ad375859a6d8c356b7704ec0f547a58e82ee80bb41baa811ad710e124bc8f2f", size = 454870, upload-time = "2025-10-07T10:50:45.974Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/8a/6d54198536a90a37807d31a156642aae7a8e1263ed9fe6fc6245defe9332/pydantic_core-2.41.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70e790fce5f05204ef4403159857bfcd587779da78627b0babb3654f75361ebf", size = 2105825, upload-time = "2025-10-06T21:10:51.719Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2e/4784fd7b22ac9c8439db25bf98ffed6853d01e7e560a346e8af821776ccc/pydantic_core-2.41.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cebf1ca35f10930612d60bd0f78adfacee824c30a880e3534ba02c207cceceb", size = 1910126, upload-time = "2025-10-06T21:10:53.145Z" }, - { url = "https://files.pythonhosted.org/packages/f3/92/31eb0748059ba5bd0aa708fb4bab9fcb211461ddcf9e90702a6542f22d0d/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:170406a37a5bc82c22c3274616bf6f17cc7df9c4a0a0a50449e559cb755db669", size = 1961472, upload-time = "2025-10-06T21:10:55.754Z" }, - { url = "https://files.pythonhosted.org/packages/ab/91/946527792275b5c4c7dde4cfa3e81241bf6900e9fee74fb1ba43e0c0f1ab/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12d4257fc9187a0ccd41b8b327d6a4e57281ab75e11dda66a9148ef2e1fb712f", size = 2063230, upload-time = "2025-10-06T21:10:57.179Z" }, - { url = "https://files.pythonhosted.org/packages/31/5d/a35c5d7b414e5c0749f1d9f0d159ee2ef4bab313f499692896b918014ee3/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a75a33b4db105dd1c8d57839e17ee12db8d5ad18209e792fa325dbb4baeb00f4", size = 2229469, upload-time = "2025-10-06T21:10:59.409Z" }, - { url = "https://files.pythonhosted.org/packages/21/4d/8713737c689afa57ecfefe38db78259d4484c97aa494979e6a9d19662584/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08a589f850803a74e0fcb16a72081cafb0d72a3cdda500106942b07e76b7bf62", size = 2347986, upload-time = "2025-10-06T21:11:00.847Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ec/929f9a3a5ed5cda767081494bacd32f783e707a690ce6eeb5e0730ec4986/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a97939d6ea44763c456bd8a617ceada2c9b96bb5b8ab3dfa0d0827df7619014", size = 2072216, upload-time = "2025-10-06T21:11:02.43Z" }, - { url = "https://files.pythonhosted.org/packages/26/55/a33f459d4f9cc8786d9db42795dbecc84fa724b290d7d71ddc3d7155d46a/pydantic_core-2.41.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae423c65c556f09569524b80ffd11babff61f33055ef9773d7c9fabc11ed8d", size = 2193047, upload-time = "2025-10-06T21:11:03.787Z" }, - { url = "https://files.pythonhosted.org/packages/77/af/d5c6959f8b089f2185760a2779079e3c2c411bfc70ea6111f58367851629/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:4dc703015fbf8764d6a8001c327a87f1823b7328d40b47ce6000c65918ad2b4f", size = 2140613, upload-time = "2025-10-06T21:11:05.607Z" }, - { url = "https://files.pythonhosted.org/packages/58/e5/2c19bd2a14bffe7fabcf00efbfbd3ac430aaec5271b504a938ff019ac7be/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:968e4ffdfd35698a5fe659e5e44c508b53664870a8e61c8f9d24d3d145d30257", size = 2327641, upload-time = "2025-10-06T21:11:07.143Z" }, - { url = "https://files.pythonhosted.org/packages/93/ef/e0870ccda798c54e6b100aff3c4d49df5458fd64217e860cb9c3b0a403f4/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fff2b76c8e172d34771cd4d4f0ade08072385310f214f823b5a6ad4006890d32", size = 2318229, upload-time = "2025-10-06T21:11:08.73Z" }, - { url = "https://files.pythonhosted.org/packages/b1/4b/c3b991d95f5deb24d0bd52e47bcf716098fa1afe0ce2d4bd3125b38566ba/pydantic_core-2.41.1-cp313-cp313-win32.whl", hash = "sha256:a38a5263185407ceb599f2f035faf4589d57e73c7146d64f10577f6449e8171d", size = 1997911, upload-time = "2025-10-06T21:11:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/5c316fd62e01f8d6be1b7ee6b54273214e871772997dc2c95e204997a055/pydantic_core-2.41.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42ae7fd6760782c975897e1fdc810f483b021b32245b0105d40f6e7a3803e4b", size = 2034301, upload-time = "2025-10-06T21:11:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/29/41/902640cfd6a6523194123e2c3373c60f19006447f2fb06f76de4e8466c5b/pydantic_core-2.41.1-cp313-cp313-win_arm64.whl", hash = "sha256:ad4111acc63b7384e205c27a2f15e23ac0ee21a9d77ad6f2e9cb516ec90965fb", size = 1977238, upload-time = "2025-10-06T21:11:14.1Z" }, - { url = "https://files.pythonhosted.org/packages/04/04/28b040e88c1b89d851278478842f0bdf39c7a05da9e850333c6c8cbe7dfa/pydantic_core-2.41.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:440d0df7415b50084a4ba9d870480c16c5f67c0d1d4d5119e3f70925533a0edc", size = 1875626, upload-time = "2025-10-06T21:11:15.69Z" }, - { url = "https://files.pythonhosted.org/packages/d6/58/b41dd3087505220bb58bc81be8c3e8cbc037f5710cd3c838f44f90bdd704/pydantic_core-2.41.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71eaa38d342099405dae6484216dcf1e8e4b0bebd9b44a4e08c9b43db6a2ab67", size = 2045708, upload-time = "2025-10-06T21:11:17.258Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b8/760f23754e40bf6c65b94a69b22c394c24058a0ef7e2aa471d2e39219c1a/pydantic_core-2.41.1-cp313-cp313t-win_amd64.whl", hash = "sha256:555ecf7e50f1161d3f693bc49f23c82cf6cdeafc71fa37a06120772a09a38795", size = 1997171, upload-time = "2025-10-06T21:11:18.822Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/cec246429ddfa2778d2d6301eca5362194dc8749ecb19e621f2f65b5090f/pydantic_core-2.41.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:05226894a26f6f27e1deb735d7308f74ef5fa3a6de3e0135bb66cdcaee88f64b", size = 2107836, upload-time = "2025-10-06T21:11:20.432Z" }, - { url = "https://files.pythonhosted.org/packages/20/39/baba47f8d8b87081302498e610aefc37142ce6a1cc98b2ab6b931a162562/pydantic_core-2.41.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85ff7911c6c3e2fd8d3779c50925f6406d770ea58ea6dde9c230d35b52b16b4a", size = 1904449, upload-time = "2025-10-06T21:11:22.185Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/9a3d87cae2c75a5178334b10358d631bd094b916a00a5993382222dbfd92/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47f1f642a205687d59b52dc1a9a607f45e588f5a2e9eeae05edd80c7a8c47674", size = 1961750, upload-time = "2025-10-06T21:11:24.348Z" }, - { url = "https://files.pythonhosted.org/packages/27/42/a96c9d793a04cf2a9773bff98003bb154087b94f5530a2ce6063ecfec583/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df11c24e138876ace5ec6043e5cae925e34cf38af1a1b3d63589e8f7b5f5cdc4", size = 2063305, upload-time = "2025-10-06T21:11:26.556Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8d/028c4b7d157a005b1f52c086e2d4b0067886b213c86220c1153398dbdf8f/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f0bf7f5c8f7bf345c527e8a0d72d6b26eda99c1227b0c34e7e59e181260de31", size = 2228959, upload-time = "2025-10-06T21:11:28.426Z" }, - { url = "https://files.pythonhosted.org/packages/08/f7/ee64cda8fcc9ca3f4716e6357144f9ee71166775df582a1b6b738bf6da57/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82b887a711d341c2c47352375d73b029418f55b20bd7815446d175a70effa706", size = 2345421, upload-time = "2025-10-06T21:11:30.226Z" }, - { url = "https://files.pythonhosted.org/packages/13/c0/e8ec05f0f5ee7a3656973ad9cd3bc73204af99f6512c1a4562f6fb4b3f7d/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5f1d5d6bbba484bdf220c72d8ecd0be460f4bd4c5e534a541bb2cd57589fb8b", size = 2065288, upload-time = "2025-10-06T21:11:32.019Z" }, - { url = "https://files.pythonhosted.org/packages/0a/25/d77a73ff24e2e4fcea64472f5e39b0402d836da9b08b5361a734d0153023/pydantic_core-2.41.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bf1917385ebe0f968dc5c6ab1375886d56992b93ddfe6bf52bff575d03662be", size = 2189759, upload-time = "2025-10-06T21:11:33.753Z" }, - { url = "https://files.pythonhosted.org/packages/66/45/4a4ebaaae12a740552278d06fe71418c0f2869537a369a89c0e6723b341d/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4f94f3ab188f44b9a73f7295663f3ecb8f2e2dd03a69c8f2ead50d37785ecb04", size = 2140747, upload-time = "2025-10-06T21:11:35.781Z" }, - { url = "https://files.pythonhosted.org/packages/da/6d/b727ce1022f143194a36593243ff244ed5a1eb3c9122296bf7e716aa37ba/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:3925446673641d37c30bd84a9d597e49f72eacee8b43322c8999fa17d5ae5bc4", size = 2327416, upload-time = "2025-10-06T21:11:37.75Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8c/02df9d8506c427787059f87c6c7253435c6895e12472a652d9616ee0fc95/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:49bd51cc27adb980c7b97357ae036ce9b3c4d0bb406e84fbe16fb2d368b602a8", size = 2318138, upload-time = "2025-10-06T21:11:39.463Z" }, - { url = "https://files.pythonhosted.org/packages/98/67/0cf429a7d6802536941f430e6e3243f6d4b68f41eeea4b242372f1901794/pydantic_core-2.41.1-cp314-cp314-win32.whl", hash = "sha256:a31ca0cd0e4d12ea0df0077df2d487fc3eb9d7f96bbb13c3c5b88dcc21d05159", size = 1998429, upload-time = "2025-10-06T21:11:41.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/742fef93de5d085022d2302a6317a2b34dbfe15258e9396a535c8a100ae7/pydantic_core-2.41.1-cp314-cp314-win_amd64.whl", hash = "sha256:1b5c4374a152e10a22175d7790e644fbd8ff58418890e07e2073ff9d4414efae", size = 2028870, upload-time = "2025-10-06T21:11:43.66Z" }, - { url = "https://files.pythonhosted.org/packages/31/38/cdd8ccb8555ef7720bd7715899bd6cfbe3c29198332710e1b61b8f5dd8b8/pydantic_core-2.41.1-cp314-cp314-win_arm64.whl", hash = "sha256:4fee76d757639b493eb600fba668f1e17475af34c17dd61db7a47e824d464ca9", size = 1974275, upload-time = "2025-10-06T21:11:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7e/8ac10ccb047dc0221aa2530ec3c7c05ab4656d4d4bd984ee85da7f3d5525/pydantic_core-2.41.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f9b9c968cfe5cd576fdd7361f47f27adeb120517e637d1b189eea1c3ece573f4", size = 1875124, upload-time = "2025-10-06T21:11:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e4/7d9791efeb9c7d97e7268f8d20e0da24d03438a7fa7163ab58f1073ba968/pydantic_core-2.41.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ebc7ab67b856384aba09ed74e3e977dded40e693de18a4f197c67d0d4e6d8e", size = 2043075, upload-time = "2025-10-06T21:11:49.542Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c3/3f6e6b2342ac11ac8cd5cb56e24c7b14afa27c010e82a765ffa5f771884a/pydantic_core-2.41.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8ae0dc57b62a762985bc7fbf636be3412394acc0ddb4ade07fe104230f1b9762", size = 1995341, upload-time = "2025-10-06T21:11:51.497Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, ] [[package]] name = "pydantic-extra-types" -version = "2.10.6" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/10/fb64987804cde41bcc39d9cd757cd5f2bb5d97b389d81aa70238b14b8a7e/pydantic_extra_types-2.10.6.tar.gz", hash = "sha256:c63d70bf684366e6bbe1f4ee3957952ebe6973d41e7802aea0b770d06b116aeb", size = 141858, upload-time = "2025-10-08T13:47:49.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/35/2fee58b1316a73e025728583d3b1447218a97e621933fc776fb8c0f2ebdd/pydantic_extra_types-2.11.0.tar.gz", hash = "sha256:4e9991959d045b75feb775683437a97991d02c138e00b59176571db9ce634f0e", size = 157226, upload-time = "2025-12-31T16:18:27.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/04/5c918669096da8d1c9ec7bb716bd72e755526103a61bc5e76a3e4fb23b53/pydantic_extra_types-2.10.6-py3-none-any.whl", hash = "sha256:6106c448316d30abf721b5b9fecc65e983ef2614399a24142d689c7546cc246a", size = 40949, upload-time = "2025-10-08T13:47:48.268Z" }, + { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" }, ] [[package]] name = "pydantic-settings" -version = "2.11.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] [[package]] name = "pydot" -version = "3.0.4" +version = "4.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/dd/e0e6a4fb84c22050f6a9701ad9fd6a67ef82faa7ba97b97eb6fdc6b49b34/pydot-3.0.4.tar.gz", hash = "sha256:3ce88b2558f3808b0376f22bfa6c263909e1c3981e2a7b629b65b451eee4a25d", size = 168167, upload-time = "2025-01-05T16:18:45.763Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/35/b17cb89ff865484c6a20ef46bf9d95a5f07328292578de0b295f4a6beec2/pydot-4.0.1.tar.gz", hash = "sha256:c2148f681c4a33e08bf0e26a9e5f8e4099a82e0e2a068098f32ce86577364ad5", size = 162594, upload-time = "2025-06-17T20:09:56.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/5f/1ebfd430df05c4f9e438dd3313c4456eab937d976f6ab8ce81a98f9fb381/pydot-3.0.4-py3-none-any.whl", hash = "sha256:bfa9c3fc0c44ba1d132adce131802d7df00429d1a79cc0346b0a5cd374dbe9c6", size = 35776, upload-time = "2025-01-05T16:18:42.836Z" }, + { url = "https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl", hash = "sha256:869c0efadd2708c0be1f916eb669f3d664ca684bc57ffb7ecc08e70d5e93fee6", size = 37087, upload-time = "2025-06-17T20:09:55.25Z" }, ] [[package]] @@ -1464,11 +1664,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [package.optional-dependencies] @@ -1478,11 +1678,11 @@ crypto = [ [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -1494,22 +1694,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, ] -[[package]] -name = "pyright" -version = "1.1.406" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, -] - [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1518,9 +1705,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] @@ -1539,14 +1726,14 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] @@ -1577,20 +1764,20 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] [[package]] name = "python-multipart" -version = "0.0.20" +version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, ] [[package]] @@ -1605,15 +1792,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -1650,23 +1828,97 @@ wheels = [ { 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 = "redis" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/80/2971931d27651affa88a44c0ad7b8c4a19dc29c998abb20b23868d319b59/redis-7.1.1.tar.gz", hash = "sha256:a2814b2bda15b39dad11391cc48edac4697214a8a5a4bd10abe936ab4892eb43", size = 4800064, upload-time = "2026-02-09T18:39:40.292Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/55/1de1d812ba1481fa4b37fb03b4eec0fcb71b6a0d44c04ea3482eb017600f/redis-7.1.1-py3-none-any.whl", hash = "sha256:f77817f16071c2950492c67d40b771fa493eb3fccc630a424a10976dbb794b7a", size = 356057, upload-time = "2026-02-09T18:39:38.602Z" }, +] + +[[package]] +name = "regex" +version = "2025.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669, upload-time = "2025-11-03T21:34:22.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a7/dda24ebd49da46a197436ad96378f17df30ceb40e52e859fc42cac45b850/regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c1e448051717a334891f2b9a620fe36776ebf3dd8ec46a0b877c8ae69575feb4", size = 489081, upload-time = "2025-11-03T21:31:55.9Z" }, + { url = "https://files.pythonhosted.org/packages/19/22/af2dc751aacf88089836aa088a1a11c4f21a04707eb1b0478e8e8fb32847/regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b5aca4d5dfd7fbfbfbdaf44850fcc7709a01146a797536a8f84952e940cca76", size = 291123, upload-time = "2025-11-03T21:31:57.758Z" }, + { url = "https://files.pythonhosted.org/packages/a3/88/1a3ea5672f4b0a84802ee9891b86743438e7c04eb0b8f8c4e16a42375327/regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:04d2765516395cf7dda331a244a3282c0f5ae96075f728629287dfa6f76ba70a", size = 288814, upload-time = "2025-11-03T21:32:01.12Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8c/f5987895bf42b8ddeea1b315c9fedcfe07cadee28b9c98cf50d00adcb14d/regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d9903ca42bfeec4cebedba8022a7c97ad2aab22e09573ce9976ba01b65e4361", size = 798592, upload-time = "2025-11-03T21:32:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/99/2a/6591ebeede78203fa77ee46a1c36649e02df9eaa77a033d1ccdf2fcd5d4e/regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:639431bdc89d6429f6721625e8129413980ccd62e9d3f496be618a41d205f160", size = 864122, upload-time = "2025-11-03T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/94/d6/be32a87cf28cf8ed064ff281cfbd49aefd90242a83e4b08b5a86b38e8eb4/regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f117efad42068f9715677c8523ed2be1518116d1c49b1dd17987716695181efe", size = 912272, upload-time = "2025-11-03T21:32:06.148Z" }, + { url = "https://files.pythonhosted.org/packages/62/11/9bcef2d1445665b180ac7f230406ad80671f0fc2a6ffb93493b5dd8cd64c/regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4aecb6f461316adf9f1f0f6a4a1a3d79e045f9b71ec76055a791affa3b285850", size = 803497, upload-time = "2025-11-03T21:32:08.162Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a7/da0dc273d57f560399aa16d8a68ae7f9b57679476fc7ace46501d455fe84/regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b3a5f320136873cc5561098dfab677eea139521cb9a9e8db98b7e64aef44cbc", size = 787892, upload-time = "2025-11-03T21:32:09.769Z" }, + { url = "https://files.pythonhosted.org/packages/da/4b/732a0c5a9736a0b8d6d720d4945a2f1e6f38f87f48f3173559f53e8d5d82/regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75fa6f0056e7efb1f42a1c34e58be24072cb9e61a601340cc1196ae92326a4f9", size = 858462, upload-time = "2025-11-03T21:32:11.769Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f5/a2a03df27dc4c2d0c769220f5110ba8c4084b0bfa9ab0f9b4fcfa3d2b0fc/regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:dbe6095001465294f13f1adcd3311e50dd84e5a71525f20a10bd16689c61ce0b", size = 850528, upload-time = "2025-11-03T21:32:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/d6/09/e1cd5bee3841c7f6eb37d95ca91cdee7100b8f88b81e41c2ef426910891a/regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:454d9b4ae7881afbc25015b8627c16d88a597479b9dea82b8c6e7e2e07240dc7", size = 789866, upload-time = "2025-11-03T21:32:15.748Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/702f5ea74e2a9c13d855a6a85b7f80c30f9e72a95493260193c07f3f8d74/regex-2025.11.3-cp313-cp313-win32.whl", hash = "sha256:28ba4d69171fc6e9896337d4fc63a43660002b7da53fc15ac992abcf3410917c", size = 266189, upload-time = "2025-11-03T21:32:17.493Z" }, + { url = "https://files.pythonhosted.org/packages/8b/00/6e29bb314e271a743170e53649db0fdb8e8ff0b64b4f425f5602f4eb9014/regex-2025.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5", size = 277054, upload-time = "2025-11-03T21:32:19.042Z" }, + { url = "https://files.pythonhosted.org/packages/25/f1/b156ff9f2ec9ac441710764dda95e4edaf5f36aca48246d1eea3f1fd96ec/regex-2025.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:2292cd5a90dab247f9abe892ac584cb24f0f54680c73fcb4a7493c66c2bf2467", size = 270325, upload-time = "2025-11-03T21:32:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/fd0c63357caefe5680b8ea052131acbd7f456893b69cc2a90cc3e0dc90d4/regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1eb1ebf6822b756c723e09f5186473d93236c06c579d2cc0671a722d2ab14281", size = 491984, upload-time = "2025-11-03T21:32:23.466Z" }, + { url = "https://files.pythonhosted.org/packages/df/ec/7014c15626ab46b902b3bcc4b28a7bae46d8f281fc7ea9c95e22fcaaa917/regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1e00ec2970aab10dc5db34af535f21fcf32b4a31d99e34963419636e2f85ae39", size = 292673, upload-time = "2025-11-03T21:32:25.034Z" }, + { url = "https://files.pythonhosted.org/packages/23/ab/3b952ff7239f20d05f1f99e9e20188513905f218c81d52fb5e78d2bf7634/regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a4cb042b615245d5ff9b3794f56be4138b5adc35a4166014d31d1814744148c7", size = 291029, upload-time = "2025-11-03T21:32:26.528Z" }, + { url = "https://files.pythonhosted.org/packages/21/7e/3dc2749fc684f455f162dcafb8a187b559e2614f3826877d3844a131f37b/regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44f264d4bf02f3176467d90b294d59bf1db9fe53c141ff772f27a8b456b2a9ed", size = 807437, upload-time = "2025-11-03T21:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0b/d529a85ab349c6a25d1ca783235b6e3eedf187247eab536797021f7126c6/regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7be0277469bf3bd7a34a9c57c1b6a724532a0d235cd0dc4e7f4316f982c28b19", size = 873368, upload-time = "2025-11-03T21:32:30.4Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/2d868155f8c9e3e9d8f9e10c64e9a9f496bb8f7e037a88a8bed26b435af6/regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0d31e08426ff4b5b650f68839f5af51a92a5b51abd8554a60c2fbc7c71f25d0b", size = 914921, upload-time = "2025-11-03T21:32:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/9d72ff0f354fa783fe2ba913c8734c3b433b86406117a8db4ea2bf1c7a2f/regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e43586ce5bd28f9f285a6e729466841368c4a0353f6fd08d4ce4630843d3648a", size = 812708, upload-time = "2025-11-03T21:32:34.305Z" }, + { url = "https://files.pythonhosted.org/packages/e7/19/ce4bf7f5575c97f82b6e804ffb5c4e940c62609ab2a0d9538d47a7fdf7d4/regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0f9397d561a4c16829d4e6ff75202c1c08b68a3bdbfe29dbfcdb31c9830907c6", size = 795472, upload-time = "2025-11-03T21:32:36.364Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/fd1063a176ffb7b2315f9a1b08d17b18118b28d9df163132615b835a26ee/regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:dd16e78eb18ffdb25ee33a0682d17912e8cc8a770e885aeee95020046128f1ce", size = 868341, upload-time = "2025-11-03T21:32:38.042Z" }, + { url = "https://files.pythonhosted.org/packages/12/43/103fb2e9811205e7386366501bc866a164a0430c79dd59eac886a2822950/regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:ffcca5b9efe948ba0661e9df0fa50d2bc4b097c70b9810212d6b62f05d83b2dd", size = 854666, upload-time = "2025-11-03T21:32:40.079Z" }, + { url = "https://files.pythonhosted.org/packages/7d/22/e392e53f3869b75804762c7c848bd2dd2abf2b70fb0e526f58724638bd35/regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c56b4d162ca2b43318ac671c65bd4d563e841a694ac70e1a976ac38fcf4ca1d2", size = 799473, upload-time = "2025-11-03T21:32:42.148Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f9/8bd6b656592f925b6845fcbb4d57603a3ac2fb2373344ffa1ed70aa6820a/regex-2025.11.3-cp313-cp313t-win32.whl", hash = "sha256:9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a", size = 268792, upload-time = "2025-11-03T21:32:44.13Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/0e7d603467775ff65cd2aeabf1b5b50cc1c3708556a8b849a2fa4dd1542b/regex-2025.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c", size = 280214, upload-time = "2025-11-03T21:32:45.853Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d0/2afc6f8e94e2b64bfb738a7c2b6387ac1699f09f032d363ed9447fd2bb57/regex-2025.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e", size = 271469, upload-time = "2025-11-03T21:32:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/31/e9/f6e13de7e0983837f7b6d238ad9458800a874bf37c264f7923e63409944c/regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6", size = 489089, upload-time = "2025-11-03T21:32:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5c/261f4a262f1fa65141c1b74b255988bd2fa020cc599e53b080667d591cfc/regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4", size = 291059, upload-time = "2025-11-03T21:32:51.682Z" }, + { url = "https://files.pythonhosted.org/packages/8e/57/f14eeb7f072b0e9a5a090d1712741fd8f214ec193dba773cf5410108bb7d/regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73", size = 288900, upload-time = "2025-11-03T21:32:53.569Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6b/1d650c45e99a9b327586739d926a1cd4e94666b1bd4af90428b36af66dc7/regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f", size = 799010, upload-time = "2025-11-03T21:32:55.222Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/d66dcbc6b628ce4e3f7f0cbbb84603aa2fc0ffc878babc857726b8aab2e9/regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d", size = 864893, upload-time = "2025-11-03T21:32:57.239Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2d/f238229f1caba7ac87a6c4153d79947fb0261415827ae0f77c304260c7d3/regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be", size = 911522, upload-time = "2025-11-03T21:32:59.274Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3d/22a4eaba214a917c80e04f6025d26143690f0419511e0116508e24b11c9b/regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db", size = 803272, upload-time = "2025-11-03T21:33:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/84/b1/03188f634a409353a84b5ef49754b97dbcc0c0f6fd6c8ede505a8960a0a4/regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62", size = 787958, upload-time = "2025-11-03T21:33:03.379Z" }, + { url = "https://files.pythonhosted.org/packages/99/6a/27d072f7fbf6fadd59c64d210305e1ff865cc3b78b526fd147db768c553b/regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f", size = 859289, upload-time = "2025-11-03T21:33:05.374Z" }, + { url = "https://files.pythonhosted.org/packages/9a/70/1b3878f648e0b6abe023172dacb02157e685564853cc363d9961bcccde4e/regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02", size = 850026, upload-time = "2025-11-03T21:33:07.131Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d5/68e25559b526b8baab8e66839304ede68ff6727237a47727d240006bd0ff/regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed", size = 789499, upload-time = "2025-11-03T21:33:09.141Z" }, + { url = "https://files.pythonhosted.org/packages/fc/df/43971264857140a350910d4e33df725e8c94dd9dee8d2e4729fa0d63d49e/regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4", size = 271604, upload-time = "2025-11-03T21:33:10.9Z" }, + { url = "https://files.pythonhosted.org/packages/01/6f/9711b57dc6894a55faf80a4c1b5aa4f8649805cb9c7aef46f7d27e2b9206/regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad", size = 280320, upload-time = "2025-11-03T21:33:12.572Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7e/f6eaa207d4377481f5e1775cdeb5a443b5a59b392d0065f3417d31d80f87/regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f", size = 273372, upload-time = "2025-11-03T21:33:14.219Z" }, + { url = "https://files.pythonhosted.org/packages/c3/06/49b198550ee0f5e4184271cee87ba4dfd9692c91ec55289e6282f0f86ccf/regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc", size = 491985, upload-time = "2025-11-03T21:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bf/abdafade008f0b1c9da10d934034cb670432d6cf6cbe38bbb53a1cfd6cf8/regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49", size = 292669, upload-time = "2025-11-03T21:33:18.32Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ef/0c357bb8edbd2ad8e273fcb9e1761bc37b8acbc6e1be050bebd6475f19c1/regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536", size = 291030, upload-time = "2025-11-03T21:33:20.048Z" }, + { url = "https://files.pythonhosted.org/packages/79/06/edbb67257596649b8fb088d6aeacbcb248ac195714b18a65e018bf4c0b50/regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95", size = 807674, upload-time = "2025-11-03T21:33:21.797Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d9/ad4deccfce0ea336296bd087f1a191543bb99ee1c53093dcd4c64d951d00/regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009", size = 873451, upload-time = "2025-11-03T21:33:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/13/75/a55a4724c56ef13e3e04acaab29df26582f6978c000ac9cd6810ad1f341f/regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9", size = 914980, upload-time = "2025-11-03T21:33:25.999Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/a1657ee15bd9116f70d4a530c736983eed997b361e20ecd8f5ca3759d5c5/regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d", size = 812852, upload-time = "2025-11-03T21:33:27.852Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6f/f7516dde5506a588a561d296b2d0044839de06035bb486b326065b4c101e/regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6", size = 795566, upload-time = "2025-11-03T21:33:32.364Z" }, + { url = "https://files.pythonhosted.org/packages/d9/dd/3d10b9e170cc16fb34cb2cef91513cf3df65f440b3366030631b2984a264/regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154", size = 868463, upload-time = "2025-11-03T21:33:34.459Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8e/935e6beff1695aa9085ff83195daccd72acc82c81793df480f34569330de/regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267", size = 854694, upload-time = "2025-11-03T21:33:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/92/12/10650181a040978b2f5720a6a74d44f841371a3d984c2083fc1752e4acf6/regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379", size = 799691, upload-time = "2025-11-03T21:33:39.079Z" }, + { url = "https://files.pythonhosted.org/packages/67/90/8f37138181c9a7690e7e4cb388debbd389342db3c7381d636d2875940752/regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38", size = 274583, upload-time = "2025-11-03T21:33:41.302Z" }, + { url = "https://files.pythonhosted.org/packages/8f/cd/867f5ec442d56beb56f5f854f40abcfc75e11d10b11fdb1869dd39c63aaf/regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de", size = 284286, upload-time = "2025-11-03T21:33:43.324Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/32c0c4610cbc070362bf1d2e4ea86d1ea29014d400a6d6c2486fcfd57766/regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801", size = 274741, upload-time = "2025-11-03T21:33:45.557Z" }, +] + [[package]] name = "relab-backend" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "aiosmtplib" }, { name = "asyncache" }, { name = "asyncpg" }, { name = "cachetools" }, { name = "email-validator" }, { name = "fastapi", extra = ["standard"] }, { name = "fastapi-filter" }, + { name = "fastapi-mail" }, { name = "fastapi-pagination" }, { name = "fastapi-storages" }, { name = "fastapi-users", extra = ["oauth", "sqlalchemy"] }, { name = "fastapi-users-db-sqlmodel" }, { name = "markdown" }, + { name = "mjml" }, { name = "pillow" }, { name = "psycopg", extra = ["binary"] }, { name = "pydantic" }, @@ -1674,6 +1926,7 @@ dependencies = [ { name = "pydantic-settings" }, { name = "python-dotenv" }, { name = "python-slugify" }, + { name = "redis" }, { name = "relab-rpi-cam-models" }, { name = "sqlalchemy" }, { name = "sqlmodel" }, @@ -1687,13 +1940,12 @@ api = [ { name = "google-auth" }, { name = "itsdangerous" }, { name = "markupsafe" }, - { name = "sqladmin" }, ] dev = [ { name = "alembic-autogen-check" }, { name = "paracelsus" }, - { name = "pyright" }, { name = "ruff" }, + { name = "ty" }, ] migrations = [ { name = "alembic" }, @@ -1711,28 +1963,30 @@ tests = [ [package.metadata] requires-dist = [ - { name = "aiosmtplib", specifier = ">=4.0.1" }, { name = "asyncache", specifier = ">=0.3.1" }, { name = "asyncpg", specifier = ">=0.30.0" }, { name = "cachetools", specifier = ">=5.5.2" }, { name = "email-validator", specifier = ">=2.2.0" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.115.14" }, { name = "fastapi-filter", specifier = ">=2.0.1" }, + { name = "fastapi-mail", git = "https://github.com/simonvanlierde/fastapi-mail?rev=6c6f04a7afaf3cdced82764009a2f1f2a3c3ee6c" }, { name = "fastapi-pagination", specifier = ">=0.13.2" }, { name = "fastapi-storages", specifier = ">=0.3.0" }, { name = "fastapi-users", extras = ["oauth", "sqlalchemy"], specifier = ">=14.0.1" }, { name = "fastapi-users-db-sqlmodel", git = "https://github.com/simonvanlierde/fastapi-users-db-sqlmodel?rev=7e9c4830e53ee20c38e3de80066cb19d7c3efc43" }, { name = "markdown", specifier = ">=3.8.2" }, + { name = "mjml", specifier = ">=0.11.1" }, { name = "pillow", specifier = ">=11.2.1" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" }, - { name = "pydantic", specifier = ">=2.11,<2.12" }, + { name = "pydantic", specifier = ">=2.12" }, { name = "pydantic-extra-types", specifier = ">=2.10.5" }, { name = "pydantic-settings", specifier = ">=2.10.1" }, { name = "python-dotenv", specifier = ">=1.1.1" }, { name = "python-slugify", specifier = ">=8.0.4" }, + { name = "redis", specifier = ">=5.2.1" }, { name = "relab-rpi-cam-models", specifier = ">=0.1.1" }, { name = "sqlalchemy", specifier = ">=2.0.41" }, - { name = "sqlmodel", specifier = ">=0.0.24" }, + { name = "sqlmodel", specifier = ">=0.0.27" }, { name = "tldextract", specifier = ">=5.3.0" }, ] @@ -1743,13 +1997,12 @@ api = [ { name = "google-auth", specifier = ">=2.40.3" }, { name = "itsdangerous", specifier = ">=2.2.0" }, { name = "markupsafe", specifier = ">=3.0.2" }, - { name = "sqladmin", specifier = ">=0.20.1" }, ] dev = [ { name = "alembic-autogen-check", specifier = ">=1.1.1" }, { name = "paracelsus", specifier = ">=0.9.0" }, - { name = "pyright", specifier = ">=1.1.402" }, { name = "ruff", specifier = ">=0.12.1" }, + { name = "ty", specifier = ">=0.0.15" }, ] migrations = [ { name = "alembic", specifier = ">=1.16.2" }, @@ -1795,77 +2048,94 @@ wheels = [ [[package]] name = "requests-file" -version = "2.1.0" +version = "3.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/97/bf44e6c6bd8ddbb99943baf7ba8b1a8485bcd2fe0e55e5708d7fee4ff1ae/requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658", size = 6891, upload-time = "2024-05-21T16:28:00.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/f8/5dc70102e4d337063452c82e1f0d95e39abfe67aa222ed8a5ddeb9df8de8/requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576", size = 6967, upload-time = "2025-10-20T18:56:42.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/25/dd878a121fcfdf38f52850f11c512e13ec87c2ea72385933818e5b6c15ce/requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c", size = 4244, upload-time = "2024-05-21T16:27:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d5/de8f089119205a09da657ed4784c584ede8381a0ce6821212a6d4ca47054/requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2", size = 4514, upload-time = "2025-10-20T18:56:41.184Z" }, ] [[package]] name = "rich" -version = "14.2.0" +version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] [[package]] name = "rich-toolkit" -version = "0.15.1" +version = "0.19.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/33/1a18839aaa8feef7983590c05c22c9c09d245ada6017d118325bbfcc7651/rich_toolkit-0.15.1.tar.gz", hash = "sha256:6f9630eb29f3843d19d48c3bd5706a086d36d62016687f9d0efa027ddc2dd08a", size = 115322, upload-time = "2025-09-04T09:28:11.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/c9/4bbf4bfee195ed1b7d7a6733cc523ca61dbfb4a3e3c12ea090aaffd97597/rich_toolkit-0.19.4.tar.gz", hash = "sha256:52e23d56f9dc30d1343eb3b3f6f18764c313fbfea24e52e6a1d6069bec9c18eb", size = 193951, upload-time = "2026-02-12T10:08:15.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/49/42821d55ead7b5a87c8d121edf323cb393d8579f63e933002ade900b784f/rich_toolkit-0.15.1-py3-none-any.whl", hash = "sha256:36a0b1d9a135d26776e4b78f1d5c2655da6e0ef432380b5c6b523c8d8ab97478", size = 29412, upload-time = "2025-09-04T09:28:10.587Z" }, + { url = "https://files.pythonhosted.org/packages/28/31/97d39719def09c134385bfcfbedfed255168b571e7beb3ad7765aae660ca/rich_toolkit-0.19.4-py3-none-any.whl", hash = "sha256:34ac344de8862801644be8b703e26becf44b047e687f208d7829e8f7cfc311d6", size = 32757, upload-time = "2026-02-12T10:08:15.037Z" }, ] [[package]] name = "rignore" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/46/e5ef3423a3746f91d3a3d9a68c499fde983be7dbab7d874efa8d3bb139ba/rignore-0.7.0.tar.gz", hash = "sha256:cfe6a2cbec855b440d7550d53e670246fce43ca5847e46557b6d4577c9cdb540", size = 12796, upload-time = "2025-10-02T13:26:22.194Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/85/cd1441043c5ed13e671153af260c5f328042ebfb87aa28849367602206f2/rignore-0.7.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:190e469db68112c4027a7a126facfd80ce353374ff208c585ca7dacc75de0472", size = 880474, upload-time = "2025-10-02T13:25:08.111Z" }, - { url = "https://files.pythonhosted.org/packages/f4/07/d5b9593cb05593718508308543a8fbee75998a7489cf4f4b489d2632bd4a/rignore-0.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0a43f6fabf46ed8e96fbf2861187362e513960c2a8200c35242981bd36ef8b96", size = 811882, upload-time = "2025-10-02T13:24:56.599Z" }, - { url = "https://files.pythonhosted.org/packages/aa/67/b82b2704660c280061d8bc90bc91092622309f78e20c9e3321f45f88cd4e/rignore-0.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89a59e5291805eca3c3317a55fcd2a579e9ee1184511660078a398182463deb", size = 892043, upload-time = "2025-10-02T13:23:22.326Z" }, - { url = "https://files.pythonhosted.org/packages/8b/7e/e91a1899a06882cd8a7acc3025c51b9f830971b193bd6b72e34254ed7733/rignore-0.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a155f36be847c05c800e0218e9ac04946ba44bf077e1f11dc024ca9e1f7a727", size = 865404, upload-time = "2025-10-02T13:23:40.085Z" }, - { url = "https://files.pythonhosted.org/packages/91/2c/68487538a2d2d7e0e1ca1051d143af690211314e22cbed58a245e816ebaf/rignore-0.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dba075135ac3cda5f3236b4f03f82bbcd97454a908631ad3da93aae1e7390b17", size = 1167661, upload-time = "2025-10-02T13:23:57.578Z" }, - { url = "https://files.pythonhosted.org/packages/b4/39/8498ac13fb710a1920526480f9476aaeaaaa20c522a027d07513929ba9d9/rignore-0.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8525b8c31f36dc9fbcb474ef58d654f6404b19b6110b7f5df332e58e657a4aa8", size = 936272, upload-time = "2025-10-02T13:24:13.414Z" }, - { url = "https://files.pythonhosted.org/packages/55/1a/38b92fde209931611dcff0db59bd5656a325ba58d368d4e50f1e711fdd16/rignore-0.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0428b64d8b02ad83fc0a2505ded0e9064cac97df7aa1dffc9c7558b56429912", size = 950552, upload-time = "2025-10-02T13:24:43.263Z" }, - { url = "https://files.pythonhosted.org/packages/e3/01/f59f38ae1b879309b0151b1ed0dd82880e1d3759f91bfdaa570730672308/rignore-0.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab1db960a64835ec3ed541951821bfc38f30dfbd6ebd990f7d039d0c54ff957", size = 974407, upload-time = "2025-10-02T13:24:30.618Z" }, - { url = "https://files.pythonhosted.org/packages/6e/67/de92fdc09dc1a622abb6d1b2678e940d24de2a07c60d193126eb52a7e8ea/rignore-0.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3749711b1e50fb5b28b55784e159a3b8209ecc72d01cc1511c05bc3a23b4a063", size = 1072865, upload-time = "2025-10-02T13:25:20.451Z" }, - { url = "https://files.pythonhosted.org/packages/65/bb/75fbef03cf56b0918880cb3b922da83d6546309566be60f6c6b451f7221b/rignore-0.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:57240739c786f897f89e29c05e529291ee1b477df9f6b29b774403a23a169fe2", size = 1129007, upload-time = "2025-10-02T13:25:36.837Z" }, - { url = "https://files.pythonhosted.org/packages/ec/24/4d591d45a8994fb4afaefa22e356d69948726c9ccba0cfd76c82509aedc2/rignore-0.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b70581286acd5f96ce11efd209bfe9261108586e1a948cc558fc3f58ba5bf5f", size = 1106827, upload-time = "2025-10-02T13:25:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b3/b614d54fa1f1c7621aeb20b2841cd980288ad9d7d61407fc4595d5c5f132/rignore-0.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33fb6e4cba1b798f1328e889b4bf2341894d82e3be42bb3513b4e0fe38788538", size = 1115328, upload-time = "2025-10-02T13:26:10.947Z" }, - { url = "https://files.pythonhosted.org/packages/83/22/ea0b3e30e230b2d2222e1ee18e20316c8297088f4cc6a6ea2ee6cb34f595/rignore-0.7.0-cp313-cp313-win32.whl", hash = "sha256:119f0497fb4776cddc663ee8f35085ce00758bd423221ba1e8222a816e10cf5e", size = 636896, upload-time = "2025-10-02T13:26:40.3Z" }, - { url = "https://files.pythonhosted.org/packages/79/16/f55b3db13f6fff408fde348d2a726d3b4ba06ed55dce8ff119e374ce3005/rignore-0.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:fb06e11dda689be138909f53639f0baa8d7c6be4d76ca9ec316382ccf3517469", size = 716519, upload-time = "2025-10-02T13:26:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/69/db/8c20a7b59abb21d3d20d387656b6759cd5890fa68185064fe8899f942a4b/rignore-0.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f2255821ab4bc34fa129a94535f5d0d88b164940b25d0a3b26ebd41d99f1a9f", size = 890684, upload-time = "2025-10-02T13:23:23.761Z" }, - { url = "https://files.pythonhosted.org/packages/45/a0/ae5ca63aed23f64dcd740f55ee6432037af5c09d25efaf79dc052a4a51ff/rignore-0.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b57efcbbc1510f8ce831a5e19fb1fe9dd329bb246c4e4f8a09bf1c06687b0331", size = 865174, upload-time = "2025-10-02T13:23:41.948Z" }, - { url = "https://files.pythonhosted.org/packages/ae/27/5aff661e792efbffda689f0d3fa91ea36f2e0d4bcca3b02f70ae95ea96da/rignore-0.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ead4bc2baceeccdfeb82cb70ba8f70fdb6dc1e58976f805f9d0d19b9ee915f0", size = 1165293, upload-time = "2025-10-02T13:23:59.238Z" }, - { url = "https://files.pythonhosted.org/packages/cb/df/13de7ce5ba2a58c724ef202310408729941c262179389df5e90cb9a41381/rignore-0.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f0a8996437a22df0faf2844d65ec91d41176b9d4e7357abee42baa39dc996ae", size = 936093, upload-time = "2025-10-02T13:24:15.057Z" }, - { url = "https://files.pythonhosted.org/packages/c3/63/4ea42bc454db8499906c8d075a7a0053b7fd381b85f3bcc857e68a8b8b23/rignore-0.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cb17ef4a413444fccbd57e1b4a3870f1320951b81f1b7007af9c70e1a5bc2897", size = 1071518, upload-time = "2025-10-02T13:25:22.076Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a7/7400a4343d1b5a1345a98846c6fd7768ff13890d207fce79d690c7fd7798/rignore-0.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b12b316adf6cf64f9d22bd690b2aa019a37335a1f632a0da7fb15a423cb64080", size = 1128403, upload-time = "2025-10-02T13:25:38.394Z" }, - { url = "https://files.pythonhosted.org/packages/45/8b/ce8ff27336a86bad47bbf011f8f7fb0b82b559ee4a0d6a4815ee3555ef56/rignore-0.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:dba8181d999387c17dd6cce5fd7f0009376ca8623d2d86842d034b18d83dc768", size = 1105552, upload-time = "2025-10-02T13:25:54.511Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e2/7925b564d853c7057f150a7f2f384400422ed30f7b7baf2fde5849562381/rignore-0.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04a3d4513cdd184f4f849ae8d6407a169cca543a2c4dd69bfc42e67cb0155504", size = 1114826, upload-time = "2025-10-02T13:26:12.56Z" }, - { url = "https://files.pythonhosted.org/packages/c4/34/c42ccdd81143d38d99e45b965e4040a1ef6c07a365ad205dd94b6d16c794/rignore-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:a296bc26b713aacd0f31702e7d89426ba6240abdbf01b2b18daeeaeaa782f475", size = 879718, upload-time = "2025-10-02T13:25:09.62Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ba/f522adf949d2b581a0a1e488a79577631ed6661fdc12e80d4182ed655036/rignore-0.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7f71807ed0bc1542860a8fa1615a0d93f3d5a22dde1066e9f50d7270bc60686", size = 810391, upload-time = "2025-10-02T13:24:58.144Z" }, - { url = "https://files.pythonhosted.org/packages/f2/82/935bffa4ad7d9560541daaca7ba0e4ee9b0b9a6370ab9518cf9c991087bb/rignore-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e6ff54399ddb650f4e4dc74b325766e7607967a49b868326e9687fc3642620", size = 950261, upload-time = "2025-10-02T13:24:45.121Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0e/22abda23cc6d20901262fcfea50c25ed66ca6e1a5dc610d338df4ca10407/rignore-0.7.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09dfad3ca450b3967533c6b1a2c7c0228c63c518f619ff342df5f9c3ed978b66", size = 974258, upload-time = "2025-10-02T13:24:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/ed/8d/0ba2c712723fdda62125087d00dcdad93102876d4e3fa5adbb99f0b859c3/rignore-0.7.0-cp314-cp314-win32.whl", hash = "sha256:2850718cfb1caece6b7ac19a524c7905a8d0c6627b0d0f4e81798e20b6c75078", size = 637403, upload-time = "2025-10-02T13:26:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/1c/63/0d7df1237c6353d1a85d8a0bc1797ac766c68e8bc6fbca241db74124eb61/rignore-0.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2401637dc8ab074f5e642295f8225d2572db395ae504ffc272a8d21e9fe77b2c", size = 717404, upload-time = "2025-10-02T13:26:29.936Z" }, +version = "0.7.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140, upload-time = "2025-11-05T21:41:21.968Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057, upload-time = "2025-11-05T20:42:42.741Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150, upload-time = "2025-11-05T20:42:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406, upload-time = "2025-11-05T20:40:53.854Z" }, + { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050, upload-time = "2025-11-05T20:41:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835, upload-time = "2025-11-05T20:41:24.997Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945, upload-time = "2025-11-05T20:41:40.628Z" }, + { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067, upload-time = "2025-11-05T20:42:11.09Z" }, + { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438, upload-time = "2025-11-05T20:41:55.443Z" }, + { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365, upload-time = "2025-11-05T21:40:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066, upload-time = "2025-11-05T21:40:32.771Z" }, + { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036, upload-time = "2025-11-05T21:40:49.646Z" }, + { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550, upload-time = "2025-11-05T21:41:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097, upload-time = "2025-11-05T21:41:53.201Z" }, + { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170, upload-time = "2025-11-05T21:41:38.131Z" }, + { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184, upload-time = "2025-11-05T21:41:27.396Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b9/1f5bd82b87e5550cd843ceb3768b4a8ef274eb63f29333cf2f29644b3d75/rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732", size = 882632, upload-time = "2025-11-05T20:42:44.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6b/07714a3efe4a8048864e8a5b7db311ba51b921e15268b17defaebf56d3db/rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90", size = 820760, upload-time = "2025-11-05T20:42:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0f/348c829ea2d8d596e856371b14b9092f8a5dfbb62674ec9b3f67e4939a9d/rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5", size = 899044, upload-time = "2025-11-05T20:40:55.336Z" }, + { url = "https://files.pythonhosted.org/packages/f0/30/2e1841a19b4dd23878d73edd5d82e998a83d5ed9570a89675f140ca8b2ad/rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6", size = 874144, upload-time = "2025-11-05T20:41:10.195Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bf/0ce9beb2e5f64c30e3580bef09f5829236889f01511a125f98b83169b993/rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2", size = 1168062, upload-time = "2025-11-05T20:41:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8b/571c178414eb4014969865317da8a02ce4cf5241a41676ef91a59aab24de/rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0", size = 942542, upload-time = "2025-11-05T20:41:41.838Z" }, + { url = "https://files.pythonhosted.org/packages/19/62/7a3cf601d5a45137a7e2b89d10c05b5b86499190c4b7ca5c3c47d79ee519/rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94", size = 958739, upload-time = "2025-11-05T20:42:12.463Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1f/4261f6a0d7caf2058a5cde2f5045f565ab91aa7badc972b57d19ce58b14e/rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9", size = 984138, upload-time = "2025-11-05T20:41:56.775Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bf/628dfe19c75e8ce1f45f7c248f5148b17dfa89a817f8e3552ab74c3ae812/rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7", size = 1079299, upload-time = "2025-11-05T21:40:16.639Z" }, + { url = "https://files.pythonhosted.org/packages/af/a5/be29c50f5c0c25c637ed32db8758fdf5b901a99e08b608971cda8afb293b/rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441", size = 1139618, upload-time = "2025-11-05T21:40:34.507Z" }, + { url = "https://files.pythonhosted.org/packages/2a/40/3c46cd7ce4fa05c20b525fd60f599165e820af66e66f2c371cd50644558f/rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72", size = 1117626, upload-time = "2025-11-05T21:40:51.494Z" }, + { url = "https://files.pythonhosted.org/packages/8c/b9/aea926f263b8a29a23c75c2e0d8447965eb1879d3feb53cfcf84db67ed58/rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b", size = 1128144, upload-time = "2025-11-05T21:41:09.169Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/0d6242f8d0df7f2ecbe91679fefc1f75e7cd2072cb4f497abaab3f0f8523/rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001", size = 646385, upload-time = "2025-11-05T21:41:55.105Z" }, + { url = "https://files.pythonhosted.org/packages/d5/38/c0dcd7b10064f084343d6af26fe9414e46e9619c5f3224b5272e8e5d9956/rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304", size = 725738, upload-time = "2025-11-05T21:41:39.736Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7a/290f868296c1ece914d565757ab363b04730a728b544beb567ceb3b2d96f/rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304", size = 656008, upload-time = "2025-11-05T21:41:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d2/3c74e3cd81fe8ea08a8dcd2d755c09ac2e8ad8fe409508904557b58383d3/rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8", size = 882835, upload-time = "2025-11-05T20:42:45.443Z" }, + { url = "https://files.pythonhosted.org/packages/77/61/a772a34b6b63154877433ac2d048364815b24c2dd308f76b212c408101a2/rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3", size = 820301, upload-time = "2025-11-05T20:42:29.226Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/054880b09c0b1b61d17eeb15279d8bf729c0ba52b36c3ada52fb827cbb3c/rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5", size = 897611, upload-time = "2025-11-05T20:40:56.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/40/b2d1c169f833d69931bf232600eaa3c7998ba4f9a402e43a822dad2ea9f2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986", size = 873875, upload-time = "2025-11-05T20:41:11.561Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/ca5ae93d83a1a60e44b21d87deb48b177a8db1b85e82fc8a9abb24a8986d/rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9", size = 1167245, upload-time = "2025-11-05T20:41:28.29Z" }, + { url = "https://files.pythonhosted.org/packages/a5/52/cf3dce392ba2af806cba265aad6bcd9c48bb2a6cb5eee448d3319f6e505b/rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2", size = 941750, upload-time = "2025-11-05T20:41:43.111Z" }, + { url = "https://files.pythonhosted.org/packages/ec/be/3f344c6218d779395e785091d05396dfd8b625f6aafbe502746fcd880af2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21", size = 958896, upload-time = "2025-11-05T20:42:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/d3fa71938aed7d00dcad87f0f9bcb02ad66c85d6ffc83ba31078ce53646a/rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f", size = 983992, upload-time = "2025-11-05T20:41:58.022Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/52a697158e9920705bdbd0748d59fa63e0f3233fb92e9df9a71afbead6ca/rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb", size = 1078181, upload-time = "2025-11-05T21:40:18.151Z" }, + { url = "https://files.pythonhosted.org/packages/ac/65/aa76dbcdabf3787a6f0fd61b5cc8ed1e88580590556d6c0207960d2384bb/rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013", size = 1139232, upload-time = "2025-11-05T21:40:35.966Z" }, + { url = "https://files.pythonhosted.org/packages/08/44/31b31a49b3233c6842acc1c0731aa1e7fb322a7170612acf30327f700b44/rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c", size = 1117349, upload-time = "2025-11-05T21:40:53.013Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ae/1b199a2302c19c658cf74e5ee1427605234e8c91787cfba0015f2ace145b/rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc", size = 1127702, upload-time = "2025-11-05T21:41:10.881Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d3/18210222b37e87e36357f7b300b7d98c6dd62b133771e71ae27acba83a4f/rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9", size = 647033, upload-time = "2025-11-05T21:42:00.095Z" }, + { url = "https://files.pythonhosted.org/packages/3e/87/033eebfbee3ec7d92b3bb1717d8f68c88e6fc7de54537040f3b3a405726f/rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96", size = 725647, upload-time = "2025-11-05T21:41:44.449Z" }, + { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" }, ] [[package]] @@ -1882,53 +2152,52 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57", size = 5452071, upload-time = "2025-10-07T18:21:55.763Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3", size = 12494532, upload-time = "2025-10-07T18:21:00.373Z" }, - { url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8", size = 13160768, upload-time = "2025-10-07T18:21:04.73Z" }, - { url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8", size = 12363376, upload-time = "2025-10-07T18:21:07.833Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7", size = 12608055, upload-time = "2025-10-07T18:21:10.72Z" }, - { url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7", size = 12318544, upload-time = "2025-10-07T18:21:13.741Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2", size = 14001280, upload-time = "2025-10-07T18:21:16.411Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c", size = 15027286, upload-time = "2025-10-07T18:21:19.577Z" }, - { url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e", size = 14451506, upload-time = "2025-10-07T18:21:22.779Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206", size = 13437384, upload-time = "2025-10-07T18:21:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e", size = 13447976, upload-time = "2025-10-07T18:21:28.83Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd", size = 13682850, upload-time = "2025-10-07T18:21:31.842Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d", size = 12449825, upload-time = "2025-10-07T18:21:35.074Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f", size = 12272599, upload-time = "2025-10-07T18:21:38.08Z" }, - { url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02", size = 13193828, upload-time = "2025-10-07T18:21:41.216Z" }, - { url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296", size = 13628617, upload-time = "2025-10-07T18:21:44.04Z" }, - { url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543", size = 12367872, upload-time = "2025-10-07T18:21:46.67Z" }, - { url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2", size = 13464628, upload-time = "2025-10-07T18:21:50.318Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" }, +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, ] [[package]] name = "s3transfer" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, ] [[package]] name = "sentry-sdk" -version = "2.41.0" +version = "2.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/47/aea50a61d85bc07a34e6e7145aad7bd96c5671a86a32618059bad0cbc73b/sentry_sdk-2.41.0.tar.gz", hash = "sha256:e7af3f4d7f8bac4c56fbaf95adb0d111f061cce58d5df91cfcd4e69782759b10", size = 343942, upload-time = "2025-10-09T14:12:21.132Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/58/175d0e4d93f62075a01f8aebe904b412c34a94a4517e5045d0a1d512aad0/sentry_sdk-2.41.0-py2.py3-none-any.whl", hash = "sha256:343cde6540574113d13d178d1b2093e011ac21dd55abd3a1ec7e540f0d18a5bd", size = 370606, upload-time = "2025-10-09T14:12:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, ] [[package]] @@ -1950,49 +2219,47 @@ wheels = [ ] [[package]] -name = "sniffio" -version = "1.3.1" +name = "soupsieve" +version = "2.8.3" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } 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 = "sqladmin" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "python-multipart" }, - { name = "sqlalchemy" }, - { name = "starlette" }, - { name = "wtforms" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/0c/614041e1b544e0de1f43b58f0105b3e2795b80369d5b0ff7412882d42fff/sqladmin-0.21.0.tar.gz", hash = "sha256:cb455b79eb79ef7d904680dd83817bf7750675147400b5b7cc401d04bda7ef2c", size = 1428312, upload-time = "2025-07-02T09:41:21.207Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/8d/81b2a48cc6f5479cb1148292518e3006ec8f5fbe3b0829ef165984e9d7b9/sqladmin-0.21.0-py3-none-any.whl", hash = "sha256:2b1802c49bdd3128c6452625705693cf32d5d33e7db30e63f409bd20a9c05b53", size = 1443585, upload-time = "2025-07-02T09:41:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] name = "sqlalchemy" -version = "2.0.44" +version = "2.0.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, - { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, - { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, - { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, - { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, - { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, - { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, - { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, + { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, + { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, + { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, + { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, + { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, ] [package.optional-dependencies] @@ -2002,27 +2269,27 @@ asyncio = [ [[package]] name = "sqlmodel" -version = "0.0.27" +version = "0.0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/5a/693d90866233e837d182da76082a6d4c2303f54d3aaaa5c78e1238c5d863/sqlmodel-0.0.27.tar.gz", hash = "sha256:ad1227f2014a03905aef32e21428640848ac09ff793047744a73dfdd077ff620", size = 118053, upload-time = "2025-10-08T16:39:11.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/62/22c287122598e61d07d005eec0b4eb97e6bde9a1b051bcd66c2bca846ea8/sqlmodel-0.0.33.tar.gz", hash = "sha256:b473544ed5fc2097894d89033049e569e1f138363dd3ec2ed4b6932cc9f29f5f", size = 95578, upload-time = "2026-02-11T15:23:39.504Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/92/c35e036151fe53822893979f8a13e6f235ae8191f4164a79ae60a95d66aa/sqlmodel-0.0.27-py3-none-any.whl", hash = "sha256:667fe10aa8ff5438134668228dc7d7a08306f4c5c4c7e6ad3ad68defa0e7aa49", size = 29131, upload-time = "2025-10-08T16:39:10.917Z" }, + { url = "https://files.pythonhosted.org/packages/63/39/13891bae4658133b489a4d8b6a2f193d56110e392289560f312748e796dc/sqlmodel-0.0.33-py3-none-any.whl", hash = "sha256:9045bb4d97d2ba099c5a068ee9525af2d106972dda1ff8488e187ce50556bf73", size = 27444, upload-time = "2026-02-11T15:23:38.678Z" }, ] [[package]] name = "starlette" -version = "0.48.0" +version = "0.52.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] [[package]] @@ -2036,7 +2303,7 @@ wheels = [ [[package]] name = "tldextract" -version = "5.3.0" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -2044,24 +2311,48 @@ dependencies = [ { name = "requests" }, { name = "requests-file" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/78/182641ea38e3cfd56e9c7b3c0d48a53d432eea755003aa544af96403d4ac/tldextract-5.3.0.tar.gz", hash = "sha256:b3d2b70a1594a0ecfa6967d57251527d58e00bb5a91a74387baa0d87a0678609", size = 128502, upload-time = "2025-04-22T06:19:37.491Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/7b/644fbbb49564a6cb124a8582013315a41148dba2f72209bba14a84242bf0/tldextract-5.3.1.tar.gz", hash = "sha256:a72756ca170b2510315076383ea2993478f7da6f897eef1f4a5400735d5057fb", size = 126105, upload-time = "2025-12-28T23:58:05.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/42/0e49d6d0aac449ca71952ec5bae764af009754fcb2e76a5cc097543747b3/tldextract-5.3.1-py3-none-any.whl", hash = "sha256:6bfe36d518de569c572062b788e16a659ccaceffc486d243af0484e8ecf432d9", size = 105886, upload-time = "2025-12-28T23:58:04.071Z" }, +] + +[[package]] +name = "ty" +version = "0.0.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/18/77f84d89db54ea0d1d1b09fa2f630ac4c240c8e270761cb908c06b6e735c/ty-0.0.16.tar.gz", hash = "sha256:a999b0db6aed7d6294d036ebe43301105681e0c821a19989be7c145805d7351c", size = 5129637, upload-time = "2026-02-10T20:24:16.48Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/7c/ea488ef48f2f544566947ced88541bc45fae9e0e422b2edbf165ee07da99/tldextract-5.3.0-py3-none-any.whl", hash = "sha256:f70f31d10b55c83993f55e91ecb7c5d84532a8972f22ec578ecfbe5ea2292db2", size = 107384, upload-time = "2025-04-22T06:19:36.304Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/909ebcc7f59eaf8a2c18fb54bfcf1c106f99afb3e5460058d4b46dec7b20/ty-0.0.16-py3-none-linux_armv6l.whl", hash = "sha256:6d8833b86396ed742f2b34028f51c0e98dbf010b13ae4b79d1126749dc9dab15", size = 10113870, upload-time = "2026-02-10T20:24:11.864Z" }, + { url = "https://files.pythonhosted.org/packages/c3/2c/b963204f3df2fdbf46a4a1ea4a060af9bb676e065d59c70ad0f5ae0dbae8/ty-0.0.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934c0055d3b7f1cf3c8eab78c6c127ef7f347ff00443cef69614bda6f1502377", size = 9936286, upload-time = "2026-02-10T20:24:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4d/3d78294f2ddfdded231e94453dea0e0adef212b2bd6536296039164c2a3e/ty-0.0.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b55e8e8733b416d914003cd22e831e139f034681b05afed7e951cc1a5ea1b8d4", size = 9442660, upload-time = "2026-02-10T20:24:02.704Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/ce48c0541e3b5749b0890725870769904e6b043e077d4710e5325d5cf807/ty-0.0.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feccae8f4abd6657de111353bd604f36e164844466346eb81ffee2c2b06ea0f0", size = 9934506, upload-time = "2026-02-10T20:24:35.818Z" }, + { url = "https://files.pythonhosted.org/packages/84/16/3b29de57e1ec6e56f50a4bb625ee0923edb058c5f53e29014873573a00cd/ty-0.0.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cad5e29d8765b92db5fa284940ac57149561f3f89470b363b9aab8a6ce553b0", size = 9933099, upload-time = "2026-02-10T20:24:43.003Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a1/e546995c25563d318c502b2f42af0fdbed91e1fc343708241e2076373644/ty-0.0.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86f28797c7dc06f081238270b533bf4fc8e93852f34df49fb660e0b58a5cda9a", size = 10438370, upload-time = "2026-02-10T20:24:33.44Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/22d301a4b2cce0f75ae84d07a495f87da193bcb68e096d43695a815c4708/ty-0.0.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be971a3b42bcae44d0e5787f88156ed2102ad07558c05a5ae4bfd32a99118e66", size = 10992160, upload-time = "2026-02-10T20:24:25.574Z" }, + { url = "https://files.pythonhosted.org/packages/6f/40/f1892b8c890db3f39a1bab8ec459b572de2df49e76d3cad2a9a239adcde9/ty-0.0.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c9f982b7c4250eb91af66933f436b3a2363c24b6353e94992eab6551166c8b7", size = 10717892, upload-time = "2026-02-10T20:24:05.914Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1b/caf9be8d0c738983845f503f2e92ea64b8d5fae1dd5ca98c3fca4aa7dadc/ty-0.0.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d122edf85ce7bdf6f85d19158c991d858fc835677bd31ca46319c4913043dc84", size = 10510916, upload-time = "2026-02-10T20:24:00.252Z" }, + { url = "https://files.pythonhosted.org/packages/60/ea/28980f5c7e1f4c9c44995811ea6a36f2fcb205232a6ae0f5b60b11504621/ty-0.0.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:497ebdddbb0e35c7758ded5aa4c6245e8696a69d531d5c9b0c1a28a075374241", size = 9908506, upload-time = "2026-02-10T20:24:28.133Z" }, + { url = "https://files.pythonhosted.org/packages/f7/80/8672306596349463c21644554f935ff8720679a14fd658fef658f66da944/ty-0.0.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e1e0ac0837bde634b030243aeba8499383c0487e08f22e80f5abdacb5b0bd8ce", size = 9949486, upload-time = "2026-02-10T20:24:18.62Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8a/d8747d36f30bd82ea157835f5b70d084c9bb5d52dd9491dba8a149792d6a/ty-0.0.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1216c9bcca551d9f89f47a817ebc80e88ac37683d71504e5509a6445f24fd024", size = 10145269, upload-time = "2026-02-10T20:24:38.249Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4c/753535acc7243570c259158b7df67e9c9dd7dab9a21ee110baa4cdcec45d/ty-0.0.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:221bbdd2c6ee558452c96916ab67fcc465b86967cf0482e19571d18f9c831828", size = 10608644, upload-time = "2026-02-10T20:24:40.565Z" }, + { url = "https://files.pythonhosted.org/packages/3e/05/8e8db64cf45a8b16757e907f7a3bfde8d6203e4769b11b64e28d5bdcd79a/ty-0.0.16-py3-none-win32.whl", hash = "sha256:d52c4eb786be878e7514cab637200af607216fcc5539a06d26573ea496b26512", size = 9582579, upload-time = "2026-02-10T20:24:30.406Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/45759faea132cd1b2a9ff8374e42ba03d39d076594fbb94f3e0e2c226c62/ty-0.0.16-py3-none-win_amd64.whl", hash = "sha256:f572c216aa8ecf79e86589c6e6d4bebc01f1f3cb3be765c0febd942013e1e73a", size = 10436043, upload-time = "2026-02-10T20:23:57.51Z" }, + { url = "https://files.pythonhosted.org/packages/7f/02/70a491802e7593e444137ed4e41a04c34d186eb2856f452dd76b60f2e325/ty-0.0.16-py3-none-win_arm64.whl", hash = "sha256:430eadeb1c0de0c31ef7bef9d002bdbb5f25a31e3aad546f1714d76cd8da0a87", size = 9915122, upload-time = "2026-02-10T20:24:14.285Z" }, ] [[package]] name = "typer" -version = "0.19.2" +version = "0.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "click" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/e6/44e073787aa57cd71c151f44855232feb0f748428fd5242d7366e3c4ae8b/typer-0.23.0.tar.gz", hash = "sha256:d8378833e47ada5d3d093fa20c4c63427cc4e27127f6b349a6c359463087d8cc", size = 120181, upload-time = "2026-02-11T15:22:18.637Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ed/d6fca788b51d0d4640c4bc82d0e85bad4b49809bca36bf4af01b4dcb66a7/typer-0.23.0-py3-none-any.whl", hash = "sha256:79f4bc262b6c37872091072a3cb7cb6d7d79ee98c0c658b4364bdcde3c42c913", size = 56668, upload-time = "2026-02-11T15:22:21.075Z" }, ] [[package]] @@ -2087,11 +2378,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.2" +version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] [[package]] @@ -2105,24 +2396,24 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] name = "uvicorn" -version = "0.37.0" +version = "0.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, ] [package.optional-dependencies] @@ -2138,16 +2429,28 @@ standard = [ [[package]] name = "uvloop" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, - { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, - { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, ] [[package]] @@ -2209,32 +2512,36 @@ wheels = [ [[package]] name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, -] - -[[package]] -name = "wtforms" -version = "3.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6a/c7/96d10183c3470f1836846f7b9527d6cb0b6c2226ebca40f36fa29f23de60/wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9", size = 134705, upload-time = "2024-01-06T07:52:41.075Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/19/c3232f35e24dccfad372e9f341c4f3a1166ae7c66e4e1351a9467c921cc1/wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07", size = 145961, upload-time = "2024-01-06T07:52:43.023Z" }, +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] diff --git a/codemeta.json b/codemeta.json index 9d940b3..caa27b5 100644 --- a/codemeta.json +++ b/codemeta.json @@ -42,7 +42,7 @@ ], "license": "https://spdx.org/licenses/AGPL-3.0-or-later", "name": "Reverse Engineering Lab", - "programmingLanguage": ["Python 3.13", "JavaScript"], + "programmingLanguage": ["Python 3", "JavaScript"], "softwareRequirements": "Docker", "version": "0.1.0" } diff --git a/compose.override.yml b/compose.override.yml index e6acd8b..d1b6eaf 100644 --- a/compose.override.yml +++ b/compose.override.yml @@ -15,6 +15,10 @@ services: - /opt/relab/backend/.venv # Prevent overwriting local virtual environment - /opt/relab/backend/logs # Prevent overwriting local logs + cache: + ports: + - "6379:6379" + database: ports: - "5433:5432" diff --git a/compose.prod.yml b/compose.prod.yml index 6e20e2c..12e7dd7 100644 --- a/compose.prod.yml +++ b/compose.prod.yml @@ -31,7 +31,11 @@ services: - user_uploads:/data/uploads:ro - ${BACKUP_DIR:-./backend/backups}/user_uploads:/backups - cloudflared: # Cloudflared tunnel to cml-relab.org + cache: + volumes: # Persist cache data in production + - cache_data:/data + + cloudflared: # Cloudflared tunnel service image: cloudflare/cloudflared:latest@sha256:89ee50efb1e9cb2ae30281a8a404fed95eb8f02f0a972617526f8c5b417acae2 command: tunnel --no-autoupdate run env_file: .env # Should contain TUNNEL_TOKEN variable @@ -46,7 +50,7 @@ services: environment: SCHEDULE: 0 2 * * * # Daily at 2am POSTGRES_HOST: database - POSTGRES_EXTRA_OPTS: "-Z1 --schema: public --blobs" # Compress backups, only back up public schema, include blobs + POSTGRES_EXTRA_OPTS: "--compress=zstd:3 --schema=public" # Compress backups, only back up public schema BACKUP_KEEP_DAYS: 7 BACKUP_KEEP_WEEKS: 4 BACKUP_KEEP_MONTHS: 6 @@ -59,3 +63,6 @@ services: docs: # Disable live reload for production command: ["serve", "--dev-addr=0.0.0.0:8000", "--no-livereload"] + +volumes: + cache_data: diff --git a/compose.yml b/compose.yml index 1d194ae..1975b5f 100644 --- a/compose.yml +++ b/compose.yml @@ -6,9 +6,12 @@ services: depends_on: database: condition: service_healthy + cache: + condition: service_healthy env_file: ./backend/.env environment: DATABASE_HOST: database # Point to the database service + REDIS_HOST: cache # Point to the cache service restart: unless-stopped volumes: - user_uploads:/opt/relab/backend/data/uploads @@ -24,6 +27,7 @@ services: env_file: ./backend/.env environment: DATABASE_HOST: database + REDIS_HOST: cache profiles: - migrations restart: on-failure:3 @@ -32,20 +36,28 @@ services: - user_uploads:/opt/relab/backend/data/uploads database: - image: postgres:18@sha256:41bfa2e5b168fff0847a5286694a86cff102bdc4d59719869f6b117bb30b3a24 + image: postgres:18@sha256:78f1aed6e8c0185d3c6963c8343dd018c63e8ba5ebd78c159c7c772a05e75b30 env_file: ./backend/.env healthcheck: - test: # Check if the database is ready to accept connections - ["CMD-SHELL", "pg_isready -h localhost -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] - interval: 5s - timeout: 5s - retries: 5 + test: ["CMD-SHELL", "pg_isready -h localhost -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + start_period: 5s + start_interval: 5s restart: unless-stopped volumes: - database_data:/var/lib/postgresql + cache: + image: redis:8 + command: ["sh", "-c", "redis-server --appendonly yes --save 60 1 --requirepass $${REDIS_PASSWORD}"] + env_file: ./backend/.env + healthcheck: + test: ["CMD-SHELL", "redis-cli ping"] + start_period: 5s + start_interval: 5s + restart: unless-stopped + docs: - image: squidfunk/mkdocs-material:9@sha256:980e11fed03b8e7851e579be9f34b1210f516c9f0b4da1a1457f21a460bd6628 + image: squidfunk/mkdocs-material:9@sha256:f5c556a6d30ce0c1c0df10e3c38c79bbcafdaea4b1c1be366809d0d4f6f9d57f restart: unless-stopped volumes: - ./docs:/docs diff --git a/docs/docs/architecture/datamodel.md b/docs/docs/architecture/datamodel.md index 68ac873..315ec0a 100644 --- a/docs/docs/architecture/datamodel.md +++ b/docs/docs/architecture/datamodel.md @@ -207,7 +207,7 @@ erDiagram PHYSICALPROPERTIES { integer id PK - float weight_kg + float weight_g float height_cm float width_cm float depth_cm diff --git a/docs/docs/architecture/system-design.md b/docs/docs/architecture/system-design.md index 0a2c523..c1cb781 100644 --- a/docs/docs/architecture/system-design.md +++ b/docs/docs/architecture/system-design.md @@ -7,11 +7,9 @@ The Reverse Engineering Lab platform is designed as a modular application for co ```mermaid graph TD User["User fa:fa-user"] -->|Interacts with| Frontend[Expo UI fa:fa-mobile] - SuperUser["Superuser fa:fa-user-shield"] -->|Interacts with| SQLAdmin[SQL Admin fa:fa-database] %% Core backend and DB Frontend -->|API Requests fa:fa-arrow-right| Backend[FastAPI Backend ] - SQLAdmin -->|Interfaces with fa:fa-link| Backend Backend -->|Queries fa:fa-database| PostgreSQL[(PostgreSQL )] %% Authentication @@ -36,7 +34,6 @@ graph TD style Frontend fill:#e0f7fa,stroke:#00acc1,stroke-width:2px style Backend fill:#e8f5e9,stroke:#4caf50,stroke-width:2px style PostgreSQL fill:#bbdefb,stroke:#1976d2,stroke-width:2px; - style SQLAdmin fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px style RaspberryPi fill:#f8bbd0,stroke:#e91e63,stroke-width:2px; style YouTube fill:#ffe6e6,stroke:#ff0000,stroke-width:2px; style Alembic fill:#fce4ec,stroke:#f06292,stroke-width:2px @@ -53,7 +50,6 @@ graph TD ## Technology Stack - **Backend**: [FastAPI](https://fastapi.tiangolo.com/) -- **Admin interface**: [SQLAdmin](https://github.com/aminalaee/sqladmin) - **ORM layer**: [SQLModel](https://github.com/fastapi/sqlmodel) - **Migrations**: [Alembic](https://alembic.sqlalchemy.org/en/latest/) - **Database**: [PostgreSQL](https://www.postgresql.org/) diff --git a/frontend-app/package-lock.json b/frontend-app/package-lock.json index bcce7aa..7e7203f 100644 --- a/frontend-app/package-lock.json +++ b/frontend-app/package-lock.json @@ -101,6 +101,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1482,6 +1483,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -3847,6 +3849,7 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.18.tgz", "integrity": "sha512-DZgd6860dxcq3YX7UzIXeBr6m3UgXvo9acxp5jiJyIZXdR00Br9JwVkO7e0bUeTA2d3Z8dsmtAR84Y86NnH64Q==", "license": "MIT", + "peer": true, "dependencies": { "@react-navigation/core": "^7.12.4", "escape-string-regexp": "^4.0.0", @@ -4043,6 +4046,7 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4121,6 +4125,7 @@ "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", @@ -4683,6 +4688,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5374,6 +5380,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -6630,6 +6637,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6724,6 +6732,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6843,6 +6852,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7156,6 +7166,7 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.15.tgz", "integrity": "sha512-d4OLUz/9nC+Aw00zamHANh5TZB4/YVYvSmKJAvCfLNxOY2AJeTFAvk0mU5HwICeHQBp6zHtz13DDCiMbcyVQWQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.12", @@ -7254,6 +7265,7 @@ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.9.tgz", "integrity": "sha512-sqoXHAOGDcr+M9NlXzj1tGoZyd3zxYDy215W6E0Z0n8fgBaqce9FAYQE2bu5X4G629AYig5go7U6sQz7Pjcm8A==", "license": "MIT", + "peer": true, "dependencies": { "@expo/config": "~12.0.9", "@expo/env": "~2.0.7" @@ -7278,6 +7290,7 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.9.tgz", "integrity": "sha512-xCoQbR/36qqB6tew/LQ6GWICpaBmHLhg/Loix5Rku/0ZtNaXMJv08M9o1AcrdiGTn/Xf/BnLu6DgS45cWQEHZg==", "license": "MIT", + "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -7372,6 +7385,7 @@ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.8.tgz", "integrity": "sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==", "license": "MIT", + "peer": true, "dependencies": { "expo-constants": "~18.0.8", "invariant": "^2.2.4" @@ -11187,6 +11201,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11416,6 +11431,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11435,6 +11451,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -11471,6 +11488,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.4.tgz", "integrity": "sha512-bt5bz3A/+Cv46KcjV0VQa+fo7MKxs17RCcpzjftINlen4ZDUl0I6Ut+brQ2FToa5oD0IB0xvQHfmsg2EDqsZdQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.4", @@ -11528,6 +11546,7 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", "license": "MIT", + "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -11612,6 +11631,7 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz", "integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==", "license": "MIT", + "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" @@ -11640,6 +11660,7 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -11650,6 +11671,7 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", + "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -11680,6 +11702,7 @@ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", @@ -11712,6 +11735,7 @@ "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz", "integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==", "license": "MIT", + "peer": true, "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" @@ -11726,6 +11750,7 @@ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "license": "MIT", + "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", @@ -11813,6 +11838,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13251,6 +13277,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -13457,6 +13484,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14247,6 +14275,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend-app/src/app/(auth)/new-account.tsx b/frontend-app/src/app/(auth)/new-account.tsx index e5f0ce5..6dce0bb 100644 --- a/frontend-app/src/app/(auth)/new-account.tsx +++ b/frontend-app/src/app/(auth)/new-account.tsx @@ -1,168 +1,221 @@ import { Link, useRouter } from 'expo-router'; import { useState } from 'react'; -import { View } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { Button, HelperText, IconButton, Text, TextInput } from 'react-native-paper'; -import validator from 'validator'; import { login, register } from '@/services/api/authentication'; +import { validateEmail, validatePassword, validateUsername } from '@/services/api/validation/user'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 20, + }, + welcomeText: { + marginTop: 80, + fontSize: 40, + marginLeft: 5, + }, + brandText: { + fontSize: 80, + fontWeight: 'bold', + }, + questionText: { + fontSize: 31, + marginTop: 80, + marginLeft: 5, + marginBottom: 40, + }, + inputContainer: { + flexDirection: 'column', + marginBottom: 10, + }, + inputRow: { + flexDirection: 'row', + alignItems: 'center', + }, + textInput: { + flex: 1, + marginRight: 10, + }, + helperText: { + marginTop: -8, + }, + backButton: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 8, + }, + backButtonIcon: { + margin: 0, + }, + backButtonText: { + fontSize: 13, + color: '#999', + marginLeft: 4, + }, + bottomContainer: { + position: 'absolute', + bottom: 20, + left: 20, + right: 20, + alignItems: 'center', + gap: 8, + }, + privacyText: { + fontSize: 12, + opacity: 0.7, + textAlign: 'center', + }, + privacyLink: { + fontSize: 12, + textDecorationLine: 'underline', + }, + registerButton: { + minWidth: 140, + }, +}); + +const PrivacyPolicy = () => ( + + By creating an account, you agree to our{' '} + + Privacy Policy + + +); export default function NewAccount() { - // Hooks const router = useRouter(); - // States const [section, setSection] = useState<'username' | 'email' | 'password'>('username'); - const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); + const [usernameError, setUsernameError] = useState(''); const [emailError, setEmailError] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [isRegistering, setIsRegistering] = useState(false); + + const handleUsernameChange = (input: string) => { + const trimmed = input.trim(); + setUsername(trimmed); + + const result = validateUsername(trimmed); + setUsernameError(result.error || ''); + }; + + const handleEmailChange = (input: string) => { + setEmail(input); + + const result = validateEmail(input); + setEmailError(result.error || ''); + }; + + const handlePasswordChange = (input: string) => { + setPassword(input); + + const result = validatePassword(input, username, email); + setPasswordError(result.error || ''); + }; - // Functions - const validateEmail = async (emailInput: string) => { - setEmail(emailInput); - setEmailError(''); + const createAccount = async () => { + // Final validation + const usernameResult = validateUsername(username); + if (!usernameResult.isValid) { + alert(usernameResult.error); + return; + } - if (!emailInput) { + const emailResult = validateEmail(email); + if (!emailResult.isValid) { + alert(emailResult.error); return; } - // Check if email format is valid - if (!validator.isEmail(emailInput)) { - setEmailError('Please enter a valid email address'); + const passwordResult = validatePassword(password, username, email); + if (!passwordResult.isValid) { + alert(passwordResult.error); return; } - // Check for disposable email via backend - try { - const response = await fetch( - `${process.env.EXPO_PUBLIC_API_URL}/auth/validate-email?email=${encodeURIComponent(emailInput)}`, - ); - const data = await response.json(); + setIsRegistering(true); - if (!data.isValid) { - setEmailError(data.reason || 'Please use a permanent email address'); - return; - } - } catch (error) { - console.error('Error validating email:', error); - // Continue even if the check fails - don't block the user - } - }; + const result = await register(username, email, password); - const createAccount = async () => { - const success = await register(username, email, password); - if (!success) { - alert('Account creation failed. Please try again.'); + if (!result.success) { + setIsRegistering(false); + alert(result.error || 'Account creation failed. Please try again.'); return; } + const loginSuccess = await login(email, password); + setIsRegistering(false); + if (!loginSuccess) { - alert('Login failed. Please try logging in manually.'); + alert('Account created! Please log in manually.'); router.replace('/login'); return; } + router.navigate('/products'); }; - // Render if (section === 'username') { return ( - - - {'Welcome to'} - - - {'ReLab.'} - - - {'Who are you?'} - + + Welcome to + ReLab. + Who are you? - - - setSection('email')} - /> + + + + setSection('email')} + /> + + {usernameError && ( + + {usernameError} + + )} + + + + + - ); } + if (section === 'email') { return ( - - - {'Hi'} - - - {username + '.'} - - - {'How do we reach you?'} - + + Hi + {username}. + How do we reach you? - - + + setSection('password')} /> - {emailError ? ( - + {emailError && ( + {emailError} - ) : null} + )} - + setSection('username')} - style={{ margin: 0 }} + style={styles.backButtonIcon} /> - setSection('username')} style={{ fontSize: 13, color: '#999', marginLeft: 4 }}> + setSection('username')} style={styles.backButtonText}> Edit username - + + + + ); } if (section === 'password') { return ( - - - {'Finally,'} - - - {username + '.'} - - - {'How will you log in?'} - + + Finally, + {username}. + How will you log in? - - - + + + + + + {passwordError && ( + + {passwordError} + + )} - + setSection('email')} - style={{ margin: 0 }} + style={styles.backButtonIcon} /> - setSection('email')} style={{ fontSize: 13, color: '#999', marginLeft: 4 }}> + setSection('email')} style={styles.backButtonText}> Edit email address - - - By creating an account, you agree to our{' '} - - Privacy Policy - - - - + + + ); diff --git a/frontend-app/src/app/products/[id]/camera.tsx b/frontend-app/src/app/products/[id]/camera.tsx index d0f5b9e..d049252 100644 --- a/frontend-app/src/app/products/[id]/camera.tsx +++ b/frontend-app/src/app/products/[id]/camera.tsx @@ -1,96 +1,213 @@ -// MAIN camera.tsx import AsyncStorage from '@react-native-async-storage/async-storage'; -import { CameraView } from 'expo-camera'; +import { CameraView, useCameraPermissions } from 'expo-camera'; +import * as ImagePicker from 'expo-image-picker'; import { useLocalSearchParams, useRouter } from 'expo-router'; -import { useRef, useState } from 'react'; -import { Pressable, View } from 'react-native'; +import React from 'react'; +import { Platform, StyleSheet, View } from 'react-native'; +import { Button, Text } from 'react-native-paper'; import { processImage } from '@/services/media/imageProcessing'; import { useDialog } from '@/components/common/DialogProvider'; -type searchParams = { - id: string; -}; +type searchParams = { id: string }; export default function ProductCamera() { // Hooks const router = useRouter(); const dialog = useDialog(); const { id } = useLocalSearchParams(); - const ref = useRef(null); - - // States - const [ready, setReady] = useState(false); - const [cameraReady, setCameraReady] = useState(false); - - // Callbacks - const takePicture = async () => { - // Take picture - const photo = await ref.current?.takePictureAsync(); - if (!photo) return; - - // Process the image - const processedUri = await processImage(photo, { - onError: (error) => { - dialog.alert({ - title: error.type === 'size' ? 'Image too large' : 'Processing failed', - message: error.message, + + // ImagePicker permissions (mobile + mobile web) + const [cameraStatus, requestCameraPermission] = ImagePicker.useCameraPermissions(); + const [libraryStatus, requestLibraryPermission] = ImagePicker.useMediaLibraryPermissions(); + + // expo-camera permission (desktop web webcam) + const [webCamPermission, requestWebCamPermission] = useCameraPermissions(); + const camRef = React.useRef(null); + + // Detect desktop web (mouse/trackpad pointer) + const isDesktopWeb = + Platform.OS === 'web' && typeof window !== 'undefined' && !window.matchMedia('(pointer: coarse)').matches; + + const handleImageResult = async (result: ImagePicker.ImagePickerResult) => { + console.log('ImagePicker result:', result); + if (!result.canceled && result.assets?.[0]) { + try { + const processedUri = await processImage(result.assets[0], { + onError: (error) => { + dialog.alert({ + title: error.type === 'size' ? 'Image too large' : 'Processing failed', + message: error.message, + }); + }, }); - }, - }); - if (!processedUri) { + if (processedUri) { + await handleCapturedUri(processedUri); + } else { + router.back(); + } + } catch (error) { + console.error('Failed to process image:', error); + router.back(); + } + } else { + console.log('Image picking canceled'); router.back(); - return; } + }; - // Save photo URI to AsyncStorage - await AsyncStorage.setItem('lastPhoto', processedUri); - - // Dismiss and return to product page + const handleCapturedUri = async (uri: string) => { + console.log('Captured URI:', uri); + await AsyncStorage.setItem('lastPhoto', uri); const params = { id: id, photoTaken: 'taken' }; router.dismissTo({ pathname: '/products/[id]', params: params }); }; - // Render - return ( - setReady(true)}> - {/*Only render when layouting is done to fix visual bug on Android*/} - {ready && setCameraReady(true)} />} + const ensureWebcamPermission = async () => { + if (!webCamPermission?.granted) { + const p = await requestWebCamPermission(); + if (!p.granted) { + await dialog.alert({ + title: 'Permission Required', + message: 'Camera permission is required to take photos', + }); + return false; + } + } + return true; + }; - {/*Only enable taking pictures when camera is actually ready*/} - {cameraReady && } - - ); -} + const takePhoto = async () => { + console.log('takePhoto pressed. isDesktopWeb:', isDesktopWeb); + + if (isDesktopWeb) { + // Desktop web: Capture from webcam + const ok = await ensureWebcamPermission(); + if (!ok) return; + try { + const photo = await camRef.current?.takePictureAsync(); + if (photo?.uri) { + // Process the webcam photo through the same validation + const processedUri = await processImage(photo, { + onError: (error) => { + dialog.alert({ + title: error.type === 'size' ? 'Image too large' : 'Processing failed', + message: error.message, + }); + }, + }); + + if (processedUri) { + await handleCapturedUri(processedUri); + } + } else { + console.warn('No photo URI returned from webcam'); + } + } catch (e) { + console.error('Webcam capture error:', e); + } + return; + } + + // Mobile / mobile web: Use ImagePicker camera + if (cameraStatus?.status !== 'granted') { + const permission = await requestCameraPermission(); + if (!permission.granted) { + await dialog.alert({ + title: 'Permission Required', + message: 'Camera permission is required to take photos', + }); + return; + } + } + + try { + const result = await ImagePicker.launchCameraAsync({ + allowsEditing: true, + mediaTypes: 'images', + }); + await handleImageResult(result); + } catch (error: any) { + console.error('Camera error:', error); + if (error.message?.includes('Unsupported file type')) { + await dialog.alert({ + title: 'Unsupported file', + message: 'Please select an image file.', + }); + } + } + }; + + const pickFromGallery = async () => { + console.log('pickFromGallery pressed'); + if (libraryStatus?.status !== 'granted') { + const permission = await requestLibraryPermission(); + if (!permission.granted) { + await dialog.alert({ + title: 'Permission Required', + message: 'Media library permission is required to choose photos', + }); + return; + } + } + try { + const result = await ImagePicker.launchImageLibraryAsync({ + allowsEditing: true, + mediaTypes: 'images', + }); + await handleImageResult(result); + } catch (error: any) { + console.error('Gallery picker error:', error); + if (error.message?.includes('Unsupported file type')) { + await dialog.alert({ + title: 'Unsupported file', + message: 'Please select an image file. PDFs and other documents are not supported.', + }); + } + } + }; -function CameraButton({ onPress }: { onPress: () => void }) { - // Render return ( - - {({ pressed }) => ( - - + + + Add Product Image + + + {isDesktopWeb && ( + + {webCamPermission?.granted ? ( + + ) : ( + + + Allow camera access to take a photo + + + + )} )} - + + + + + + + ); } + +const styles = StyleSheet.create({ + header: { alignItems: 'center', justifyContent: 'center', paddingVertical: 16 }, + permissionBox: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 20 }, + actions: { alignItems: 'center', justifyContent: 'center', padding: 16, gap: 8 }, + button: { marginVertical: 6, minWidth: 200 }, +}); diff --git a/frontend-app/src/app/products/[id]/index.tsx b/frontend-app/src/app/products/[id]/index.tsx index f240cac..611235e 100644 --- a/frontend-app/src/app/products/[id]/index.tsx +++ b/frontend-app/src/app/products/[id]/index.tsx @@ -1,12 +1,13 @@ import { MaterialCommunityIcons } from '@expo/vector-icons'; import { HeaderBackButton } from '@react-navigation/elements'; import { useLocalSearchParams, useNavigation, useRouter } from 'expo-router'; -import { JSX, useCallback, useEffect, useState } from 'react'; +import { JSX, useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, NativeScrollEvent, NativeSyntheticEvent, View } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'; -import { AnimatedFAB, Button, useTheme } from 'react-native-paper'; +import { AnimatedFAB, Button, Tooltip, useTheme } from 'react-native-paper'; import ProductAmountInParent from '@/components/product/ProductAmountInParent'; +import ProductCircularityProperties from '@/components/product/ProductCircularityProperties'; import ProductComponents from '@/components/product/ProductComponents'; import ProductDelete from '@/components/product/ProductDelete'; import ProductDescription from '@/components/product/ProductDescription'; @@ -15,14 +16,13 @@ import ProductMetaData from '@/components/product/ProductMetaData'; import ProductPhysicalProperties from '@/components/product/ProductPhysicalProperties'; import ProductTags from '@/components/product/ProductTags'; import ProductType from '@/components/product/ProductType'; -import ProductCircularityProperties from '@/components/product/ProductCircularityProperties'; -import ProductVideo from "@/components/product/ProductVideo"; +import ProductVideo from '@/components/product/ProductVideo'; import { useDialog } from '@/components/common/DialogProvider'; import { getProduct, newProduct } from '@/services/api/fetching'; import { deleteProduct, saveProduct } from '@/services/api/saving'; -import { getProductNameHelperText, isProductValid, isValidProductName } from '@/services/api/validation/product'; +import { getProductNameHelperText, validateProduct, validateProductName } from '@/services/api/validation/product'; import { Product } from '@/types/Product'; /** @@ -50,9 +50,13 @@ export default function ProductPage(): JSX.Element { const [editMode, setEditMode] = useState(id === 'new' || false); const [savingState, setSavingState] = useState<'saving' | 'success' | undefined>(undefined); const [fabExtended, setFabExtended] = useState(true); + const [tooltipVisible, setTooltipVisible] = useState(false); const isProductComponent = typeof product.parentID === 'number' && !isNaN(product.parentID); + // Validate product on every change + const validationResult = useMemo(() => validateProduct(product), [product]); + // Callbacks const onProductNameChange = useCallback( (newName: string) => { @@ -256,15 +260,18 @@ export default function ProductPage(): JSX.Element { - + + setTooltipVisible(true)} + style={{ position: 'absolute', right: 0, bottom: 0, overflow: 'hidden', margin: 19 }} + disabled={!validationResult.isValid} + extended={fabExtended} + label={editMode ? 'Save Product' : 'Edit Product'} + visible={product.ownedBy === 'me'} + /> + ); } @@ -291,9 +298,19 @@ function EditNameButton({ { text: 'Cancel', onPress: () => undefined }, { text: 'OK', - disabled: (value) => !isValidProductName(value), + disabled: (value) => { + const result = validateProductName(value); + return !result.isValid; + }, onPress: (newName) => { const name = typeof newName === 'string' ? newName.trim() : ''; + const result = validateProductName(name); + + if (!result.isValid) { + alert(result.error); + return; + } + onProductNameChange?.(name); }, }, diff --git a/frontend-app/src/assets/data/demo.json b/frontend-app/src/assets/data/demo.json deleted file mode 100644 index 3289842..0000000 --- a/frontend-app/src/assets/data/demo.json +++ /dev/null @@ -1,50791 +0,0 @@ -{ - "products": [ - { - "id": 1, - "name": "SmartToast Pro 2000", - "description": "Advanced 4-slice toaster with smart temperature control and multiple browning settings", - "brand": "SmartToast", - "model": "pr-2000-3111", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.8, - "unit": "kg" - }, - { - "property_name": "Width", - "value": 35, - "unit": "cm" - }, - { - "property_name": "Height", - "value": 22, - "unit": "cm" - }, - { - "property_name": "Depth", - "value": 18, - "unit": "cm" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 45, - "mass": 1.26, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 35, - "mass": 0.98, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 15, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 5, - "mass": 0.14, - "unit": "kg" - } - ], - "components": [ - { - "id": 101, - "name": "Heating Element", - "description": "Nichrome wire heating element" - }, - { - "id": 102, - "name": "Control Panel", - "description": "Digital display with touch controls" - }, - { - "id": 103, - "name": "Spring Mechanism", - "description": "Pop-up mechanism for toast ejection" - }, - { - "id": 104, - "name": "Crumb Tray", - "description": "Removable stainless steel crumb collection tray" - } - ], - "images": [ - { - "id": 1, - "url": "https://via.placeholder.com/400x300/0066cc/ffffff?text=SmartToast+Pro+2000", - "description": "Front view of toaster" - } - ], - "created_at": "2024-01-15T10:30:00Z", - "updated_at": "2024-06-15T14:22:00Z" - }, - { - "id": 2, - "name": "EcoBoil Electric Kettle", - "description": "Energy-efficient 1.7L electric kettle with temperature control and auto shut-off", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.2, - "unit": "kg" - }, - { - "property_name": "Capacity", - "value": 1.7, - "unit": "L" - }, - { - "property_name": "Height", - "value": 24, - "unit": "cm" - }, - { - "property_name": "Base Diameter", - "value": 20, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2200, - "unit": "W" - }, - { - "property_name": "Boiling Time", - "value": 3.5, - "unit": "min" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 60, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 25, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 10, - "mass": 0.12, - "unit": "kg" - }, - { - "material": { - "id": 8, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 5, - "mass": 0.06, - "unit": "kg" - } - ], - "components": [ - { - "id": 201, - "name": "Heating Base", - "description": "Electric heating element with power cord" - }, - { - "id": 202, - "name": "Glass Body", - "description": "Borosilicate glass water container" - }, - { - "id": 203, - "name": "Temperature Sensor", - "description": "Auto shut-off temperature control" - }, - { - "id": 204, - "name": "Handle Assembly", - "description": "Ergonomic heat-resistant handle" - }, - { - "id": 205, - "name": "Lid Mechanism", - "description": "Spring-loaded lid with steam vent" - } - ], - "images": [ - { - "id": 2, - "url": "https://via.placeholder.com/400x300/009966/ffffff?text=EcoBoil+Electric+Kettle", - "description": "Side view of electric kettle" - } - ], - "created_at": "2024-02-20T09:15:00Z", - "updated_at": "2024-06-10T11:45:00Z" - }, - { - "id": 3, - "name": "ZenGear Iron 607C", - "description": "ZenGear Iron 607C is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.62, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1332, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 56.3, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 43.7, - "mass": 1.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 31, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 32, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 33, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 3, - "url": "https://via.placeholder.com/400x300/0da203/ffffff?text=ZenGear+Iron+607C", - "description": "Iron product image" - } - ], - "created_at": "2025-02-09T09:16:11.448062Z", - "updated_at": "2025-05-01T09:16:11.448062Z" - }, - { - "id": 4, - "name": "EcoTech Kettle 101P", - "description": "EcoTech Kettle 101P is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2176, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 38.1, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 39.7, - "mass": 1.18, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 22.2, - "mass": 0.23, - "unit": "kg" - } - ], - "components": [ - { - "id": 41, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 42, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 4, - "url": "https://via.placeholder.com/400x300/069a50/ffffff?text=EcoTech+Kettle+101P", - "description": "Kettle product image" - } - ], - "created_at": "2024-12-01T09:16:11.448126Z", - "updated_at": "2025-07-03T09:16:11.448126Z" - }, - { - "id": 5, - "name": "CleanWave Blender 604Z", - "description": "CleanWave Blender 604Z is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.68, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1210, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 38.6, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.9, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 31.5, - "mass": 0.47, - "unit": "kg" - } - ], - "components": [ - { - "id": 51, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 52, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 53, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 54, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 5, - "url": "https://via.placeholder.com/400x300/072c44/ffffff?text=CleanWave+Blender+604Z", - "description": "Blender product image" - } - ], - "created_at": "2025-04-01T09:16:11.448191Z", - "updated_at": "2025-07-19T09:16:11.448191Z" - }, - { - "id": 6, - "name": "AquaPro Monitor 996S", - "description": "AquaPro Monitor 996S is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1520, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 8.9, - "mass": 0.11, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 42.3, - "mass": 1.68, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 48.8, - "mass": 0.8, - "unit": "kg" - } - ], - "components": [ - { - "id": 61, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 62, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 6, - "url": "https://via.placeholder.com/400x300/0d7971/ffffff?text=AquaPro+Monitor+996S", - "description": "Monitor product image" - } - ], - "created_at": "2024-09-08T09:16:11.448244Z", - "updated_at": "2025-06-13T09:16:11.448244Z" - }, - { - "id": 7, - "name": "PureLife Iron 148J", - "description": "PureLife Iron 148J is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.55, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2134, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 14.3, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 19.3, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 42.9, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.5, - "mass": 0.53, - "unit": "kg" - } - ], - "components": [ - { - "id": 71, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 72, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 73, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 7, - "url": "https://via.placeholder.com/400x300/06f447/ffffff?text=PureLife+Iron+148J", - "description": "Iron product image" - } - ], - "created_at": "2024-06-17T09:16:11.448297Z", - "updated_at": "2025-05-30T09:16:11.448297Z" - }, - { - "id": 8, - "name": "SmartHome Toaster 171W", - "description": "SmartHome Toaster 171W is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.24, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 831, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 71.2, - "mass": 2.77, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.7, - "mass": 0.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 81, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 82, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 8, - "url": "https://via.placeholder.com/400x300/0c50a7/ffffff?text=SmartHome+Toaster+171W", - "description": "Toaster product image" - } - ], - "created_at": "2024-12-19T09:16:11.448351Z", - "updated_at": "2025-06-08T09:16:11.448351Z" - }, - { - "id": 9, - "name": "ZenGear Kettle 987Y", - "description": "ZenGear Kettle 987Y is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.28, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 808, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 38.1, - "mass": 1.34, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 23.9, - "mass": 0.96, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 38.1, - "mass": 0.74, - "unit": "kg" - } - ], - "components": [ - { - "id": 91, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 92, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 93, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 94, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 95, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 9, - "url": "https://via.placeholder.com/400x300/0c41dd/ffffff?text=ZenGear+Kettle+987Y", - "description": "Kettle product image" - } - ], - "created_at": "2024-12-07T09:16:11.448425Z", - "updated_at": "2025-05-27T09:16:11.448425Z" - }, - { - "id": 10, - "name": "EcoTech Rice Cooker 218T", - "description": "EcoTech Rice Cooker 218T is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.57, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1245, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 55.6, - "mass": 2.02, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 44.4, - "mass": 1.19, - "unit": "kg" - } - ], - "components": [ - { - "id": 101, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 102, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 10, - "url": "https://via.placeholder.com/400x300/0265e0/ffffff?text=EcoTech+Rice+Cooker+218T", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-09-23T09:16:11.448490Z", - "updated_at": "2025-06-30T09:16:11.448490Z" - }, - { - "id": 11, - "name": "EcoTech Coffee Maker 433D", - "description": "EcoTech Coffee Maker 433D is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.83, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1320, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 37.3, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 44.9, - "mass": 1.13, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 17.8, - "mass": 0.4, - "unit": "kg" - } - ], - "components": [ - { - "id": 111, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 112, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 11, - "url": "https://via.placeholder.com/400x300/0dbfa2/ffffff?text=EcoTech+Coffee+Maker+433D", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-03-12T09:16:11.448541Z", - "updated_at": "2025-04-27T09:16:11.448541Z" - }, - { - "id": 12, - "name": "CleanWave Toaster 161K", - "description": "CleanWave Toaster 161K is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.3, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1429, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 22.8, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 56.5, - "mass": 1.12, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 20.7, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 121, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 122, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 123, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 12, - "url": "https://via.placeholder.com/400x300/02c151/ffffff?text=CleanWave+Toaster+161K", - "description": "Toaster product image" - } - ], - "created_at": "2024-05-15T09:16:11.448588Z", - "updated_at": "2025-07-14T09:16:11.448588Z" - }, - { - "id": 13, - "name": "ChefMate Monitor 267O", - "description": "ChefMate Monitor 267O is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.71, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 893, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 17.2, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 19.4, - "mass": 0.55, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 22.4, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 41.0, - "mass": 0.68, - "unit": "kg" - } - ], - "components": [ - { - "id": 131, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 132, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 13, - "url": "https://via.placeholder.com/400x300/03dc20/ffffff?text=ChefMate+Monitor+267O", - "description": "Monitor product image" - } - ], - "created_at": "2024-12-15T09:16:11.448644Z", - "updated_at": "2025-06-24T09:16:11.448644Z" - }, - { - "id": 14, - "name": "SmartHome Toaster 457E", - "description": "SmartHome Toaster 457E is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2091, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 16.7, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 83.3, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 141, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 142, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 143, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 14, - "url": "https://via.placeholder.com/400x300/050341/ffffff?text=SmartHome+Toaster+457E", - "description": "Toaster product image" - } - ], - "created_at": "2024-06-19T09:16:11.448693Z", - "updated_at": "2025-07-03T09:16:11.448693Z" - }, - { - "id": 15, - "name": "SmartHome Humidifier 945H", - "description": "SmartHome Humidifier 945H is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 895, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 32.3, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 13.5, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 15.8, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.3, - "mass": 1.29, - "unit": "kg" - } - ], - "components": [ - { - "id": 151, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 152, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 153, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 15, - "url": "https://via.placeholder.com/400x300/0a70db/ffffff?text=SmartHome+Humidifier+945H", - "description": "Humidifier product image" - } - ], - "created_at": "2024-08-15T09:16:11.448751Z", - "updated_at": "2025-07-06T09:16:11.448751Z" - }, - { - "id": 16, - "name": "CleanWave Monitor 211U", - "description": "CleanWave Monitor 211U is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.35, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1361, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 20.2, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 26.4, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 35.6, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 17.8, - "mass": 0.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 161, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 162, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 163, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 164, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 16, - "url": "https://via.placeholder.com/400x300/0294b4/ffffff?text=CleanWave+Monitor+211U", - "description": "Monitor product image" - } - ], - "created_at": "2024-06-16T09:16:11.448814Z", - "updated_at": "2025-05-02T09:16:11.448814Z" - }, - { - "id": 17, - "name": "PureLife Iron 216C", - "description": "PureLife Iron 216C is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.34, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1146, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 25.5, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 15.3, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 36.3, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 22.9, - "mass": 0.6, - "unit": "kg" - } - ], - "components": [ - { - "id": 171, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 172, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 17, - "url": "https://via.placeholder.com/400x300/0e8e27/ffffff?text=PureLife+Iron+216C", - "description": "Iron product image" - } - ], - "created_at": "2024-11-27T09:16:11.448862Z", - "updated_at": "2025-06-09T09:16:11.448862Z" - }, - { - "id": 18, - "name": "CleanWave Coffee Maker 800U", - "description": "CleanWave Coffee Maker 800U is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.19, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2100, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 29.1, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.3, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 48.5, - "mass": 1.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 181, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 182, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 183, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 18, - "url": "https://via.placeholder.com/400x300/0a7c26/ffffff?text=CleanWave+Coffee+Maker+800U", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-09-11T09:16:11.448908Z", - "updated_at": "2025-07-13T09:16:11.448908Z" - }, - { - "id": 19, - "name": "ZenGear Rice Cooker 172F", - "description": "ZenGear Rice Cooker 172F is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2192, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 54.3, - "mass": 1.82, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 45.7, - "mass": 1.31, - "unit": "kg" - } - ], - "components": [ - { - "id": 191, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 192, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 193, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 19, - "url": "https://via.placeholder.com/400x300/036ad5/ffffff?text=ZenGear+Rice+Cooker+172F", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-11-24T09:16:11.448939Z", - "updated_at": "2025-07-08T09:16:11.448939Z" - }, - { - "id": 20, - "name": "ZenGear Iron 414Y", - "description": "ZenGear Iron 414Y is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.81, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1082, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.4, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 36.9, - "mass": 1.34, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 46.7, - "mass": 1.26, - "unit": "kg" - } - ], - "components": [ - { - "id": 201, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 202, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 20, - "url": "https://via.placeholder.com/400x300/0cd673/ffffff?text=ZenGear+Iron+414Y", - "description": "Iron product image" - } - ], - "created_at": "2025-01-06T09:16:11.448987Z", - "updated_at": "2025-05-06T09:16:11.448987Z" - }, - { - "id": 21, - "name": "PureLife Rice Cooker 449F", - "description": "PureLife Rice Cooker 449F is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.95, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2053, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 52.8, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 47.2, - "mass": 1.59, - "unit": "kg" - } - ], - "components": [ - { - "id": 211, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 212, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 213, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 21, - "url": "https://via.placeholder.com/400x300/04576b/ffffff?text=PureLife+Rice+Cooker+449F", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-09-10T09:16:11.449032Z", - "updated_at": "2025-05-29T09:16:11.449032Z" - }, - { - "id": 22, - "name": "AquaPro Blender 361J", - "description": "AquaPro Blender 361J is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1695, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 35.8, - "mass": 1.16, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 38.7, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.5, - "mass": 0.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 221, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 222, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 223, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 224, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 22, - "url": "https://via.placeholder.com/400x300/068aab/ffffff?text=AquaPro+Blender+361J", - "description": "Blender product image" - } - ], - "created_at": "2025-03-17T09:16:11.449086Z", - "updated_at": "2025-06-28T09:16:11.449086Z" - }, - { - "id": 23, - "name": "SmartHome Kettle 600E", - "description": "SmartHome Kettle 600E is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 887, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.3, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 58.7, - "mass": 2.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 231, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 232, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 233, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 234, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 23, - "url": "https://via.placeholder.com/400x300/03810c/ffffff?text=SmartHome+Kettle+600E", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-24T09:16:11.449263Z", - "updated_at": "2025-06-30T09:16:11.449263Z" - }, - { - "id": 24, - "name": "AquaPro Toaster 875K", - "description": "AquaPro Toaster 875K is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.02, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2144, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 16.6, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 27.8, - "mass": 1.04, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 29.4, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 26.2, - "mass": 0.88, - "unit": "kg" - } - ], - "components": [ - { - "id": 241, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 242, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 243, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 24, - "url": "https://via.placeholder.com/400x300/0639a7/ffffff?text=AquaPro+Toaster+875K", - "description": "Toaster product image" - } - ], - "created_at": "2025-02-11T09:16:11.449344Z", - "updated_at": "2025-05-02T09:16:11.449344Z" - }, - { - "id": 25, - "name": "EcoTech Toaster 888I", - "description": "EcoTech Toaster 888I is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.71, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1052, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 31.4, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 54.3, - "mass": 2.13, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 14.3, - "mass": 0.38, - "unit": "kg" - } - ], - "components": [ - { - "id": 251, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 252, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 253, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 254, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 255, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 25, - "url": "https://via.placeholder.com/400x300/0f345d/ffffff?text=EcoTech+Toaster+888I", - "description": "Toaster product image" - } - ], - "created_at": "2024-11-15T09:16:11.449404Z", - "updated_at": "2025-05-16T09:16:11.449404Z" - }, - { - "id": 26, - "name": "NeoCook Humidifier 796I", - "description": "NeoCook Humidifier 796I is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.58, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 824, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 70.3, - "mass": 2.54, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 29.7, - "mass": 0.86, - "unit": "kg" - } - ], - "components": [ - { - "id": 261, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 262, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 263, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 264, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 26, - "url": "https://via.placeholder.com/400x300/0c9cb6/ffffff?text=NeoCook+Humidifier+796I", - "description": "Humidifier product image" - } - ], - "created_at": "2024-04-09T09:16:11.449463Z", - "updated_at": "2025-06-23T09:16:11.449463Z" - }, - { - "id": 27, - "name": "SmartHome Rice Cooker 876N", - "description": "SmartHome Rice Cooker 876N is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.31, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2037, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 48.9, - "mass": 1.66, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 51.1, - "mass": 0.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 271, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 272, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 27, - "url": "https://via.placeholder.com/400x300/0d4b5a/ffffff?text=SmartHome+Rice+Cooker+876N", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-04-15T09:16:11.449525Z", - "updated_at": "2025-06-12T09:16:11.449525Z" - }, - { - "id": 28, - "name": "PureLife Fan 661Y", - "description": "PureLife Fan 661Y is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.98, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1543, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 42.2, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 57.8, - "mass": 1.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 281, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 282, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 28, - "url": "https://via.placeholder.com/400x300/06f71a/ffffff?text=PureLife+Fan+661Y", - "description": "Fan product image" - } - ], - "created_at": "2024-11-06T09:16:11.449580Z", - "updated_at": "2025-05-10T09:16:11.449580Z" - }, - { - "id": 29, - "name": "PureLife Kettle 359P", - "description": "PureLife Kettle 359P is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1849, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 59.2, - "mass": 1.76, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 40.8, - "mass": 1.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 291, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 292, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 293, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 294, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 29, - "url": "https://via.placeholder.com/400x300/023dff/ffffff?text=PureLife+Kettle+359P", - "description": "Kettle product image" - } - ], - "created_at": "2025-02-27T09:16:11.449635Z", - "updated_at": "2025-07-11T09:16:11.449635Z" - }, - { - "id": 30, - "name": "ChefMate Humidifier 229A", - "description": "ChefMate Humidifier 229A is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.29, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1712, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 13.8, - "mass": 0.18, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 51.7, - "mass": 1.38, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 34.5, - "mass": 1.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 301, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 302, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 303, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 304, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 30, - "url": "https://via.placeholder.com/400x300/03af0e/ffffff?text=ChefMate+Humidifier+229A", - "description": "Humidifier product image" - } - ], - "created_at": "2024-06-22T09:16:11.449692Z", - "updated_at": "2025-07-26T09:16:11.449692Z" - }, - { - "id": 31, - "name": "PureLife Air Purifier 405S", - "description": "PureLife Air Purifier 405S is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1108, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 22.6, - "mass": 0.24, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 24.8, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 43.8, - "mass": 1.15, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 8.8, - "mass": 0.19, - "unit": "kg" - } - ], - "components": [ - { - "id": 311, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 312, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 313, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 31, - "url": "https://via.placeholder.com/400x300/097300/ffffff?text=PureLife+Air+Purifier+405S", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-06-15T09:16:11.449731Z", - "updated_at": "2025-06-11T09:16:11.449731Z" - }, - { - "id": 32, - "name": "PureLife Blender 476H", - "description": "PureLife Blender 476H is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1712, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 62.1, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 37.9, - "mass": 0.74, - "unit": "kg" - } - ], - "components": [ - { - "id": 321, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 322, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 32, - "url": "https://via.placeholder.com/400x300/06c4a0/ffffff?text=PureLife+Blender+476H", - "description": "Blender product image" - } - ], - "created_at": "2025-01-20T09:16:11.449760Z", - "updated_at": "2025-05-28T09:16:11.449760Z" - }, - { - "id": 33, - "name": "ChefMate Iron 478S", - "description": "ChefMate Iron 478S is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.29, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1948, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.1, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 55.2, - "mass": 2.06, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 26.7, - "mass": 0.88, - "unit": "kg" - } - ], - "components": [ - { - "id": 331, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 332, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 33, - "url": "https://via.placeholder.com/400x300/0b8f9f/ffffff?text=ChefMate+Iron+478S", - "description": "Iron product image" - } - ], - "created_at": "2024-09-17T09:16:11.449791Z", - "updated_at": "2025-04-30T09:16:11.449791Z" - }, - { - "id": 34, - "name": "PureLife Coffee Maker 338V", - "description": "PureLife Coffee Maker 338V is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 944, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 41.9, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 58.1, - "mass": 2.0, - "unit": "kg" - } - ], - "components": [ - { - "id": 341, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 342, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 343, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 34, - "url": "https://via.placeholder.com/400x300/048f56/ffffff?text=PureLife+Coffee+Maker+338V", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-02-10T09:16:11.449820Z", - "updated_at": "2025-07-30T09:16:11.449820Z" - }, - { - "id": 35, - "name": "AquaPro Humidifier 442U", - "description": "AquaPro Humidifier 442U is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 933, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 57.7, - "mass": 2.22, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 42.3, - "mass": 1.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 351, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 352, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 353, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 354, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 355, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 35, - "url": "https://via.placeholder.com/400x300/0e23c6/ffffff?text=AquaPro+Humidifier+442U", - "description": "Humidifier product image" - } - ], - "created_at": "2025-03-03T09:16:11.449869Z", - "updated_at": "2025-05-23T09:16:11.449869Z" - }, - { - "id": 36, - "name": "PureLife Iron 423Q", - "description": "PureLife Iron 423Q is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 978, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.3, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 18.8, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.3, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 27.5, - "mass": 0.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 361, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 362, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 363, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 364, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 365, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 36, - "url": "https://via.placeholder.com/400x300/04726a/ffffff?text=PureLife+Iron+423Q", - "description": "Iron product image" - } - ], - "created_at": "2024-03-27T09:16:11.449941Z", - "updated_at": "2025-05-18T09:16:11.449941Z" - }, - { - "id": 37, - "name": "PureLife Rice Cooker 677F", - "description": "PureLife Rice Cooker 677F is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.91, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1711, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.4, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 70.6, - "mass": 1.53, - "unit": "kg" - } - ], - "components": [ - { - "id": 371, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 372, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 373, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 37, - "url": "https://via.placeholder.com/400x300/030e25/ffffff?text=PureLife+Rice+Cooker+677F", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-06-05T09:16:11.449994Z", - "updated_at": "2025-05-25T09:16:11.449994Z" - }, - { - "id": 38, - "name": "EcoTech Monitor 581K", - "description": "EcoTech Monitor 581K is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.46, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1585, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 26.1, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 32.7, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 26.8, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 14.4, - "mass": 0.4, - "unit": "kg" - } - ], - "components": [ - { - "id": 381, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 382, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 383, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 384, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 38, - "url": "https://via.placeholder.com/400x300/0332f4/ffffff?text=EcoTech+Monitor+581K", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-28T09:16:11.450054Z", - "updated_at": "2025-05-24T09:16:11.450054Z" - }, - { - "id": 39, - "name": "NeoCook Iron 319T", - "description": "NeoCook Iron 319T is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.4, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1786, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 78.1, - "mass": 2.58, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 21.9, - "mass": 0.66, - "unit": "kg" - } - ], - "components": [ - { - "id": 391, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 392, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 39, - "url": "https://via.placeholder.com/400x300/0774ba/ffffff?text=NeoCook+Iron+319T", - "description": "Iron product image" - } - ], - "created_at": "2025-02-19T09:16:11.450107Z", - "updated_at": "2025-07-03T09:16:11.450107Z" - }, - { - "id": 40, - "name": "EcoTech Air Purifier 835Q", - "description": "EcoTech Air Purifier 835Q is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1201, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 43.1, - "mass": 1.2, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 20.3, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 8.9, - "mass": 0.1, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 27.6, - "mass": 0.62, - "unit": "kg" - } - ], - "components": [ - { - "id": 401, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 402, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 40, - "url": "https://via.placeholder.com/400x300/07d9b6/ffffff?text=EcoTech+Air+Purifier+835Q", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-03-27T09:16:11.450162Z", - "updated_at": "2025-07-18T09:16:11.450162Z" - }, - { - "id": 41, - "name": "CleanWave Iron 565H", - "description": "CleanWave Iron 565H is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1409, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 20.6, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 44.1, - "mass": 1.71, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 35.3, - "mass": 0.61, - "unit": "kg" - } - ], - "components": [ - { - "id": 411, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 412, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 41, - "url": "https://via.placeholder.com/400x300/0ddb85/ffffff?text=CleanWave+Iron+565H", - "description": "Iron product image" - } - ], - "created_at": "2024-11-20T09:16:11.450211Z", - "updated_at": "2025-06-07T09:16:11.450211Z" - }, - { - "id": 42, - "name": "PureLife Blender 974S", - "description": "PureLife Blender 974S is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.79, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1410, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 54.1, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 45.9, - "mass": 0.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 421, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 422, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 423, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 424, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 425, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 42, - "url": "https://via.placeholder.com/400x300/069e57/ffffff?text=PureLife+Blender+974S", - "description": "Blender product image" - } - ], - "created_at": "2024-10-27T09:16:11.450262Z", - "updated_at": "2025-05-19T09:16:11.450262Z" - }, - { - "id": 43, - "name": "PureLife Fan 944U", - "description": "PureLife Fan 944U is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.3, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1062, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.7, - "mass": 1.01, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 14.7, - "mass": 0.24, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.8, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.8, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 431, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 432, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 433, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 434, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 435, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 43, - "url": "https://via.placeholder.com/400x300/0b9486/ffffff?text=PureLife+Fan+944U", - "description": "Fan product image" - } - ], - "created_at": "2024-06-28T09:16:11.450318Z", - "updated_at": "2025-06-19T09:16:11.450318Z" - }, - { - "id": 44, - "name": "SmartHome Monitor 264C", - "description": "SmartHome Monitor 264C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.55, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 926, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 31.0, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.0, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 19.0, - "mass": 0.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 441, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 442, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 443, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 444, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 445, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 44, - "url": "https://via.placeholder.com/400x300/0e4812/ffffff?text=SmartHome+Monitor+264C", - "description": "Monitor product image" - } - ], - "created_at": "2024-10-04T09:16:11.450374Z", - "updated_at": "2025-06-21T09:16:11.450374Z" - }, - { - "id": 45, - "name": "AquaPro Toaster 959U", - "description": "AquaPro Toaster 959U is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.2, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1487, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 47.3, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 52.7, - "mass": 0.62, - "unit": "kg" - } - ], - "components": [ - { - "id": 451, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 452, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 45, - "url": "https://via.placeholder.com/400x300/0b2990/ffffff?text=AquaPro+Toaster+959U", - "description": "Toaster product image" - } - ], - "created_at": "2024-06-07T09:16:11.450426Z", - "updated_at": "2025-06-16T09:16:11.450426Z" - }, - { - "id": 46, - "name": "NeoCook Monitor 646J", - "description": "NeoCook Monitor 646J is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1709, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 32.1, - "mass": 0.93, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 46.2, - "mass": 1.5, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 21.7, - "mass": 0.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 461, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 462, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 463, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 464, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 465, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 46, - "url": "https://via.placeholder.com/400x300/08046c/ffffff?text=NeoCook+Monitor+646J", - "description": "Monitor product image" - } - ], - "created_at": "2024-04-17T09:16:11.450510Z", - "updated_at": "2025-05-11T09:16:11.450510Z" - }, - { - "id": 47, - "name": "ZenGear Fan 714T", - "description": "ZenGear Fan 714T is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.74, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 969, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 62.9, - "mass": 2.39, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 18.6, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.6, - "mass": 0.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 471, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 472, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 473, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 474, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 475, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 47, - "url": "https://via.placeholder.com/400x300/0c6ad3/ffffff?text=ZenGear+Fan+714T", - "description": "Fan product image" - } - ], - "created_at": "2024-09-30T09:16:11.450571Z", - "updated_at": "2025-07-07T09:16:11.450571Z" - }, - { - "id": 48, - "name": "SmartHome Monitor 576C", - "description": "SmartHome Monitor 576C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.72, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1312, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 39.5, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 60.5, - "mass": 2.19, - "unit": "kg" - } - ], - "components": [ - { - "id": 481, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 482, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 48, - "url": "https://via.placeholder.com/400x300/0afd76/ffffff?text=SmartHome+Monitor+576C", - "description": "Monitor product image" - } - ], - "created_at": "2024-07-24T09:16:11.450622Z", - "updated_at": "2025-04-26T09:16:11.450622Z" - }, - { - "id": 49, - "name": "EcoTech Coffee Maker 618O", - "description": "EcoTech Coffee Maker 618O is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1340, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.7, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 72.3, - "mass": 1.95, - "unit": "kg" - } - ], - "components": [ - { - "id": 491, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 492, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 49, - "url": "https://via.placeholder.com/400x300/0de4d0/ffffff?text=EcoTech+Coffee+Maker+618O", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-06-19T09:16:11.450670Z", - "updated_at": "2025-07-17T09:16:11.450670Z" - }, - { - "id": 50, - "name": "EcoTech Blender 695H", - "description": "EcoTech Blender 695H is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.71, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1391, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.0, - "mass": 0.97, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 29.9, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 15.3, - "mass": 0.16, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.9, - "mass": 0.98, - "unit": "kg" - } - ], - "components": [ - { - "id": 501, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 502, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 503, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 504, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 505, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 50, - "url": "https://via.placeholder.com/400x300/02b428/ffffff?text=EcoTech+Blender+695H", - "description": "Blender product image" - } - ], - "created_at": "2024-10-19T09:16:11.450731Z", - "updated_at": "2025-07-29T09:16:11.450731Z" - }, - { - "id": 51, - "name": "EcoTech Air Purifier 705E", - "description": "EcoTech Air Purifier 705E is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.2, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1461, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 34.3, - "mass": 1.04, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 55.9, - "mass": 2.08, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 9.8, - "mass": 0.16, - "unit": "kg" - } - ], - "components": [ - { - "id": 511, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 512, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 51, - "url": "https://via.placeholder.com/400x300/035657/ffffff?text=EcoTech+Air+Purifier+705E", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-12-27T09:16:11.450787Z", - "updated_at": "2025-04-27T09:16:11.450787Z" - }, - { - "id": 52, - "name": "AquaPro Iron 902H", - "description": "AquaPro Iron 902H is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.26, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2060, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.3, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 32.0, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 44.7, - "mass": 0.88, - "unit": "kg" - } - ], - "components": [ - { - "id": 521, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 522, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 52, - "url": "https://via.placeholder.com/400x300/0a3c5c/ffffff?text=AquaPro+Iron+902H", - "description": "Iron product image" - } - ], - "created_at": "2025-01-11T09:16:11.450851Z", - "updated_at": "2025-07-04T09:16:11.450851Z" - }, - { - "id": 53, - "name": "AquaPro Humidifier 598N", - "description": "AquaPro Humidifier 598N is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 30.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 802, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.9, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 82.1, - "mass": 3.0, - "unit": "kg" - } - ], - "components": [ - { - "id": 531, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 532, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 533, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 534, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 535, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 53, - "url": "https://via.placeholder.com/400x300/04ebc3/ffffff?text=AquaPro+Humidifier+598N", - "description": "Humidifier product image" - } - ], - "created_at": "2024-04-02T09:16:11.450905Z", - "updated_at": "2025-05-15T09:16:11.450905Z" - }, - { - "id": 54, - "name": "ChefMate Blender 609N", - "description": "ChefMate Blender 609N is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1173, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 47.4, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 52.6, - "mass": 1.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 541, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 542, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 54, - "url": "https://via.placeholder.com/400x300/0782a7/ffffff?text=ChefMate+Blender+609N", - "description": "Blender product image" - } - ], - "created_at": "2025-03-12T09:16:11.450954Z", - "updated_at": "2025-07-13T09:16:11.450954Z" - }, - { - "id": 55, - "name": "ChefMate Blender 554T", - "description": "ChefMate Blender 554T is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1816, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 25.8, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 42.7, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.5, - "mass": 1.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 551, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 552, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 553, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 554, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 555, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 55, - "url": "https://via.placeholder.com/400x300/086604/ffffff?text=ChefMate+Blender+554T", - "description": "Blender product image" - } - ], - "created_at": "2024-11-10T09:16:11.451002Z", - "updated_at": "2025-06-29T09:16:11.451002Z" - }, - { - "id": 56, - "name": "ZenGear Coffee Maker 136V", - "description": "ZenGear Coffee Maker 136V is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2168, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.0, - "mass": 1.23, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 12.2, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.2, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 29.6, - "mass": 1.0, - "unit": "kg" - } - ], - "components": [ - { - "id": 561, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 562, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 563, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 56, - "url": "https://via.placeholder.com/400x300/0e065e/ffffff?text=ZenGear+Coffee+Maker+136V", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-05-03T09:16:11.451059Z", - "updated_at": "2025-05-24T09:16:11.451059Z" - }, - { - "id": 57, - "name": "ZenGear Air Purifier 922A", - "description": "ZenGear Air Purifier 922A is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.57, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1760, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 35.7, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.7, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 32.5, - "mass": 0.39, - "unit": "kg" - } - ], - "components": [ - { - "id": 571, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 572, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 573, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 57, - "url": "https://via.placeholder.com/400x300/03e73b/ffffff?text=ZenGear+Air+Purifier+922A", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-10-28T09:16:11.451110Z", - "updated_at": "2025-06-19T09:16:11.451110Z" - }, - { - "id": 58, - "name": "AquaPro Blender 481C", - "description": "AquaPro Blender 481C is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.11, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1054, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 55.8, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 44.2, - "mass": 0.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 581, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 582, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 583, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 58, - "url": "https://via.placeholder.com/400x300/0442d3/ffffff?text=AquaPro+Blender+481C", - "description": "Blender product image" - } - ], - "created_at": "2024-08-24T09:16:11.451244Z", - "updated_at": "2025-07-11T09:16:11.451244Z" - }, - { - "id": 59, - "name": "NeoCook Blender 846D", - "description": "NeoCook Blender 846D is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.69, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2176, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 42.1, - "mass": 0.88, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 57.9, - "mass": 1.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 591, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 592, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 593, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 594, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 59, - "url": "https://via.placeholder.com/400x300/0d36a8/ffffff?text=NeoCook+Blender+846D", - "description": "Blender product image" - } - ], - "created_at": "2025-04-19T09:16:11.451291Z", - "updated_at": "2025-07-25T09:16:11.451291Z" - }, - { - "id": 60, - "name": "SmartHome Rice Cooker 607S", - "description": "SmartHome Rice Cooker 607S is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.68, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1531, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 42.2, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 57.8, - "mass": 1.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 601, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 602, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 603, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 60, - "url": "https://via.placeholder.com/400x300/05bcce/ffffff?text=SmartHome+Rice+Cooker+607S", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-05-04T09:16:11.451331Z", - "updated_at": "2025-06-02T09:16:11.451331Z" - }, - { - "id": 61, - "name": "AquaPro Fan 781X", - "description": "AquaPro Fan 781X is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.95, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1849, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 29.0, - "mass": 0.87, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 8.9, - "mass": 0.25, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 21.8, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.3, - "mass": 0.96, - "unit": "kg" - } - ], - "components": [ - { - "id": 611, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 612, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 613, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 614, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 615, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 61, - "url": "https://via.placeholder.com/400x300/04115f/ffffff?text=AquaPro+Fan+781X", - "description": "Fan product image" - } - ], - "created_at": "2024-10-25T09:16:11.451377Z", - "updated_at": "2025-06-24T09:16:11.451377Z" - }, - { - "id": 62, - "name": "NeoCook Humidifier 532S", - "description": "NeoCook Humidifier 532S is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.34, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2171, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.2, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.9, - "mass": 1.03, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 10.5, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 33.3, - "mass": 0.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 621, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 622, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 62, - "url": "https://via.placeholder.com/400x300/0d16cf/ffffff?text=NeoCook+Humidifier+532S", - "description": "Humidifier product image" - } - ], - "created_at": "2024-10-19T09:16:11.451428Z", - "updated_at": "2025-06-18T09:16:11.451428Z" - }, - { - "id": 63, - "name": "CleanWave Toaster 934J", - "description": "CleanWave Toaster 934J is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.09, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2044, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 30.9, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 37.5, - "mass": 1.04, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.6, - "mass": 0.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 631, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 632, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 633, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 63, - "url": "https://via.placeholder.com/400x300/0b850a/ffffff?text=CleanWave+Toaster+934J", - "description": "Toaster product image" - } - ], - "created_at": "2024-10-12T09:16:11.451483Z", - "updated_at": "2025-07-17T09:16:11.451483Z" - }, - { - "id": 64, - "name": "ChefMate Humidifier 817V", - "description": "ChefMate Humidifier 817V is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 931, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 24.6, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 16.6, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 32.6, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 26.3, - "mass": 0.62, - "unit": "kg" - } - ], - "components": [ - { - "id": 641, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 642, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 643, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 644, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 645, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 64, - "url": "https://via.placeholder.com/400x300/0c871e/ffffff?text=ChefMate+Humidifier+817V", - "description": "Humidifier product image" - } - ], - "created_at": "2024-06-14T09:16:11.451537Z", - "updated_at": "2025-05-23T09:16:11.451537Z" - }, - { - "id": 65, - "name": "EcoTech Monitor 888W", - "description": "EcoTech Monitor 888W is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1091, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.6, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 42.3, - "mass": 1.41, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 21.1, - "mass": 0.56, - "unit": "kg" - } - ], - "components": [ - { - "id": 651, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 652, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 653, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 654, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 65, - "url": "https://via.placeholder.com/400x300/0a8e4d/ffffff?text=EcoTech+Monitor+888W", - "description": "Monitor product image" - } - ], - "created_at": "2024-07-15T09:16:11.451569Z", - "updated_at": "2025-05-29T09:16:11.451569Z" - }, - { - "id": 66, - "name": "NeoCook Kettle 219G", - "description": "NeoCook Kettle 219G is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 968, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 14.4, - "mass": 0.18, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 43.9, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.7, - "mass": 1.34, - "unit": "kg" - } - ], - "components": [ - { - "id": 661, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 662, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 66, - "url": "https://via.placeholder.com/400x300/038125/ffffff?text=NeoCook+Kettle+219G", - "description": "Kettle product image" - } - ], - "created_at": "2024-03-30T09:16:11.451600Z", - "updated_at": "2025-06-26T09:16:11.451600Z" - }, - { - "id": 67, - "name": "CleanWave Fan 871L", - "description": "CleanWave Fan 871L is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.24, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1259, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 24.1, - "mass": 0.93, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 54.4, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.5, - "mass": 0.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 671, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 672, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 673, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 674, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 67, - "url": "https://via.placeholder.com/400x300/06766b/ffffff?text=CleanWave+Fan+871L", - "description": "Fan product image" - } - ], - "created_at": "2024-11-30T09:16:11.451635Z", - "updated_at": "2025-04-26T09:16:11.451635Z" - }, - { - "id": 68, - "name": "EcoTech Fan 746H", - "description": "EcoTech Fan 746H is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1488, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.0, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 46.8, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.2, - "mass": 0.31, - "unit": "kg" - } - ], - "components": [ - { - "id": 681, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 682, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 683, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 684, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 68, - "url": "https://via.placeholder.com/400x300/0e6f50/ffffff?text=EcoTech+Fan+746H", - "description": "Fan product image" - } - ], - "created_at": "2025-04-05T09:16:11.451671Z", - "updated_at": "2025-05-15T09:16:11.451671Z" - }, - { - "id": 69, - "name": "NeoCook Toaster 779R", - "description": "NeoCook Toaster 779R is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.29, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2135, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 22.7, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 77.3, - "mass": 2.93, - "unit": "kg" - } - ], - "components": [ - { - "id": 691, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 692, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 693, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 694, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 69, - "url": "https://via.placeholder.com/400x300/05ac9b/ffffff?text=NeoCook+Toaster+779R", - "description": "Toaster product image" - } - ], - "created_at": "2025-01-25T09:16:11.451718Z", - "updated_at": "2025-06-05T09:16:11.451718Z" - }, - { - "id": 70, - "name": "NeoCook Toaster 749F", - "description": "NeoCook Toaster 749F is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 845, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 81.5, - "mass": 2.22, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 18.5, - "mass": 0.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 701, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 702, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 703, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 704, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 70, - "url": "https://via.placeholder.com/400x300/0d3980/ffffff?text=NeoCook+Toaster+749F", - "description": "Toaster product image" - } - ], - "created_at": "2024-07-29T09:16:11.451770Z", - "updated_at": "2025-06-21T09:16:11.451770Z" - }, - { - "id": 71, - "name": "ZenGear Blender 125N", - "description": "ZenGear Blender 125N is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.81, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 928, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 48.6, - "mass": 1.92, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 51.4, - "mass": 1.5, - "unit": "kg" - } - ], - "components": [ - { - "id": 711, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 712, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 71, - "url": "https://via.placeholder.com/400x300/0b18a9/ffffff?text=ZenGear+Blender+125N", - "description": "Blender product image" - } - ], - "created_at": "2024-03-28T09:16:11.451819Z", - "updated_at": "2025-05-05T09:16:11.451819Z" - }, - { - "id": 72, - "name": "PureLife Toaster 874Q", - "description": "PureLife Toaster 874Q is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.13, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1878, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 35.0, - "mass": 0.85, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.5, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.6, - "mass": 0.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 721, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 722, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 723, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 724, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 72, - "url": "https://via.placeholder.com/400x300/0f0e0d/ffffff?text=PureLife+Toaster+874Q", - "description": "Toaster product image" - } - ], - "created_at": "2025-04-05T09:16:11.451880Z", - "updated_at": "2025-06-14T09:16:11.451880Z" - }, - { - "id": 73, - "name": "ChefMate Air Purifier 590Y", - "description": "ChefMate Air Purifier 590Y is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.26, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1519, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 65.8, - "mass": 2.01, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 34.2, - "mass": 0.5, - "unit": "kg" - } - ], - "components": [ - { - "id": 731, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 732, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 733, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 73, - "url": "https://via.placeholder.com/400x300/0cc406/ffffff?text=ChefMate+Air+Purifier+590Y", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-03-30T09:16:11.451926Z", - "updated_at": "2025-06-15T09:16:11.451926Z" - }, - { - "id": 74, - "name": "AquaPro Iron 454C", - "description": "AquaPro Iron 454C is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1280, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 29.7, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 48.6, - "mass": 1.85, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.6, - "mass": 0.26, - "unit": "kg" - } - ], - "components": [ - { - "id": 741, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 742, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 743, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 744, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 745, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 74, - "url": "https://via.placeholder.com/400x300/07dbf9/ffffff?text=AquaPro+Iron+454C", - "description": "Iron product image" - } - ], - "created_at": "2025-03-22T09:16:11.451961Z", - "updated_at": "2025-04-27T09:16:11.451961Z" - }, - { - "id": 75, - "name": "PureLife Air Purifier 527P", - "description": "PureLife Air Purifier 527P is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.37, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2127, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 12.7, - "mass": 0.15, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 37.3, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 12.0, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.0, - "mass": 1.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 751, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 752, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 753, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 754, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 755, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 75, - "url": "https://via.placeholder.com/400x300/043674/ffffff?text=PureLife+Air+Purifier+527P", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-05-02T09:16:11.451999Z", - "updated_at": "2025-07-24T09:16:11.451999Z" - }, - { - "id": 76, - "name": "ZenGear Monitor 938X", - "description": "ZenGear Monitor 938X is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.06, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1145, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 19.8, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.1, - "mass": 1.0, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 49.1, - "mass": 0.85, - "unit": "kg" - } - ], - "components": [ - { - "id": 761, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 762, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 763, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 76, - "url": "https://via.placeholder.com/400x300/0ddb03/ffffff?text=ZenGear+Monitor+938X", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-15T09:16:11.452045Z", - "updated_at": "2025-07-11T09:16:11.452045Z" - }, - { - "id": 77, - "name": "EcoTech Monitor 598B", - "description": "EcoTech Monitor 598B is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.08, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1944, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 26.4, - "mass": 0.96, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.9, - "mass": 0.94, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.0, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.8, - "mass": 0.53, - "unit": "kg" - } - ], - "components": [ - { - "id": 771, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 772, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 773, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 774, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 775, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 77, - "url": "https://via.placeholder.com/400x300/03abc0/ffffff?text=EcoTech+Monitor+598B", - "description": "Monitor product image" - } - ], - "created_at": "2025-01-12T09:16:11.452091Z", - "updated_at": "2025-06-13T09:16:11.452091Z" - }, - { - "id": 78, - "name": "NeoCook Toaster 499E", - "description": "NeoCook Toaster 499E is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.27, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1203, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 43.8, - "mass": 1.63, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 56.2, - "mass": 1.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 781, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 782, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 78, - "url": "https://via.placeholder.com/400x300/06fcae/ffffff?text=NeoCook+Toaster+499E", - "description": "Toaster product image" - } - ], - "created_at": "2025-01-13T09:16:11.452135Z", - "updated_at": "2025-07-23T09:16:11.452135Z" - }, - { - "id": 79, - "name": "PureLife Iron 780K", - "description": "PureLife Iron 780K is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.13, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1041, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.5, - "mass": 1.27, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 49.5, - "mass": 1.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 791, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 792, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 79, - "url": "https://via.placeholder.com/400x300/0cbe03/ffffff?text=PureLife+Iron+780K", - "description": "Iron product image" - } - ], - "created_at": "2025-01-08T09:16:11.452166Z", - "updated_at": "2025-07-07T09:16:11.452166Z" - }, - { - "id": 80, - "name": "EcoTech Humidifier 864C", - "description": "EcoTech Humidifier 864C is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.08, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2052, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 68.5, - "mass": 2.73, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.5, - "mass": 0.81, - "unit": "kg" - } - ], - "components": [ - { - "id": 801, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 802, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 803, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 804, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 80, - "url": "https://via.placeholder.com/400x300/0454a1/ffffff?text=EcoTech+Humidifier+864C", - "description": "Humidifier product image" - } - ], - "created_at": "2024-07-17T09:16:11.452198Z", - "updated_at": "2025-06-09T09:16:11.452198Z" - }, - { - "id": 81, - "name": "SmartHome Fan 980L", - "description": "SmartHome Fan 980L is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.8, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1134, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 24.6, - "mass": 0.82, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 75.4, - "mass": 0.91, - "unit": "kg" - } - ], - "components": [ - { - "id": 811, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 812, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 813, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 814, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 81, - "url": "https://via.placeholder.com/400x300/063607/ffffff?text=SmartHome+Fan+980L", - "description": "Fan product image" - } - ], - "created_at": "2024-06-26T09:16:11.452227Z", - "updated_at": "2025-05-18T09:16:11.452227Z" - }, - { - "id": 82, - "name": "ZenGear Kettle 816G", - "description": "ZenGear Kettle 816G is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 977, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.4, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 76.6, - "mass": 1.98, - "unit": "kg" - } - ], - "components": [ - { - "id": 821, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 822, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 823, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 824, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 825, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 82, - "url": "https://via.placeholder.com/400x300/03f661/ffffff?text=ZenGear+Kettle+816G", - "description": "Kettle product image" - } - ], - "created_at": "2025-04-03T09:16:11.452256Z", - "updated_at": "2025-05-22T09:16:11.452256Z" - }, - { - "id": 83, - "name": "CleanWave Kettle 743T", - "description": "CleanWave Kettle 743T is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.86, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 882, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.2, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 48.6, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 31.2, - "mass": 0.56, - "unit": "kg" - } - ], - "components": [ - { - "id": 831, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 832, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 833, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 834, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 83, - "url": "https://via.placeholder.com/400x300/060138/ffffff?text=CleanWave+Kettle+743T", - "description": "Kettle product image" - } - ], - "created_at": "2024-12-30T09:16:11.452541Z", - "updated_at": "2025-07-22T09:16:11.452541Z" - }, - { - "id": 84, - "name": "AquaPro Iron 426H", - "description": "AquaPro Iron 426H is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1477, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 18.2, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 81.8, - "mass": 2.84, - "unit": "kg" - } - ], - "components": [ - { - "id": 841, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 842, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 843, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 84, - "url": "https://via.placeholder.com/400x300/020ecf/ffffff?text=AquaPro+Iron+426H", - "description": "Iron product image" - } - ], - "created_at": "2024-08-14T09:16:11.452584Z", - "updated_at": "2025-07-15T09:16:11.452584Z" - }, - { - "id": 85, - "name": "EcoTech Humidifier 407F", - "description": "EcoTech Humidifier 407F is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.92, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1692, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 48.6, - "mass": 0.52, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 51.4, - "mass": 0.6, - "unit": "kg" - } - ], - "components": [ - { - "id": 851, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 852, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 853, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 854, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 855, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 85, - "url": "https://via.placeholder.com/400x300/05ddbf/ffffff?text=EcoTech+Humidifier+407F", - "description": "Humidifier product image" - } - ], - "created_at": "2025-02-25T09:16:11.452617Z", - "updated_at": "2025-06-10T09:16:11.452617Z" - }, - { - "id": 86, - "name": "ZenGear Toaster 287I", - "description": "ZenGear Toaster 287I is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.77, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1830, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 60.6, - "mass": 2.2, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 39.4, - "mass": 0.66, - "unit": "kg" - } - ], - "components": [ - { - "id": 861, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 862, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 86, - "url": "https://via.placeholder.com/400x300/04ea76/ffffff?text=ZenGear+Toaster+287I", - "description": "Toaster product image" - } - ], - "created_at": "2024-10-24T09:16:11.452644Z", - "updated_at": "2025-07-26T09:16:11.452644Z" - }, - { - "id": 87, - "name": "EcoTech Monitor 837D", - "description": "EcoTech Monitor 837D is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.08, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1779, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 58.3, - "mass": 1.99, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 41.7, - "mass": 1.37, - "unit": "kg" - } - ], - "components": [ - { - "id": 871, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 872, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 873, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 874, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 87, - "url": "https://via.placeholder.com/400x300/0dc27f/ffffff?text=EcoTech+Monitor+837D", - "description": "Monitor product image" - } - ], - "created_at": "2024-05-28T09:16:11.452675Z", - "updated_at": "2025-06-24T09:16:11.452675Z" - }, - { - "id": 88, - "name": "PureLife Iron 727S", - "description": "PureLife Iron 727S is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.5, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1529, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 37.3, - "mass": 1.16, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 13.6, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 18.2, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 30.9, - "mass": 0.54, - "unit": "kg" - } - ], - "components": [ - { - "id": 881, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 882, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 88, - "url": "https://via.placeholder.com/400x300/0aaeaf/ffffff?text=PureLife+Iron+727S", - "description": "Iron product image" - } - ], - "created_at": "2025-03-04T09:16:11.452707Z", - "updated_at": "2025-06-22T09:16:11.452707Z" - }, - { - "id": 89, - "name": "PureLife Monitor 709R", - "description": "PureLife Monitor 709R is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.18, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1418, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 19.4, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 40.3, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 40.3, - "mass": 0.55, - "unit": "kg" - } - ], - "components": [ - { - "id": 891, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 892, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 893, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 894, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 895, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 89, - "url": "https://via.placeholder.com/400x300/054ec8/ffffff?text=PureLife+Monitor+709R", - "description": "Monitor product image" - } - ], - "created_at": "2024-10-20T09:16:11.452738Z", - "updated_at": "2025-06-27T09:16:11.452738Z" - }, - { - "id": 90, - "name": "SmartHome Kettle 786V", - "description": "SmartHome Kettle 786V is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1169, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 32.2, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 32.2, - "mass": 0.94, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 13.5, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 22.2, - "mass": 0.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 901, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 902, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 903, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 904, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 905, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 90, - "url": "https://via.placeholder.com/400x300/0a3e25/ffffff?text=SmartHome+Kettle+786V", - "description": "Kettle product image" - } - ], - "created_at": "2025-01-08T09:16:11.452771Z", - "updated_at": "2025-06-16T09:16:11.452771Z" - }, - { - "id": 91, - "name": "ChefMate Fan 942H", - "description": "ChefMate Fan 942H is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.46, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1299, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 20.8, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 79.2, - "mass": 2.1, - "unit": "kg" - } - ], - "components": [ - { - "id": 911, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 912, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 913, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 91, - "url": "https://via.placeholder.com/400x300/0c2c51/ffffff?text=ChefMate+Fan+942H", - "description": "Fan product image" - } - ], - "created_at": "2024-11-15T09:16:11.452799Z", - "updated_at": "2025-06-14T09:16:11.452799Z" - }, - { - "id": 92, - "name": "NeoCook Coffee Maker 473K", - "description": "NeoCook Coffee Maker 473K is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.71, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1991, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 24.7, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 25.3, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 28.2, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.8, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 921, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 922, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 92, - "url": "https://via.placeholder.com/400x300/028c87/ffffff?text=NeoCook+Coffee+Maker+473K", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-03-26T09:16:11.452845Z", - "updated_at": "2025-07-08T09:16:11.452845Z" - }, - { - "id": 93, - "name": "ChefMate Fan 200G", - "description": "ChefMate Fan 200G is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.01, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1998, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 40.5, - "mass": 0.47, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 36.2, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 23.3, - "mass": 0.51, - "unit": "kg" - } - ], - "components": [ - { - "id": 931, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 932, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 93, - "url": "https://via.placeholder.com/400x300/0b4781/ffffff?text=ChefMate+Fan+200G", - "description": "Fan product image" - } - ], - "created_at": "2025-02-11T09:16:11.452979Z", - "updated_at": "2025-06-17T09:16:11.452979Z" - }, - { - "id": 94, - "name": "CleanWave Coffee Maker 693H", - "description": "CleanWave Coffee Maker 693H is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.39, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1061, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 52.2, - "mass": 1.01, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 30.4, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 17.4, - "mass": 0.64, - "unit": "kg" - } - ], - "components": [ - { - "id": 941, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 942, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 94, - "url": "https://via.placeholder.com/400x300/078609/ffffff?text=CleanWave+Coffee+Maker+693H", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-01-16T09:16:11.453014Z", - "updated_at": "2025-05-26T09:16:11.453014Z" - }, - { - "id": 95, - "name": "CleanWave Air Purifier 565A", - "description": "CleanWave Air Purifier 565A is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 30.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1724, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 34.5, - "mass": 1.37, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.6, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.9, - "mass": 0.73, - "unit": "kg" - } - ], - "components": [ - { - "id": 951, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 952, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 953, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 954, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 95, - "url": "https://via.placeholder.com/400x300/0488b4/ffffff?text=CleanWave+Air+Purifier+565A", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-07-09T09:16:11.453047Z", - "updated_at": "2025-07-17T09:16:11.453047Z" - }, - { - "id": 96, - "name": "ZenGear Monitor 993I", - "description": "ZenGear Monitor 993I is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1664, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 20.9, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 15.1, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.6, - "mass": 1.14, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 24.5, - "mass": 0.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 961, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 962, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 963, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 96, - "url": "https://via.placeholder.com/400x300/0ac1a9/ffffff?text=ZenGear+Monitor+993I", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-31T09:16:11.453082Z", - "updated_at": "2025-07-07T09:16:11.453082Z" - }, - { - "id": 97, - "name": "SmartHome Humidifier 193Z", - "description": "SmartHome Humidifier 193Z is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.31, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1245, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 40.4, - "mass": 0.88, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 30.8, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 13.0, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 15.8, - "mass": 0.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 971, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 972, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 97, - "url": "https://via.placeholder.com/400x300/0a16c7/ffffff?text=SmartHome+Humidifier+193Z", - "description": "Humidifier product image" - } - ], - "created_at": "2025-02-10T09:16:11.453132Z", - "updated_at": "2025-06-12T09:16:11.453132Z" - }, - { - "id": 98, - "name": "AquaPro Coffee Maker 128R", - "description": "AquaPro Coffee Maker 128R is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.08, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2131, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 53.4, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 27.6, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 19.0, - "mass": 0.56, - "unit": "kg" - } - ], - "components": [ - { - "id": 981, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 982, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 983, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 98, - "url": "https://via.placeholder.com/400x300/05e3b0/ffffff?text=AquaPro+Coffee+Maker+128R", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-10-13T09:16:11.453186Z", - "updated_at": "2025-05-27T09:16:11.453186Z" - }, - { - "id": 99, - "name": "ChefMate Kettle 864I", - "description": "ChefMate Kettle 864I is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.52, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1265, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.0, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 38.4, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.6, - "mass": 1.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 991, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 992, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 993, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 994, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 99, - "url": "https://via.placeholder.com/400x300/022bcc/ffffff?text=ChefMate+Kettle+864I", - "description": "Kettle product image" - } - ], - "created_at": "2024-11-09T09:16:11.453220Z", - "updated_at": "2025-06-20T09:16:11.453220Z" - }, - { - "id": 100, - "name": "CleanWave Humidifier 149T", - "description": "CleanWave Humidifier 149T is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.11, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1658, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 13.7, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 35.9, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 40.5, - "mass": 1.42, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 9.9, - "mass": 0.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 1001, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1002, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1003, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1004, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1005, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 100, - "url": "https://via.placeholder.com/400x300/0a7255/ffffff?text=CleanWave+Humidifier+149T", - "description": "Humidifier product image" - } - ], - "created_at": "2024-08-31T09:16:11.453256Z", - "updated_at": "2025-07-04T09:16:11.453256Z" - }, - { - "id": 101, - "name": "PureLife Blender 932O", - "description": "PureLife Blender 932O is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.78, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2114, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 22.0, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 78.0, - "mass": 1.21, - "unit": "kg" - } - ], - "components": [ - { - "id": 1011, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1012, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1013, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 101, - "url": "https://via.placeholder.com/400x300/0876c7/ffffff?text=PureLife+Blender+932O", - "description": "Blender product image" - } - ], - "created_at": "2024-06-09T09:16:11.453285Z", - "updated_at": "2025-06-21T09:16:11.453285Z" - }, - { - "id": 102, - "name": "EcoTech Coffee Maker 859H", - "description": "EcoTech Coffee Maker 859H is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1521, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 28.7, - "mass": 1.07, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 71.2, - "mass": 1.37, - "unit": "kg" - } - ], - "components": [ - { - "id": 1021, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1022, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1023, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 102, - "url": "https://via.placeholder.com/400x300/0495ec/ffffff?text=EcoTech+Coffee+Maker+859H", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-12-03T09:16:11.453315Z", - "updated_at": "2025-06-14T09:16:11.453315Z" - }, - { - "id": 103, - "name": "NeoCook Coffee Maker 171A", - "description": "NeoCook Coffee Maker 171A is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1065, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 18.5, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 23.6, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 29.8, - "mass": 1.01, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.1, - "mass": 0.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 1031, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1032, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1033, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1034, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1035, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 103, - "url": "https://via.placeholder.com/400x300/0c248e/ffffff?text=NeoCook+Coffee+Maker+171A", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-02-18T09:16:11.453348Z", - "updated_at": "2025-06-26T09:16:11.453348Z" - }, - { - "id": 104, - "name": "SmartHome Rice Cooker 419U", - "description": "SmartHome Rice Cooker 419U is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.33, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1217, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 11.9, - "mass": 0.13, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 24.6, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 41.0, - "mass": 1.55, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 22.4, - "mass": 0.51, - "unit": "kg" - } - ], - "components": [ - { - "id": 1041, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1042, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1043, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1044, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1045, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 104, - "url": "https://via.placeholder.com/400x300/0899aa/ffffff?text=SmartHome+Rice+Cooker+419U", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-12-14T09:16:11.453381Z", - "updated_at": "2025-05-01T09:16:11.453381Z" - }, - { - "id": 105, - "name": "AquaPro Air Purifier 306F", - "description": "AquaPro Air Purifier 306F is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.05, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1472, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 18.4, - "mass": 0.23, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 16.1, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 36.8, - "mass": 1.43, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.7, - "mass": 0.72, - "unit": "kg" - } - ], - "components": [ - { - "id": 1051, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1052, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1053, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 105, - "url": "https://via.placeholder.com/400x300/0eaa70/ffffff?text=AquaPro+Air+Purifier+306F", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-10-30T09:16:11.453413Z", - "updated_at": "2025-07-09T09:16:11.453413Z" - }, - { - "id": 106, - "name": "PureLife Iron 428F", - "description": "PureLife Iron 428F is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1605, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 75.0, - "mass": 1.13, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.0, - "mass": 0.7, - "unit": "kg" - } - ], - "components": [ - { - "id": 1061, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1062, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 106, - "url": "https://via.placeholder.com/400x300/070382/ffffff?text=PureLife+Iron+428F", - "description": "Iron product image" - } - ], - "created_at": "2025-04-13T09:16:11.453442Z", - "updated_at": "2025-06-16T09:16:11.453442Z" - }, - { - "id": 107, - "name": "SmartHome Rice Cooker 782J", - "description": "SmartHome Rice Cooker 782J is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.75, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2091, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.3, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 46.7, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.0, - "mass": 0.38, - "unit": "kg" - } - ], - "components": [ - { - "id": 1071, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1072, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1073, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1074, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 107, - "url": "https://via.placeholder.com/400x300/0676cc/ffffff?text=SmartHome+Rice+Cooker+782J", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-05-27T09:16:11.453488Z", - "updated_at": "2025-06-09T09:16:11.453488Z" - }, - { - "id": 108, - "name": "ZenGear Blender 351S", - "description": "ZenGear Blender 351S is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1460, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 35.0, - "mass": 1.22, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 18.3, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 11.7, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 35.0, - "mass": 0.97, - "unit": "kg" - } - ], - "components": [ - { - "id": 1081, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1082, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 108, - "url": "https://via.placeholder.com/400x300/0b229f/ffffff?text=ZenGear+Blender+351S", - "description": "Blender product image" - } - ], - "created_at": "2024-12-18T09:16:11.453527Z", - "updated_at": "2025-07-14T09:16:11.453527Z" - }, - { - "id": 109, - "name": "ZenGear Rice Cooker 350F", - "description": "ZenGear Rice Cooker 350F is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 950, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 51.5, - "mass": 1.8, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 48.5, - "mass": 1.33, - "unit": "kg" - } - ], - "components": [ - { - "id": 1091, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1092, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1093, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1094, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1095, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 109, - "url": "https://via.placeholder.com/400x300/0312f6/ffffff?text=ZenGear+Rice+Cooker+350F", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-09-26T09:16:11.453560Z", - "updated_at": "2025-05-02T09:16:11.453560Z" - }, - { - "id": 110, - "name": "NeoCook Blender 666A", - "description": "NeoCook Blender 666A is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1205, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 34.1, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 65.9, - "mass": 1.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 1101, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1102, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1103, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 1104, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 1105, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 110, - "url": "https://via.placeholder.com/400x300/0204bc/ffffff?text=NeoCook+Blender+666A", - "description": "Blender product image" - } - ], - "created_at": "2024-11-25T09:16:11.453592Z", - "updated_at": "2025-05-28T09:16:11.453592Z" - }, - { - "id": 111, - "name": "PureLife Rice Cooker 176O", - "description": "PureLife Rice Cooker 176O is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.05, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1244, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 19.6, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 32.9, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.5, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 30.1, - "mass": 0.72, - "unit": "kg" - } - ], - "components": [ - { - "id": 1111, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1112, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1113, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1114, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1115, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 111, - "url": "https://via.placeholder.com/400x300/06f8ff/ffffff?text=PureLife+Rice+Cooker+176O", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-03-21T09:16:11.453631Z", - "updated_at": "2025-05-24T09:16:11.453631Z" - }, - { - "id": 112, - "name": "ZenGear Rice Cooker 747A", - "description": "ZenGear Rice Cooker 747A is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.06, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1575, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 58.8, - "mass": 1.73, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 41.2, - "mass": 1.47, - "unit": "kg" - } - ], - "components": [ - { - "id": 1121, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1122, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1123, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1124, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1125, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 112, - "url": "https://via.placeholder.com/400x300/07f384/ffffff?text=ZenGear+Rice+Cooker+747A", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-07-16T09:16:11.453667Z", - "updated_at": "2025-06-09T09:16:11.453667Z" - }, - { - "id": 113, - "name": "AquaPro Toaster 535O", - "description": "AquaPro Toaster 535O is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.78, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1784, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.1, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 30.7, - "mass": 0.99, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.8, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 21.4, - "mass": 0.26, - "unit": "kg" - } - ], - "components": [ - { - "id": 1131, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1132, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1133, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 113, - "url": "https://via.placeholder.com/400x300/051dd1/ffffff?text=AquaPro+Toaster+535O", - "description": "Toaster product image" - } - ], - "created_at": "2024-07-28T09:16:11.453706Z", - "updated_at": "2025-06-04T09:16:11.453706Z" - }, - { - "id": 114, - "name": "NeoCook Iron 558J", - "description": "NeoCook Iron 558J is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.44, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1313, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 8.9, - "mass": 0.21, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 41.1, - "mass": 1.2, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.6, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 19.4, - "mass": 0.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 1141, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1142, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 114, - "url": "https://via.placeholder.com/400x300/0d56eb/ffffff?text=NeoCook+Iron+558J", - "description": "Iron product image" - } - ], - "created_at": "2025-03-19T09:16:11.453766Z", - "updated_at": "2025-05-06T09:16:11.453766Z" - }, - { - "id": 115, - "name": "EcoTech Iron 827F", - "description": "EcoTech Iron 827F is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.7, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1995, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 48.3, - "mass": 1.46, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 51.7, - "mass": 0.73, - "unit": "kg" - } - ], - "components": [ - { - "id": 1151, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1152, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 115, - "url": "https://via.placeholder.com/400x300/0491fe/ffffff?text=EcoTech+Iron+827F", - "description": "Iron product image" - } - ], - "created_at": "2024-04-16T09:16:11.453823Z", - "updated_at": "2025-06-23T09:16:11.453823Z" - }, - { - "id": 116, - "name": "NeoCook Toaster 570V", - "description": "NeoCook Toaster 570V is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.04, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1252, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 19.9, - "mass": 0.64, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 30.7, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.6, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 27.8, - "mass": 0.97, - "unit": "kg" - } - ], - "components": [ - { - "id": 1161, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1162, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1163, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 116, - "url": "https://via.placeholder.com/400x300/0a3b61/ffffff?text=NeoCook+Toaster+570V", - "description": "Toaster product image" - } - ], - "created_at": "2024-04-24T09:16:11.453896Z", - "updated_at": "2025-04-30T09:16:11.453896Z" - }, - { - "id": 117, - "name": "EcoTech Fan 392G", - "description": "EcoTech Fan 392G is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.47, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1361, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 12.2, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 26.7, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.0, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.1, - "mass": 0.93, - "unit": "kg" - } - ], - "components": [ - { - "id": 1171, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1172, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1173, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1174, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 1175, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 117, - "url": "https://via.placeholder.com/400x300/06785f/ffffff?text=EcoTech+Fan+392G", - "description": "Fan product image" - } - ], - "created_at": "2024-05-15T09:16:11.454124Z", - "updated_at": "2025-05-08T09:16:11.454124Z" - }, - { - "id": 118, - "name": "EcoTech Monitor 661X", - "description": "EcoTech Monitor 661X is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.04, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1126, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 47.7, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 52.3, - "mass": 0.95, - "unit": "kg" - } - ], - "components": [ - { - "id": 1181, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1182, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1183, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1184, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 118, - "url": "https://via.placeholder.com/400x300/03d2a7/ffffff?text=EcoTech+Monitor+661X", - "description": "Monitor product image" - } - ], - "created_at": "2024-10-31T09:16:11.454397Z", - "updated_at": "2025-05-15T09:16:11.454397Z" - }, - { - "id": 119, - "name": "CleanWave Fan 528A", - "description": "CleanWave Fan 528A is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.95, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1294, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 12.0, - "mass": 0.16, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 35.4, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 16.5, - "mass": 0.25, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 36.1, - "mass": 0.69, - "unit": "kg" - } - ], - "components": [ - { - "id": 1191, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1192, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1193, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1194, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 1195, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 119, - "url": "https://via.placeholder.com/400x300/03e948/ffffff?text=CleanWave+Fan+528A", - "description": "Fan product image" - } - ], - "created_at": "2025-04-07T09:16:11.454813Z", - "updated_at": "2025-06-12T09:16:11.454813Z" - }, - { - "id": 120, - "name": "AquaPro Blender 433Z", - "description": "AquaPro Blender 433Z is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.26, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1899, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 31.4, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 31.4, - "mass": 0.85, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 31.9, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 5.3, - "mass": 0.15, - "unit": "kg" - } - ], - "components": [ - { - "id": 1201, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1202, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 120, - "url": "https://via.placeholder.com/400x300/06af50/ffffff?text=AquaPro+Blender+433Z", - "description": "Blender product image" - } - ], - "created_at": "2024-10-14T09:16:11.454916Z", - "updated_at": "2025-07-21T09:16:11.454916Z" - }, - { - "id": 121, - "name": "CleanWave Kettle 614T", - "description": "CleanWave Kettle 614T is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.74, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1292, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 11.5, - "mass": 0.2, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 48.1, - "mass": 0.75, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 40.4, - "mass": 0.85, - "unit": "kg" - } - ], - "components": [ - { - "id": 1211, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1212, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1213, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1214, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1215, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 121, - "url": "https://via.placeholder.com/400x300/0afe96/ffffff?text=CleanWave+Kettle+614T", - "description": "Kettle product image" - } - ], - "created_at": "2025-01-09T09:16:11.454988Z", - "updated_at": "2025-07-21T09:16:11.454988Z" - }, - { - "id": 122, - "name": "NeoCook Coffee Maker 892J", - "description": "NeoCook Coffee Maker 892J is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.21, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1383, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 27.4, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.2, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 23.7, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.7, - "mass": 0.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 1221, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1222, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 122, - "url": "https://via.placeholder.com/400x300/0bf37c/ffffff?text=NeoCook+Coffee+Maker+892J", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-03-25T09:16:11.455056Z", - "updated_at": "2025-07-12T09:16:11.455056Z" - }, - { - "id": 123, - "name": "CleanWave Fan 298P", - "description": "CleanWave Fan 298P is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1089, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.5, - "mass": 0.47, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 23.8, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 35.7, - "mass": 1.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 1231, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1232, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1233, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 123, - "url": "https://via.placeholder.com/400x300/0979a8/ffffff?text=CleanWave+Fan+298P", - "description": "Fan product image" - } - ], - "created_at": "2025-02-02T09:16:11.455114Z", - "updated_at": "2025-06-17T09:16:11.455114Z" - }, - { - "id": 124, - "name": "NeoCook Kettle 965Z", - "description": "NeoCook Kettle 965Z is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1928, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 16.9, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 27.1, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 45.8, - "mass": 1.0, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 10.2, - "mass": 0.17, - "unit": "kg" - } - ], - "components": [ - { - "id": 1241, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1242, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1243, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1244, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1245, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 124, - "url": "https://via.placeholder.com/400x300/0e07cc/ffffff?text=NeoCook+Kettle+965Z", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-02T09:16:11.455190Z", - "updated_at": "2025-05-19T09:16:11.455190Z" - }, - { - "id": 125, - "name": "PureLife Toaster 428F", - "description": "PureLife Toaster 428F is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.37, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1647, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 16.8, - "mass": 0.26, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.6, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 22.7, - "mass": 0.26, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.9, - "mass": 0.54, - "unit": "kg" - } - ], - "components": [ - { - "id": 1251, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1252, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1253, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1254, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1255, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 125, - "url": "https://via.placeholder.com/400x300/0c7508/ffffff?text=PureLife+Toaster+428F", - "description": "Toaster product image" - } - ], - "created_at": "2024-03-29T09:16:11.455388Z", - "updated_at": "2025-05-04T09:16:11.455388Z" - }, - { - "id": 126, - "name": "ZenGear Kettle 889Q", - "description": "ZenGear Kettle 889Q is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1558, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 13.5, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 66.3, - "mass": 1.82, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.2, - "mass": 0.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 1261, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1262, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 126, - "url": "https://via.placeholder.com/400x300/034a52/ffffff?text=ZenGear+Kettle+889Q", - "description": "Kettle product image" - } - ], - "created_at": "2024-05-30T09:16:11.455471Z", - "updated_at": "2025-06-15T09:16:11.455471Z" - }, - { - "id": 127, - "name": "SmartHome Air Purifier 934O", - "description": "SmartHome Air Purifier 934O is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.06, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2050, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.7, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 25.9, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 22.8, - "mass": 0.75, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.6, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 1271, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1272, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1273, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1274, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1275, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 127, - "url": "https://via.placeholder.com/400x300/0f29cd/ffffff?text=SmartHome+Air+Purifier+934O", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-10-19T09:16:11.455605Z", - "updated_at": "2025-05-04T09:16:11.455605Z" - }, - { - "id": 128, - "name": "EcoTech Rice Cooker 420T", - "description": "EcoTech Rice Cooker 420T is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1892, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 42.0, - "mass": 1.02, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 10.1, - "mass": 0.12, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 47.9, - "mass": 0.76, - "unit": "kg" - } - ], - "components": [ - { - "id": 1281, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1282, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 128, - "url": "https://via.placeholder.com/400x300/056bba/ffffff?text=EcoTech+Rice+Cooker+420T", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-08-09T09:16:11.455824Z", - "updated_at": "2025-07-17T09:16:11.455824Z" - }, - { - "id": 129, - "name": "AquaPro Rice Cooker 402Y", - "description": "AquaPro Rice Cooker 402Y is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.63, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 921, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 55.9, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 44.1, - "mass": 0.86, - "unit": "kg" - } - ], - "components": [ - { - "id": 1291, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1292, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1293, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 129, - "url": "https://via.placeholder.com/400x300/03a221/ffffff?text=AquaPro+Rice+Cooker+402Y", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-08-16T09:16:11.455917Z", - "updated_at": "2025-07-16T09:16:11.455917Z" - }, - { - "id": 130, - "name": "AquaPro Monitor 295C", - "description": "AquaPro Monitor 295C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.96, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1157, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.9, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 24.3, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 38.8, - "mass": 0.43, - "unit": "kg" - } - ], - "components": [ - { - "id": 1301, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1302, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1303, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1304, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 130, - "url": "https://via.placeholder.com/400x300/07b9a6/ffffff?text=AquaPro+Monitor+295C", - "description": "Monitor product image" - } - ], - "created_at": "2024-10-22T09:16:11.456018Z", - "updated_at": "2025-05-17T09:16:11.456018Z" - }, - { - "id": 131, - "name": "NeoCook Coffee Maker 459Q", - "description": "NeoCook Coffee Maker 459Q is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1518, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 14.3, - "mass": 0.55, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.6, - "mass": 1.04, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 57.1, - "mass": 1.69, - "unit": "kg" - } - ], - "components": [ - { - "id": 1311, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1312, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 131, - "url": "https://via.placeholder.com/400x300/03bb89/ffffff?text=NeoCook+Coffee+Maker+459Q", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-11-28T09:16:11.456102Z", - "updated_at": "2025-05-09T09:16:11.456102Z" - }, - { - "id": 132, - "name": "NeoCook Monitor 111M", - "description": "NeoCook Monitor 111M is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.37, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2091, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 42.4, - "mass": 0.74, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 32.0, - "mass": 0.96, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.6, - "mass": 0.29, - "unit": "kg" - } - ], - "components": [ - { - "id": 1321, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1322, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1323, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1324, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1325, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 132, - "url": "https://via.placeholder.com/400x300/0d2198/ffffff?text=NeoCook+Monitor+111M", - "description": "Monitor product image" - } - ], - "created_at": "2024-04-26T09:16:11.456170Z", - "updated_at": "2025-07-22T09:16:11.456170Z" - }, - { - "id": 133, - "name": "AquaPro Iron 797U", - "description": "AquaPro Iron 797U is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1019, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 13.1, - "mass": 0.15, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 59.6, - "mass": 1.6, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 12.1, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 15.2, - "mass": 0.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 1331, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1332, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 1333, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 133, - "url": "https://via.placeholder.com/400x300/07d5df/ffffff?text=AquaPro+Iron+797U", - "description": "Iron product image" - } - ], - "created_at": "2024-08-01T09:16:11.456224Z", - "updated_at": "2025-04-29T09:16:11.456224Z" - }, - { - "id": 134, - "name": "ZenGear Monitor 834P", - "description": "ZenGear Monitor 834P is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.7, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 948, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 66.7, - "mass": 2.16, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.3, - "mass": 1.18, - "unit": "kg" - } - ], - "components": [ - { - "id": 1341, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1342, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1343, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1344, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 134, - "url": "https://via.placeholder.com/400x300/0703f2/ffffff?text=ZenGear+Monitor+834P", - "description": "Monitor product image" - } - ], - "created_at": "2024-12-19T09:16:11.456285Z", - "updated_at": "2025-04-25T09:16:11.456285Z" - }, - { - "id": 135, - "name": "PureLife Kettle 981Q", - "description": "PureLife Kettle 981Q is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.52, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1716, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 53.8, - "mass": 1.18, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 19.8, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 26.4, - "mass": 0.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 1351, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1352, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1353, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1354, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 135, - "url": "https://via.placeholder.com/400x300/0e823d/ffffff?text=PureLife+Kettle+981Q", - "description": "Kettle product image" - } - ], - "created_at": "2025-03-08T09:16:11.456359Z", - "updated_at": "2025-07-10T09:16:11.456359Z" - }, - { - "id": 136, - "name": "SmartHome Humidifier 259V", - "description": "SmartHome Humidifier 259V is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2079, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 9.4, - "mass": 0.24, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 48.1, - "mass": 1.84, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 42.5, - "mass": 1.08, - "unit": "kg" - } - ], - "components": [ - { - "id": 1361, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1362, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1363, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1364, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1365, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 136, - "url": "https://via.placeholder.com/400x300/0bf14d/ffffff?text=SmartHome+Humidifier+259V", - "description": "Humidifier product image" - } - ], - "created_at": "2025-02-24T09:16:11.456420Z", - "updated_at": "2025-07-31T09:16:11.456420Z" - }, - { - "id": 137, - "name": "ZenGear Coffee Maker 637O", - "description": "ZenGear Coffee Maker 637O is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.55, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1587, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 21.1, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 50.5, - "mass": 1.94, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.4, - "mass": 0.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 1371, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1372, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1373, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1374, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1375, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 137, - "url": "https://via.placeholder.com/400x300/09b645/ffffff?text=ZenGear+Coffee+Maker+637O", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-01-22T09:16:11.456499Z", - "updated_at": "2025-05-07T09:16:11.456499Z" - }, - { - "id": 138, - "name": "CleanWave Kettle 783H", - "description": "CleanWave Kettle 783H is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.98, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1534, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 37.3, - "mass": 1.38, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 30.3, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 32.4, - "mass": 0.84, - "unit": "kg" - } - ], - "components": [ - { - "id": 1381, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1382, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1383, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 138, - "url": "https://via.placeholder.com/400x300/09b511/ffffff?text=CleanWave+Kettle+783H", - "description": "Kettle product image" - } - ], - "created_at": "2024-11-08T09:16:11.456556Z", - "updated_at": "2025-07-19T09:16:11.456556Z" - }, - { - "id": 139, - "name": "ZenGear Rice Cooker 406O", - "description": "ZenGear Rice Cooker 406O is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.24, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1764, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.0, - "mass": 1.2, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 33.8, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 11.0, - "mass": 0.13, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 19.1, - "mass": 0.23, - "unit": "kg" - } - ], - "components": [ - { - "id": 1391, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1392, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1393, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1394, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1395, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 139, - "url": "https://via.placeholder.com/400x300/0c089d/ffffff?text=ZenGear+Rice+Cooker+406O", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-02-04T09:16:11.456803Z", - "updated_at": "2025-07-19T09:16:11.456803Z" - }, - { - "id": 140, - "name": "ChefMate Humidifier 939C", - "description": "ChefMate Humidifier 939C is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.5, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2065, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 31.7, - "mass": 1.12, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 68.3, - "mass": 2.11, - "unit": "kg" - } - ], - "components": [ - { - "id": 1401, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1402, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1403, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1404, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1405, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 140, - "url": "https://via.placeholder.com/400x300/0cfc83/ffffff?text=ChefMate+Humidifier+939C", - "description": "Humidifier product image" - } - ], - "created_at": "2024-09-24T09:16:11.456881Z", - "updated_at": "2025-06-08T09:16:11.456881Z" - }, - { - "id": 141, - "name": "NeoCook Humidifier 447M", - "description": "NeoCook Humidifier 447M is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.72, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2132, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 67.1, - "mass": 2.21, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 32.9, - "mass": 0.95, - "unit": "kg" - } - ], - "components": [ - { - "id": 1411, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1412, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 141, - "url": "https://via.placeholder.com/400x300/0d3b4c/ffffff?text=NeoCook+Humidifier+447M", - "description": "Humidifier product image" - } - ], - "created_at": "2025-01-04T09:16:11.456942Z", - "updated_at": "2025-05-27T09:16:11.456942Z" - }, - { - "id": 142, - "name": "ZenGear Fan 908F", - "description": "ZenGear Fan 908F is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.29, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1815, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 27.9, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 72.1, - "mass": 2.31, - "unit": "kg" - } - ], - "components": [ - { - "id": 1421, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1422, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1423, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1424, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 142, - "url": "https://via.placeholder.com/400x300/073224/ffffff?text=ZenGear+Fan+908F", - "description": "Fan product image" - } - ], - "created_at": "2025-03-18T09:16:11.456997Z", - "updated_at": "2025-05-11T09:16:11.456997Z" - }, - { - "id": 143, - "name": "PureLife Coffee Maker 690C", - "description": "PureLife Coffee Maker 690C is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 922, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 35.0, - "mass": 0.93, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 15.4, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 14.5, - "mass": 0.19, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 35.0, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 1431, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1432, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1433, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 143, - "url": "https://via.placeholder.com/400x300/0807e3/ffffff?text=PureLife+Coffee+Maker+690C", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-11-08T09:16:11.457052Z", - "updated_at": "2025-04-26T09:16:11.457052Z" - }, - { - "id": 144, - "name": "EcoTech Blender 793S", - "description": "EcoTech Blender 793S is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.18, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2141, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.2, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 56.9, - "mass": 1.54, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 16.9, - "mass": 0.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 1441, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1442, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1443, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 144, - "url": "https://via.placeholder.com/400x300/0d15de/ffffff?text=EcoTech+Blender+793S", - "description": "Blender product image" - } - ], - "created_at": "2024-08-01T09:16:11.457118Z", - "updated_at": "2025-06-08T09:16:11.457118Z" - }, - { - "id": 145, - "name": "CleanWave Kettle 715F", - "description": "CleanWave Kettle 715F is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1738, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 30.7, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 9.2, - "mass": 0.22, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 22.9, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 37.3, - "mass": 0.61, - "unit": "kg" - } - ], - "components": [ - { - "id": 1451, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1452, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1453, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1454, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1455, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 145, - "url": "https://via.placeholder.com/400x300/04e263/ffffff?text=CleanWave+Kettle+715F", - "description": "Kettle product image" - } - ], - "created_at": "2025-01-14T09:16:11.457163Z", - "updated_at": "2025-07-09T09:16:11.457163Z" - }, - { - "id": 146, - "name": "ChefMate Toaster 831R", - "description": "ChefMate Toaster 831R is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.88, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1013, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 27.3, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 15.3, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 30.7, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.7, - "mass": 0.7, - "unit": "kg" - } - ], - "components": [ - { - "id": 1461, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1462, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1463, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 146, - "url": "https://via.placeholder.com/400x300/0b1b13/ffffff?text=ChefMate+Toaster+831R", - "description": "Toaster product image" - } - ], - "created_at": "2024-12-09T09:16:11.457254Z", - "updated_at": "2025-07-17T09:16:11.457254Z" - }, - { - "id": 147, - "name": "EcoTech Coffee Maker 849K", - "description": "EcoTech Coffee Maker 849K is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.29, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1389, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 82.2, - "mass": 1.48, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 17.8, - "mass": 0.69, - "unit": "kg" - } - ], - "components": [ - { - "id": 1471, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1472, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 147, - "url": "https://via.placeholder.com/400x300/05f647/ffffff?text=EcoTech+Coffee+Maker+849K", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-12-01T09:16:11.457313Z", - "updated_at": "2025-05-03T09:16:11.457313Z" - }, - { - "id": 148, - "name": "SmartHome Fan 907H", - "description": "SmartHome Fan 907H is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.28, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1964, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 32.5, - "mass": 0.74, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 33.8, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.8, - "mass": 1.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 1481, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1482, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1483, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1484, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 148, - "url": "https://via.placeholder.com/400x300/0c3578/ffffff?text=SmartHome+Fan+907H", - "description": "Fan product image" - } - ], - "created_at": "2025-04-02T09:16:11.457377Z", - "updated_at": "2025-07-14T09:16:11.457377Z" - }, - { - "id": 149, - "name": "CleanWave Iron 405P", - "description": "CleanWave Iron 405P is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.5, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 811, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 29.7, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 70.3, - "mass": 2.04, - "unit": "kg" - } - ], - "components": [ - { - "id": 1491, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1492, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 1493, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 149, - "url": "https://via.placeholder.com/400x300/060da7/ffffff?text=CleanWave+Iron+405P", - "description": "Iron product image" - } - ], - "created_at": "2025-04-13T09:16:11.457418Z", - "updated_at": "2025-06-25T09:16:11.457418Z" - }, - { - "id": 150, - "name": "PureLife Iron 151K", - "description": "PureLife Iron 151K is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1239, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 13.9, - "mass": 0.27, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 45.1, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 11.5, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.5, - "mass": 0.97, - "unit": "kg" - } - ], - "components": [ - { - "id": 1501, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1502, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 1503, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 1504, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 150, - "url": "https://via.placeholder.com/400x300/054030/ffffff?text=PureLife+Iron+151K", - "description": "Iron product image" - } - ], - "created_at": "2025-03-09T09:16:11.457478Z", - "updated_at": "2025-05-10T09:16:11.457478Z" - }, - { - "id": 151, - "name": "ZenGear Fan 425K", - "description": "ZenGear Fan 425K is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1316, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 27.8, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 72.2, - "mass": 0.79, - "unit": "kg" - } - ], - "components": [ - { - "id": 1511, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1512, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1513, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 151, - "url": "https://via.placeholder.com/400x300/05c99a/ffffff?text=ZenGear+Fan+425K", - "description": "Fan product image" - } - ], - "created_at": "2024-05-30T09:16:11.457516Z", - "updated_at": "2025-06-02T09:16:11.457516Z" - }, - { - "id": 152, - "name": "EcoTech Fan 802P", - "description": "EcoTech Fan 802P is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1916, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 44.8, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 55.2, - "mass": 1.07, - "unit": "kg" - } - ], - "components": [ - { - "id": 1521, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1522, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1523, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1524, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 152, - "url": "https://via.placeholder.com/400x300/039628/ffffff?text=EcoTech+Fan+802P", - "description": "Fan product image" - } - ], - "created_at": "2024-07-26T09:16:11.457559Z", - "updated_at": "2025-05-26T09:16:11.457559Z" - }, - { - "id": 153, - "name": "NeoCook Iron 387O", - "description": "NeoCook Iron 387O is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.91, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 900, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 21.5, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 56.9, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 21.5, - "mass": 0.68, - "unit": "kg" - } - ], - "components": [ - { - "id": 1531, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1532, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 1533, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 153, - "url": "https://via.placeholder.com/400x300/03d8f1/ffffff?text=NeoCook+Iron+387O", - "description": "Iron product image" - } - ], - "created_at": "2025-03-23T09:16:11.457614Z", - "updated_at": "2025-07-17T09:16:11.457614Z" - }, - { - "id": 154, - "name": "ChefMate Rice Cooker 645N", - "description": "ChefMate Rice Cooker 645N is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 950, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 21.1, - "mass": 0.82, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 78.9, - "mass": 3.09, - "unit": "kg" - } - ], - "components": [ - { - "id": 1541, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1542, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1543, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1544, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1545, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 154, - "url": "https://via.placeholder.com/400x300/05f50e/ffffff?text=ChefMate+Rice+Cooker+645N", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-03-24T09:16:11.457658Z", - "updated_at": "2025-05-24T09:16:11.457658Z" - }, - { - "id": 155, - "name": "SmartHome Coffee Maker 347W", - "description": "SmartHome Coffee Maker 347W is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1116, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 44.3, - "mass": 1.0, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 55.7, - "mass": 0.85, - "unit": "kg" - } - ], - "components": [ - { - "id": 1551, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1552, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1553, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1554, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1555, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 155, - "url": "https://via.placeholder.com/400x300/09b7f2/ffffff?text=SmartHome+Coffee+Maker+347W", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-06-06T09:16:11.457697Z", - "updated_at": "2025-05-31T09:16:11.457697Z" - }, - { - "id": 156, - "name": "SmartHome Humidifier 635C", - "description": "SmartHome Humidifier 635C is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1267, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 26.7, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 13.3, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 60.0, - "mass": 1.28, - "unit": "kg" - } - ], - "components": [ - { - "id": 1561, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1562, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1563, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1564, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1565, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 156, - "url": "https://via.placeholder.com/400x300/08a081/ffffff?text=SmartHome+Humidifier+635C", - "description": "Humidifier product image" - } - ], - "created_at": "2025-02-02T09:16:11.457947Z", - "updated_at": "2025-06-06T09:16:11.457947Z" - }, - { - "id": 157, - "name": "PureLife Iron 639P", - "description": "PureLife Iron 639P is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.06, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1800, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.0, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 72.0, - "mass": 1.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 1571, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1572, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 1573, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 157, - "url": "https://via.placeholder.com/400x300/08ca64/ffffff?text=PureLife+Iron+639P", - "description": "Iron product image" - } - ], - "created_at": "2025-03-31T09:16:11.458028Z", - "updated_at": "2025-06-02T09:16:11.458028Z" - }, - { - "id": 158, - "name": "PureLife Coffee Maker 399D", - "description": "PureLife Coffee Maker 399D is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1604, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 31.2, - "mass": 1.14, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 44.1, - "mass": 1.14, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 24.7, - "mass": 0.38, - "unit": "kg" - } - ], - "components": [ - { - "id": 1581, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1582, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 158, - "url": "https://via.placeholder.com/400x300/0abfac/ffffff?text=PureLife+Coffee+Maker+399D", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-01-28T09:16:11.458092Z", - "updated_at": "2025-04-30T09:16:11.458092Z" - }, - { - "id": 159, - "name": "ZenGear Iron 956L", - "description": "ZenGear Iron 956L is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.97, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1118, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 30.1, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 32.2, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 17.5, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.2, - "mass": 0.58, - "unit": "kg" - } - ], - "components": [ - { - "id": 1591, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1592, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 1593, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 159, - "url": "https://via.placeholder.com/400x300/097bdb/ffffff?text=ZenGear+Iron+956L", - "description": "Iron product image" - } - ], - "created_at": "2024-12-05T09:16:11.458161Z", - "updated_at": "2025-07-25T09:16:11.458161Z" - }, - { - "id": 160, - "name": "PureLife Air Purifier 644M", - "description": "PureLife Air Purifier 644M is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.08, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1495, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 39.7, - "mass": 0.93, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 11.1, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 20.6, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.6, - "mass": 1.0, - "unit": "kg" - } - ], - "components": [ - { - "id": 1601, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1602, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1603, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 1604, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 160, - "url": "https://via.placeholder.com/400x300/073e17/ffffff?text=PureLife+Air+Purifier+644M", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-08-24T09:16:11.458215Z", - "updated_at": "2025-06-09T09:16:11.458215Z" - }, - { - "id": 161, - "name": "ChefMate Coffee Maker 778K", - "description": "ChefMate Coffee Maker 778K is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.46, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1729, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 13.2, - "mass": 0.14, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 9.6, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 38.2, - "mass": 1.48, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.0, - "mass": 1.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 1611, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1612, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1613, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1614, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1615, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 161, - "url": "https://via.placeholder.com/400x300/01bace/ffffff?text=ChefMate+Coffee+Maker+778K", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-04-18T09:16:11.458338Z", - "updated_at": "2025-05-01T09:16:11.458338Z" - }, - { - "id": 162, - "name": "ChefMate Fan 148C", - "description": "ChefMate Fan 148C is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1754, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 37.1, - "mass": 1.42, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 40.9, - "mass": 1.48, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.0, - "mass": 0.22, - "unit": "kg" - } - ], - "components": [ - { - "id": 1621, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1622, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1623, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1624, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 162, - "url": "https://via.placeholder.com/400x300/0d0929/ffffff?text=ChefMate+Fan+148C", - "description": "Fan product image" - } - ], - "created_at": "2024-05-26T09:16:11.458381Z", - "updated_at": "2025-05-22T09:16:11.458381Z" - }, - { - "id": 163, - "name": "SmartHome Fan 954Q", - "description": "SmartHome Fan 954Q is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.35, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1141, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 78.5, - "mass": 1.88, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 21.5, - "mass": 0.5, - "unit": "kg" - } - ], - "components": [ - { - "id": 1631, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1632, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1633, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 163, - "url": "https://via.placeholder.com/400x300/072ef0/ffffff?text=SmartHome+Fan+954Q", - "description": "Fan product image" - } - ], - "created_at": "2024-08-17T09:16:11.458431Z", - "updated_at": "2025-06-02T09:16:11.458431Z" - }, - { - "id": 164, - "name": "PureLife Blender 806F", - "description": "PureLife Blender 806F is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 993, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.8, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 45.6, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 13.6, - "mass": 0.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 1641, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1642, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1643, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 1644, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 1645, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 164, - "url": "https://via.placeholder.com/400x300/03a7bb/ffffff?text=PureLife+Blender+806F", - "description": "Blender product image" - } - ], - "created_at": "2025-01-08T09:16:11.458628Z", - "updated_at": "2025-07-14T09:16:11.458628Z" - }, - { - "id": 165, - "name": "CleanWave Blender 124H", - "description": "CleanWave Blender 124H is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2192, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.6, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 60.4, - "mass": 2.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 1651, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1652, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1653, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 1654, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 1655, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 165, - "url": "https://via.placeholder.com/400x300/019b00/ffffff?text=CleanWave+Blender+124H", - "description": "Blender product image" - } - ], - "created_at": "2024-12-12T09:16:11.458679Z", - "updated_at": "2025-07-03T09:16:11.458679Z" - }, - { - "id": 166, - "name": "ChefMate Monitor 543X", - "description": "ChefMate Monitor 543X is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.8, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1043, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 27.9, - "mass": 0.58, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.3, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 14.4, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 40.4, - "mass": 1.46, - "unit": "kg" - } - ], - "components": [ - { - "id": 1661, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1662, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 166, - "url": "https://via.placeholder.com/400x300/0d7224/ffffff?text=ChefMate+Monitor+543X", - "description": "Monitor product image" - } - ], - "created_at": "2025-01-27T09:16:11.458720Z", - "updated_at": "2025-06-05T09:16:11.458720Z" - }, - { - "id": 167, - "name": "ZenGear Iron 514I", - "description": "ZenGear Iron 514I is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.01, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1492, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 56.5, - "mass": 1.8, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 43.5, - "mass": 1.63, - "unit": "kg" - } - ], - "components": [ - { - "id": 1671, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1672, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 167, - "url": "https://via.placeholder.com/400x300/0b2876/ffffff?text=ZenGear+Iron+514I", - "description": "Iron product image" - } - ], - "created_at": "2025-02-16T09:16:11.458757Z", - "updated_at": "2025-06-21T09:16:11.458757Z" - }, - { - "id": 168, - "name": "AquaPro Blender 568J", - "description": "AquaPro Blender 568J is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 932, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 7.0, - "mass": 0.11, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 42.3, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 40.8, - "mass": 1.61, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 9.9, - "mass": 0.16, - "unit": "kg" - } - ], - "components": [ - { - "id": 1681, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1682, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1683, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 168, - "url": "https://via.placeholder.com/400x300/0a0983/ffffff?text=AquaPro+Blender+568J", - "description": "Blender product image" - } - ], - "created_at": "2024-04-29T09:16:11.458791Z", - "updated_at": "2025-07-02T09:16:11.458791Z" - }, - { - "id": 169, - "name": "SmartHome Coffee Maker 447B", - "description": "SmartHome Coffee Maker 447B is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1942, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 38.8, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 9.5, - "mass": 0.13, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 25.9, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.9, - "mass": 0.85, - "unit": "kg" - } - ], - "components": [ - { - "id": 1691, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1692, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 169, - "url": "https://via.placeholder.com/400x300/05bf0b/ffffff?text=SmartHome+Coffee+Maker+447B", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-03-19T09:16:11.458829Z", - "updated_at": "2025-07-14T09:16:11.458829Z" - }, - { - "id": 170, - "name": "ZenGear Humidifier 398I", - "description": "ZenGear Humidifier 398I is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.81, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1109, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 27.3, - "mass": 1.0, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.3, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 42.4, - "mass": 1.65, - "unit": "kg" - } - ], - "components": [ - { - "id": 1701, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1702, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1703, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1704, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 170, - "url": "https://via.placeholder.com/400x300/061b21/ffffff?text=ZenGear+Humidifier+398I", - "description": "Humidifier product image" - } - ], - "created_at": "2025-04-18T09:16:11.458866Z", - "updated_at": "2025-06-16T09:16:11.458866Z" - }, - { - "id": 171, - "name": "NeoCook Toaster 221Z", - "description": "NeoCook Toaster 221Z is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1265, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 46.4, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 53.6, - "mass": 0.54, - "unit": "kg" - } - ], - "components": [ - { - "id": 1711, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1712, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1713, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1714, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 1715, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 171, - "url": "https://via.placeholder.com/400x300/08cc4b/ffffff?text=NeoCook+Toaster+221Z", - "description": "Toaster product image" - } - ], - "created_at": "2024-11-01T09:16:11.458898Z", - "updated_at": "2025-07-30T09:16:11.458898Z" - }, - { - "id": 172, - "name": "SmartHome Coffee Maker 126B", - "description": "SmartHome Coffee Maker 126B is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1736, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 29.5, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 29.5, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 40.9, - "mass": 0.61, - "unit": "kg" - } - ], - "components": [ - { - "id": 1721, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1722, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1723, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 172, - "url": "https://via.placeholder.com/400x300/074699/ffffff?text=SmartHome+Coffee+Maker+126B", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-12-15T09:16:11.458933Z", - "updated_at": "2025-05-08T09:16:11.458933Z" - }, - { - "id": 173, - "name": "ZenGear Fan 130W", - "description": "ZenGear Fan 130W is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1338, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 22.6, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.0, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 49.5, - "mass": 1.18, - "unit": "kg" - } - ], - "components": [ - { - "id": 1731, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1732, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 173, - "url": "https://via.placeholder.com/400x300/033a12/ffffff?text=ZenGear+Fan+130W", - "description": "Fan product image" - } - ], - "created_at": "2024-12-22T09:16:11.458986Z", - "updated_at": "2025-07-22T09:16:11.458986Z" - }, - { - "id": 174, - "name": "CleanWave Iron 282V", - "description": "CleanWave Iron 282V is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.31, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 871, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 49.0, - "mass": 1.28, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 51.0, - "mass": 1.11, - "unit": "kg" - } - ], - "components": [ - { - "id": 1741, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1742, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 174, - "url": "https://via.placeholder.com/400x300/0659ce/ffffff?text=CleanWave+Iron+282V", - "description": "Iron product image" - } - ], - "created_at": "2024-04-23T09:16:11.459039Z", - "updated_at": "2025-06-21T09:16:11.459039Z" - }, - { - "id": 175, - "name": "ZenGear Kettle 134D", - "description": "ZenGear Kettle 134D is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.38, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1026, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 22.6, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.1, - "mass": 0.98, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 38.3, - "mass": 1.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 1751, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1752, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 175, - "url": "https://via.placeholder.com/400x300/0e1694/ffffff?text=ZenGear+Kettle+134D", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-16T09:16:11.459086Z", - "updated_at": "2025-06-28T09:16:11.459086Z" - }, - { - "id": 176, - "name": "ChefMate Monitor 551S", - "description": "ChefMate Monitor 551S is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.33, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1685, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 49.0, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 51.0, - "mass": 1.22, - "unit": "kg" - } - ], - "components": [ - { - "id": 1761, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1762, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1763, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1764, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1765, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 176, - "url": "https://via.placeholder.com/400x300/0e72cd/ffffff?text=ChefMate+Monitor+551S", - "description": "Monitor product image" - } - ], - "created_at": "2024-09-24T09:16:11.459141Z", - "updated_at": "2025-07-10T09:16:11.459141Z" - }, - { - "id": 177, - "name": "EcoTech Fan 100N", - "description": "EcoTech Fan 100N is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2085, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 37.8, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 18.9, - "mass": 0.21, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 43.2, - "mass": 0.58, - "unit": "kg" - } - ], - "components": [ - { - "id": 1771, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1772, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1773, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 1774, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 177, - "url": "https://via.placeholder.com/400x300/05ada2/ffffff?text=EcoTech+Fan+100N", - "description": "Fan product image" - } - ], - "created_at": "2024-10-23T09:16:11.459190Z", - "updated_at": "2025-05-09T09:16:11.459190Z" - }, - { - "id": 178, - "name": "ChefMate Humidifier 376F", - "description": "ChefMate Humidifier 376F is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1596, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.4, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 58.6, - "mass": 2.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 1781, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1782, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1783, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 178, - "url": "https://via.placeholder.com/400x300/0c6482/ffffff?text=ChefMate+Humidifier+376F", - "description": "Humidifier product image" - } - ], - "created_at": "2025-04-04T09:16:11.459226Z", - "updated_at": "2025-07-25T09:16:11.459226Z" - }, - { - "id": 179, - "name": "ChefMate Monitor 876P", - "description": "ChefMate Monitor 876P is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.56, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1998, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 46.7, - "mass": 1.4, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 53.3, - "mass": 1.8, - "unit": "kg" - } - ], - "components": [ - { - "id": 1791, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1792, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1793, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1794, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 179, - "url": "https://via.placeholder.com/400x300/069c1b/ffffff?text=ChefMate+Monitor+876P", - "description": "Monitor product image" - } - ], - "created_at": "2025-03-07T09:16:11.459264Z", - "updated_at": "2025-06-02T09:16:11.459264Z" - }, - { - "id": 180, - "name": "PureLife Blender 523P", - "description": "PureLife Blender 523P is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1200, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 65.1, - "mass": 2.49, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 34.9, - "mass": 1.13, - "unit": "kg" - } - ], - "components": [ - { - "id": 1801, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1802, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1803, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 180, - "url": "https://via.placeholder.com/400x300/07724e/ffffff?text=PureLife+Blender+523P", - "description": "Blender product image" - } - ], - "created_at": "2025-01-11T09:16:11.459308Z", - "updated_at": "2025-07-21T09:16:11.459308Z" - }, - { - "id": 181, - "name": "PureLife Rice Cooker 749I", - "description": "PureLife Rice Cooker 749I is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.58, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 894, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 16.4, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 22.4, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 32.2, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.9, - "mass": 1.12, - "unit": "kg" - } - ], - "components": [ - { - "id": 1811, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1812, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1813, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1814, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 181, - "url": "https://via.placeholder.com/400x300/0bc755/ffffff?text=PureLife+Rice+Cooker+749I", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-01-29T09:16:11.459366Z", - "updated_at": "2025-04-25T09:16:11.459366Z" - }, - { - "id": 182, - "name": "NeoCook Blender 829D", - "description": "NeoCook Blender 829D is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.11, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2108, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 43.9, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 29.8, - "mass": 0.87, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 26.3, - "mass": 0.85, - "unit": "kg" - } - ], - "components": [ - { - "id": 1821, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1822, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 182, - "url": "https://via.placeholder.com/400x300/07ebac/ffffff?text=NeoCook+Blender+829D", - "description": "Blender product image" - } - ], - "created_at": "2024-10-14T09:16:11.459428Z", - "updated_at": "2025-06-11T09:16:11.459428Z" - }, - { - "id": 183, - "name": "SmartHome Monitor 878G", - "description": "SmartHome Monitor 878G is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.42, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1969, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 31.1, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 10.9, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 20.2, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 37.8, - "mass": 0.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 1831, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1832, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1833, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1834, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 183, - "url": "https://via.placeholder.com/400x300/0eeb79/ffffff?text=SmartHome+Monitor+878G", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-27T09:16:11.459490Z", - "updated_at": "2025-04-28T09:16:11.459490Z" - }, - { - "id": 184, - "name": "NeoCook Coffee Maker 492J", - "description": "NeoCook Coffee Maker 492J is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1698, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 32.9, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.1, - "mass": 1.21, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 36.0, - "mass": 0.56, - "unit": "kg" - } - ], - "components": [ - { - "id": 1841, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1842, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1843, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1844, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 1845, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 184, - "url": "https://via.placeholder.com/400x300/0cea6a/ffffff?text=NeoCook+Coffee+Maker+492J", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-11-09T09:16:11.459534Z", - "updated_at": "2025-05-18T09:16:11.459534Z" - }, - { - "id": 185, - "name": "NeoCook Blender 191L", - "description": "NeoCook Blender 191L is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1870, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 33.5, - "mass": 0.52, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 7.2, - "mass": 0.27, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.5, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.7, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 1851, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1852, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 1853, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 1854, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 1855, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 185, - "url": "https://via.placeholder.com/400x300/0bfcef/ffffff?text=NeoCook+Blender+191L", - "description": "Blender product image" - } - ], - "created_at": "2025-03-10T09:16:11.459585Z", - "updated_at": "2025-07-23T09:16:11.459585Z" - }, - { - "id": 186, - "name": "NeoCook Iron 604K", - "description": "NeoCook Iron 604K is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.01, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1356, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.5, - "mass": 0.99, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 42.9, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 31.6, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 1861, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1862, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 186, - "url": "https://via.placeholder.com/400x300/0a08f5/ffffff?text=NeoCook+Iron+604K", - "description": "Iron product image" - } - ], - "created_at": "2024-12-10T09:16:11.459621Z", - "updated_at": "2025-05-07T09:16:11.459621Z" - }, - { - "id": 187, - "name": "ChefMate Iron 615G", - "description": "ChefMate Iron 615G is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1227, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 9.4, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 30.4, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 26.3, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.9, - "mass": 1.02, - "unit": "kg" - } - ], - "components": [ - { - "id": 1871, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 1872, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 187, - "url": "https://via.placeholder.com/400x300/048ca6/ffffff?text=ChefMate+Iron+615G", - "description": "Iron product image" - } - ], - "created_at": "2024-03-26T09:16:11.459662Z", - "updated_at": "2025-06-06T09:16:11.459662Z" - }, - { - "id": 188, - "name": "NeoCook Rice Cooker 721C", - "description": "NeoCook Rice Cooker 721C is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.62, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1689, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 43.0, - "mass": 0.98, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 40.2, - "mass": 1.34, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 16.8, - "mass": 0.37, - "unit": "kg" - } - ], - "components": [ - { - "id": 1881, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1882, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1883, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1884, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 1885, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 188, - "url": "https://via.placeholder.com/400x300/03116a/ffffff?text=NeoCook+Rice+Cooker+721C", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-04-17T09:16:11.459724Z", - "updated_at": "2025-05-24T09:16:11.459724Z" - }, - { - "id": 189, - "name": "ChefMate Monitor 877K", - "description": "ChefMate Monitor 877K is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1175, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 32.8, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 12.0, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 30.4, - "mass": 1.16, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 24.8, - "mass": 0.58, - "unit": "kg" - } - ], - "components": [ - { - "id": 1891, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1892, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 189, - "url": "https://via.placeholder.com/400x300/05a4a4/ffffff?text=ChefMate+Monitor+877K", - "description": "Monitor product image" - } - ], - "created_at": "2024-06-19T09:16:11.459760Z", - "updated_at": "2025-05-24T09:16:11.459760Z" - }, - { - "id": 190, - "name": "AquaPro Humidifier 762F", - "description": "AquaPro Humidifier 762F is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1156, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 40.0, - "mass": 0.55, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.2, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 18.8, - "mass": 0.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 1901, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1902, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1903, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1904, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 190, - "url": "https://via.placeholder.com/400x300/03f749/ffffff?text=AquaPro+Humidifier+762F", - "description": "Humidifier product image" - } - ], - "created_at": "2024-09-01T09:16:11.459793Z", - "updated_at": "2025-07-20T09:16:11.459793Z" - }, - { - "id": 191, - "name": "NeoCook Humidifier 113P", - "description": "NeoCook Humidifier 113P is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2075, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 55.0, - "mass": 1.87, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 45.0, - "mass": 1.38, - "unit": "kg" - } - ], - "components": [ - { - "id": 1911, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1912, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 191, - "url": "https://via.placeholder.com/400x300/0af212/ffffff?text=NeoCook+Humidifier+113P", - "description": "Humidifier product image" - } - ], - "created_at": "2024-06-25T09:16:11.459844Z", - "updated_at": "2025-05-10T09:16:11.459844Z" - }, - { - "id": 192, - "name": "PureLife Fan 544N", - "description": "PureLife Fan 544N is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.33, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1244, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 24.2, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 42.4, - "mass": 1.67, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 33.3, - "mass": 0.94, - "unit": "kg" - } - ], - "components": [ - { - "id": 1921, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1922, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 192, - "url": "https://via.placeholder.com/400x300/0a688d/ffffff?text=PureLife+Fan+544N", - "description": "Fan product image" - } - ], - "created_at": "2024-06-04T09:16:11.459889Z", - "updated_at": "2025-06-21T09:16:11.459889Z" - }, - { - "id": 193, - "name": "CleanWave Monitor 173R", - "description": "CleanWave Monitor 173R is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 920, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 42.0, - "mass": 1.53, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 29.4, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.7, - "mass": 0.98, - "unit": "kg" - } - ], - "components": [ - { - "id": 1931, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1932, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1933, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 193, - "url": "https://via.placeholder.com/400x300/01c58a/ffffff?text=CleanWave+Monitor+173R", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-06T09:16:11.459924Z", - "updated_at": "2025-07-06T09:16:11.459924Z" - }, - { - "id": 194, - "name": "ChefMate Monitor 848C", - "description": "ChefMate Monitor 848C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.55, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1298, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 60.4, - "mass": 2.13, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.6, - "mass": 0.75, - "unit": "kg" - } - ], - "components": [ - { - "id": 1941, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1942, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1943, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 194, - "url": "https://via.placeholder.com/400x300/0d129a/ffffff?text=ChefMate+Monitor+848C", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-11T09:16:11.459950Z", - "updated_at": "2025-07-26T09:16:11.459950Z" - }, - { - "id": 195, - "name": "NeoCook Monitor 299Z", - "description": "NeoCook Monitor 299Z is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.35, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1163, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 45.0, - "mass": 1.69, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 55.0, - "mass": 2.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 1951, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 1952, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 195, - "url": "https://via.placeholder.com/400x300/0475c3/ffffff?text=NeoCook+Monitor+299Z", - "description": "Monitor product image" - } - ], - "created_at": "2024-05-09T09:16:11.459985Z", - "updated_at": "2025-05-02T09:16:11.459985Z" - }, - { - "id": 196, - "name": "ZenGear Kettle 605E", - "description": "ZenGear Kettle 605E is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.13, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 882, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 23.4, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 22.8, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 26.1, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 27.7, - "mass": 0.46, - "unit": "kg" - } - ], - "components": [ - { - "id": 1961, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1962, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1963, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1964, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 1965, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 196, - "url": "https://via.placeholder.com/400x300/051679/ffffff?text=ZenGear+Kettle+605E", - "description": "Kettle product image" - } - ], - "created_at": "2024-10-22T09:16:11.460074Z", - "updated_at": "2025-07-08T09:16:11.460074Z" - }, - { - "id": 197, - "name": "ChefMate Fan 829A", - "description": "ChefMate Fan 829A is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.24, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1651, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 9.3, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 10.9, - "mass": 0.15, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 42.6, - "mass": 1.62, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 37.2, - "mass": 1.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 1971, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 1972, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 1973, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 197, - "url": "https://via.placeholder.com/400x300/038a09/ffffff?text=ChefMate+Fan+829A", - "description": "Fan product image" - } - ], - "created_at": "2024-07-18T09:16:11.460140Z", - "updated_at": "2025-04-30T09:16:11.460140Z" - }, - { - "id": 198, - "name": "CleanWave Blender 175J", - "description": "CleanWave Blender 175J is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 829, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 15.2, - "mass": 0.16, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 42.8, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 23.9, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 18.1, - "mass": 0.69, - "unit": "kg" - } - ], - "components": [ - { - "id": 1981, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 1982, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 198, - "url": "https://via.placeholder.com/400x300/028e3f/ffffff?text=CleanWave+Blender+175J", - "description": "Blender product image" - } - ], - "created_at": "2025-03-31T09:16:11.460191Z", - "updated_at": "2025-07-07T09:16:11.460191Z" - }, - { - "id": 199, - "name": "ChefMate Humidifier 238X", - "description": "ChefMate Humidifier 238X is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.96, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1673, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 64.9, - "mass": 1.12, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 35.1, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 1991, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1992, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 1993, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 199, - "url": "https://via.placeholder.com/400x300/05d368/ffffff?text=ChefMate+Humidifier+238X", - "description": "Humidifier product image" - } - ], - "created_at": "2025-04-07T09:16:11.460242Z", - "updated_at": "2025-06-09T09:16:11.460242Z" - }, - { - "id": 200, - "name": "AquaPro Fan 496Z", - "description": "AquaPro Fan 496Z is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.11, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2153, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 30.5, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 25.7, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 11.4, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 32.4, - "mass": 0.67, - "unit": "kg" - } - ], - "components": [ - { - "id": 2001, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2002, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 200, - "url": "https://via.placeholder.com/400x300/034f85/ffffff?text=AquaPro+Fan+496Z", - "description": "Fan product image" - } - ], - "created_at": "2025-03-22T09:16:11.460292Z", - "updated_at": "2025-06-20T09:16:11.460292Z" - }, - { - "id": 201, - "name": "NeoCook Blender 309E", - "description": "NeoCook Blender 309E is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2087, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 38.2, - "mass": 0.96, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 16.8, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 45.0, - "mass": 1.6, - "unit": "kg" - } - ], - "components": [ - { - "id": 2011, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2012, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 201, - "url": "https://via.placeholder.com/400x300/058ce4/ffffff?text=NeoCook+Blender+309E", - "description": "Blender product image" - } - ], - "created_at": "2024-09-09T09:16:11.460352Z", - "updated_at": "2025-06-24T09:16:11.460352Z" - }, - { - "id": 202, - "name": "NeoCook Kettle 631F", - "description": "NeoCook Kettle 631F is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.45, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 957, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 37.0, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 63.0, - "mass": 1.16, - "unit": "kg" - } - ], - "components": [ - { - "id": 2021, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2022, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2023, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2024, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2025, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 202, - "url": "https://via.placeholder.com/400x300/04c645/ffffff?text=NeoCook+Kettle+631F", - "description": "Kettle product image" - } - ], - "created_at": "2024-11-19T09:16:11.460397Z", - "updated_at": "2025-07-27T09:16:11.460397Z" - }, - { - "id": 203, - "name": "SmartHome Kettle 398Z", - "description": "SmartHome Kettle 398Z is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1592, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 21.6, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 20.1, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.4, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 32.8, - "mass": 0.76, - "unit": "kg" - } - ], - "components": [ - { - "id": 2031, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2032, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2033, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2034, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2035, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 203, - "url": "https://via.placeholder.com/400x300/04379f/ffffff?text=SmartHome+Kettle+398Z", - "description": "Kettle product image" - } - ], - "created_at": "2024-07-22T09:16:11.460453Z", - "updated_at": "2025-06-15T09:16:11.460453Z" - }, - { - "id": 204, - "name": "ChefMate Air Purifier 481K", - "description": "ChefMate Air Purifier 481K is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.4, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1486, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.3, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 40.0, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 12.4, - "mass": 0.14, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 19.3, - "mass": 0.75, - "unit": "kg" - } - ], - "components": [ - { - "id": 2041, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2042, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2043, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2044, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2045, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 204, - "url": "https://via.placeholder.com/400x300/0707d1/ffffff?text=ChefMate+Air+Purifier+481K", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-04-01T09:16:11.460520Z", - "updated_at": "2025-07-05T09:16:11.460520Z" - }, - { - "id": 205, - "name": "EcoTech Blender 294G", - "description": "EcoTech Blender 294G is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.52, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1636, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 33.6, - "mass": 0.74, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.0, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 36.4, - "mass": 0.88, - "unit": "kg" - } - ], - "components": [ - { - "id": 2051, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2052, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2053, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2054, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 2055, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 205, - "url": "https://via.placeholder.com/400x300/093ae7/ffffff?text=EcoTech+Blender+294G", - "description": "Blender product image" - } - ], - "created_at": "2024-07-29T09:16:11.460570Z", - "updated_at": "2025-05-05T09:16:11.460570Z" - }, - { - "id": 206, - "name": "PureLife Kettle 641E", - "description": "PureLife Kettle 641E is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.21, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1325, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 71.2, - "mass": 1.36, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.8, - "mass": 0.72, - "unit": "kg" - } - ], - "components": [ - { - "id": 2061, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2062, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 206, - "url": "https://via.placeholder.com/400x300/071524/ffffff?text=PureLife+Kettle+641E", - "description": "Kettle product image" - } - ], - "created_at": "2024-09-16T09:16:11.460611Z", - "updated_at": "2025-05-13T09:16:11.460611Z" - }, - { - "id": 207, - "name": "ChefMate Fan 302I", - "description": "ChefMate Fan 302I is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1465, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 40.0, - "mass": 0.47, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 60.0, - "mass": 1.11, - "unit": "kg" - } - ], - "components": [ - { - "id": 2071, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2072, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2073, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 2074, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 2075, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 207, - "url": "https://via.placeholder.com/400x300/0c3abe/ffffff?text=ChefMate+Fan+302I", - "description": "Fan product image" - } - ], - "created_at": "2024-11-19T09:16:11.460661Z", - "updated_at": "2025-05-09T09:16:11.460661Z" - }, - { - "id": 208, - "name": "AquaPro Monitor 969U", - "description": "AquaPro Monitor 969U is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.81, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1779, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 56.1, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 43.9, - "mass": 1.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 2081, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2082, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2083, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2084, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 208, - "url": "https://via.placeholder.com/400x300/0481cb/ffffff?text=AquaPro+Monitor+969U", - "description": "Monitor product image" - } - ], - "created_at": "2024-11-09T09:16:11.460690Z", - "updated_at": "2025-07-19T09:16:11.460690Z" - }, - { - "id": 209, - "name": "AquaPro Air Purifier 689J", - "description": "AquaPro Air Purifier 689J is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.26, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1256, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 50.4, - "mass": 1.6, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 49.6, - "mass": 1.1, - "unit": "kg" - } - ], - "components": [ - { - "id": 2091, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2092, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2093, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 209, - "url": "https://via.placeholder.com/400x300/0d732a/ffffff?text=AquaPro+Air+Purifier+689J", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-06-10T09:16:11.460721Z", - "updated_at": "2025-06-13T09:16:11.460721Z" - }, - { - "id": 210, - "name": "NeoCook Iron 975Q", - "description": "NeoCook Iron 975Q is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1365, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 43.2, - "mass": 1.67, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.6, - "mass": 1.03, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 18.2, - "mass": 0.34, - "unit": "kg" - } - ], - "components": [ - { - "id": 2101, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 2102, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 2103, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 2104, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 210, - "url": "https://via.placeholder.com/400x300/0ecacf/ffffff?text=NeoCook+Iron+975Q", - "description": "Iron product image" - } - ], - "created_at": "2024-03-26T09:16:11.460775Z", - "updated_at": "2025-05-10T09:16:11.460775Z" - }, - { - "id": 211, - "name": "ChefMate Rice Cooker 688A", - "description": "ChefMate Rice Cooker 688A is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1670, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 45.0, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 55.0, - "mass": 1.27, - "unit": "kg" - } - ], - "components": [ - { - "id": 2111, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2112, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2113, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2114, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2115, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 211, - "url": "https://via.placeholder.com/400x300/0905d7/ffffff?text=ChefMate+Rice+Cooker+688A", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-02-26T09:16:11.460815Z", - "updated_at": "2025-06-30T09:16:11.460815Z" - }, - { - "id": 212, - "name": "CleanWave Toaster 324U", - "description": "CleanWave Toaster 324U is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.32, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1310, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.8, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 22.9, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 27.9, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 27.4, - "mass": 0.8, - "unit": "kg" - } - ], - "components": [ - { - "id": 2121, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2122, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 212, - "url": "https://via.placeholder.com/400x300/01b496/ffffff?text=CleanWave+Toaster+324U", - "description": "Toaster product image" - } - ], - "created_at": "2024-12-30T09:16:11.460855Z", - "updated_at": "2025-06-12T09:16:11.460855Z" - }, - { - "id": 213, - "name": "CleanWave Toaster 334M", - "description": "CleanWave Toaster 334M is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 929, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 51.7, - "mass": 1.52, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 14.7, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.6, - "mass": 0.78, - "unit": "kg" - } - ], - "components": [ - { - "id": 2131, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2132, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 213, - "url": "https://via.placeholder.com/400x300/0e9982/ffffff?text=CleanWave+Toaster+334M", - "description": "Toaster product image" - } - ], - "created_at": "2025-01-21T09:16:11.460888Z", - "updated_at": "2025-06-26T09:16:11.460888Z" - }, - { - "id": 214, - "name": "AquaPro Monitor 790C", - "description": "AquaPro Monitor 790C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 818, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.6, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 43.4, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 38.1, - "mass": 0.53, - "unit": "kg" - } - ], - "components": [ - { - "id": 2141, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2142, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2143, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2144, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2145, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 214, - "url": "https://via.placeholder.com/400x300/0c2322/ffffff?text=AquaPro+Monitor+790C", - "description": "Monitor product image" - } - ], - "created_at": "2024-12-09T09:16:11.460938Z", - "updated_at": "2025-05-03T09:16:11.460938Z" - }, - { - "id": 215, - "name": "ChefMate Monitor 826P", - "description": "ChefMate Monitor 826P is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.93, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1241, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 17.0, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 34.1, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 31.1, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.8, - "mass": 0.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 2151, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2152, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2153, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2154, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 215, - "url": "https://via.placeholder.com/400x300/0b2990/ffffff?text=ChefMate+Monitor+826P", - "description": "Monitor product image" - } - ], - "created_at": "2024-04-29T09:16:11.460976Z", - "updated_at": "2025-05-13T09:16:11.460976Z" - }, - { - "id": 216, - "name": "AquaPro Blender 614W", - "description": "AquaPro Blender 614W is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.14, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2046, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 56.8, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 14.8, - "mass": 0.21, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 28.4, - "mass": 0.31, - "unit": "kg" - } - ], - "components": [ - { - "id": 2161, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2162, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2163, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 216, - "url": "https://via.placeholder.com/400x300/027527/ffffff?text=AquaPro+Blender+614W", - "description": "Blender product image" - } - ], - "created_at": "2024-04-20T09:16:11.461014Z", - "updated_at": "2025-07-22T09:16:11.461014Z" - }, - { - "id": 217, - "name": "AquaPro Blender 132T", - "description": "AquaPro Blender 132T is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.5, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1108, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 11.6, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 69.8, - "mass": 1.32, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.6, - "mass": 0.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 2171, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2172, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2173, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2174, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 217, - "url": "https://via.placeholder.com/400x300/078f15/ffffff?text=AquaPro+Blender+132T", - "description": "Blender product image" - } - ], - "created_at": "2024-05-03T09:16:11.461050Z", - "updated_at": "2025-06-26T09:16:11.461050Z" - }, - { - "id": 218, - "name": "CleanWave Humidifier 617O", - "description": "CleanWave Humidifier 617O is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.71, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1314, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.3, - "mass": 1.22, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 66.7, - "mass": 1.88, - "unit": "kg" - } - ], - "components": [ - { - "id": 2181, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2182, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 218, - "url": "https://via.placeholder.com/400x300/09da1c/ffffff?text=CleanWave+Humidifier+617O", - "description": "Humidifier product image" - } - ], - "created_at": "2024-08-26T09:16:11.461081Z", - "updated_at": "2025-07-07T09:16:11.461081Z" - }, - { - "id": 219, - "name": "EcoTech Kettle 628E", - "description": "EcoTech Kettle 628E is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.47, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2187, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 47.0, - "mass": 0.58, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 53.0, - "mass": 1.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 2191, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2192, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 219, - "url": "https://via.placeholder.com/400x300/0dbff8/ffffff?text=EcoTech+Kettle+628E", - "description": "Kettle product image" - } - ], - "created_at": "2024-04-08T09:16:11.461116Z", - "updated_at": "2025-06-16T09:16:11.461116Z" - }, - { - "id": 220, - "name": "ZenGear Coffee Maker 413T", - "description": "ZenGear Coffee Maker 413T is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.28, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1659, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 11.0, - "mass": 0.27, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 47.3, - "mass": 0.58, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 41.8, - "mass": 1.18, - "unit": "kg" - } - ], - "components": [ - { - "id": 2201, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2202, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2203, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2204, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 220, - "url": "https://via.placeholder.com/400x300/046a5b/ffffff?text=ZenGear+Coffee+Maker+413T", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-03-19T09:16:11.461152Z", - "updated_at": "2025-06-04T09:16:11.461152Z" - }, - { - "id": 221, - "name": "PureLife Fan 491R", - "description": "PureLife Fan 491R is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.17, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 984, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 41.3, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 58.7, - "mass": 1.07, - "unit": "kg" - } - ], - "components": [ - { - "id": 2211, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2212, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2213, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 221, - "url": "https://via.placeholder.com/400x300/08557e/ffffff?text=PureLife+Fan+491R", - "description": "Fan product image" - } - ], - "created_at": "2024-05-05T09:16:11.461194Z", - "updated_at": "2025-04-29T09:16:11.461194Z" - }, - { - "id": 222, - "name": "SmartHome Fan 715N", - "description": "SmartHome Fan 715N is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2136, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 25.0, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 34.5, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 40.5, - "mass": 1.16, - "unit": "kg" - } - ], - "components": [ - { - "id": 2221, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2222, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2223, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 2224, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 222, - "url": "https://via.placeholder.com/400x300/05e641/ffffff?text=SmartHome+Fan+715N", - "description": "Fan product image" - } - ], - "created_at": "2025-02-19T09:16:11.461261Z", - "updated_at": "2025-07-06T09:16:11.461261Z" - }, - { - "id": 223, - "name": "NeoCook Toaster 809G", - "description": "NeoCook Toaster 809G is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.76, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1541, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 46.5, - "mass": 1.35, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.8, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.7, - "mass": 0.5, - "unit": "kg" - } - ], - "components": [ - { - "id": 2231, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2232, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2233, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 223, - "url": "https://via.placeholder.com/400x300/07ab6a/ffffff?text=NeoCook+Toaster+809G", - "description": "Toaster product image" - } - ], - "created_at": "2025-02-06T09:16:11.461307Z", - "updated_at": "2025-07-19T09:16:11.461307Z" - }, - { - "id": 224, - "name": "ChefMate Iron 970X", - "description": "ChefMate Iron 970X is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.46, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1848, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 30.8, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 36.4, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 32.9, - "mass": 0.47, - "unit": "kg" - } - ], - "components": [ - { - "id": 2241, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 2242, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 2243, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 2244, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 224, - "url": "https://via.placeholder.com/400x300/0763bf/ffffff?text=ChefMate+Iron+970X", - "description": "Iron product image" - } - ], - "created_at": "2024-08-13T09:16:11.461369Z", - "updated_at": "2025-05-22T09:16:11.461369Z" - }, - { - "id": 225, - "name": "AquaPro Humidifier 893T", - "description": "AquaPro Humidifier 893T is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.48, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1083, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 50.7, - "mass": 1.27, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 49.3, - "mass": 1.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 2251, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2252, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2253, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2254, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 225, - "url": "https://via.placeholder.com/400x300/03c5d6/ffffff?text=AquaPro+Humidifier+893T", - "description": "Humidifier product image" - } - ], - "created_at": "2024-10-24T09:16:11.461433Z", - "updated_at": "2025-05-15T09:16:11.461433Z" - }, - { - "id": 226, - "name": "ZenGear Fan 548F", - "description": "ZenGear Fan 548F is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1765, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 63.1, - "mass": 1.46, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 36.9, - "mass": 0.95, - "unit": "kg" - } - ], - "components": [ - { - "id": 2261, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2262, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 226, - "url": "https://via.placeholder.com/400x300/0b33a5/ffffff?text=ZenGear+Fan+548F", - "description": "Fan product image" - } - ], - "created_at": "2025-01-14T09:16:11.461481Z", - "updated_at": "2025-07-16T09:16:11.461481Z" - }, - { - "id": 227, - "name": "CleanWave Blender 976X", - "description": "CleanWave Blender 976X is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.65, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1615, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 25.0, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 55.6, - "mass": 1.81, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 19.4, - "mass": 0.28, - "unit": "kg" - } - ], - "components": [ - { - "id": 2271, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2272, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2273, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2274, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 227, - "url": "https://via.placeholder.com/400x300/038d5b/ffffff?text=CleanWave+Blender+976X", - "description": "Blender product image" - } - ], - "created_at": "2024-08-29T09:16:11.461538Z", - "updated_at": "2025-05-31T09:16:11.461538Z" - }, - { - "id": 228, - "name": "PureLife Toaster 790V", - "description": "PureLife Toaster 790V is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.87, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1825, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 34.7, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 19.4, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 20.1, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.7, - "mass": 0.56, - "unit": "kg" - } - ], - "components": [ - { - "id": 2281, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2282, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2283, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2284, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 228, - "url": "https://via.placeholder.com/400x300/0a57db/ffffff?text=PureLife+Toaster+790V", - "description": "Toaster product image" - } - ], - "created_at": "2024-09-21T09:16:11.461585Z", - "updated_at": "2025-05-04T09:16:11.461585Z" - }, - { - "id": 229, - "name": "CleanWave Humidifier 848K", - "description": "CleanWave Humidifier 848K is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.17, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1439, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.4, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.4, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.3, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 27.9, - "mass": 0.74, - "unit": "kg" - } - ], - "components": [ - { - "id": 2291, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2292, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 229, - "url": "https://via.placeholder.com/400x300/0cc646/ffffff?text=CleanWave+Humidifier+848K", - "description": "Humidifier product image" - } - ], - "created_at": "2024-09-29T09:16:11.461627Z", - "updated_at": "2025-04-27T09:16:11.461627Z" - }, - { - "id": 230, - "name": "ZenGear Humidifier 675S", - "description": "ZenGear Humidifier 675S is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.24, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1324, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 65.1, - "mass": 2.23, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 34.9, - "mass": 0.62, - "unit": "kg" - } - ], - "components": [ - { - "id": 2301, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2302, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2303, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 230, - "url": "https://via.placeholder.com/400x300/03cc91/ffffff?text=ZenGear+Humidifier+675S", - "description": "Humidifier product image" - } - ], - "created_at": "2025-01-10T09:16:11.461666Z", - "updated_at": "2025-07-03T09:16:11.461666Z" - }, - { - "id": 231, - "name": "CleanWave Fan 815Z", - "description": "CleanWave Fan 815Z is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.49, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 994, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 51.2, - "mass": 1.62, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 48.8, - "mass": 0.6, - "unit": "kg" - } - ], - "components": [ - { - "id": 2311, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2312, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2313, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 231, - "url": "https://via.placeholder.com/400x300/0ab275/ffffff?text=CleanWave+Fan+815Z", - "description": "Fan product image" - } - ], - "created_at": "2024-11-04T09:16:11.462805Z", - "updated_at": "2025-07-16T09:16:11.462805Z" - }, - { - "id": 232, - "name": "ZenGear Humidifier 843P", - "description": "ZenGear Humidifier 843P is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.24, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1959, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 45.5, - "mass": 1.45, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.8, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.8, - "mass": 0.51, - "unit": "kg" - } - ], - "components": [ - { - "id": 2321, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2322, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 232, - "url": "https://via.placeholder.com/400x300/01ccb7/ffffff?text=ZenGear+Humidifier+843P", - "description": "Humidifier product image" - } - ], - "created_at": "2024-08-26T09:16:11.462873Z", - "updated_at": "2025-07-07T09:16:11.462873Z" - }, - { - "id": 233, - "name": "PureLife Coffee Maker 108Z", - "description": "PureLife Coffee Maker 108Z is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2114, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 21.3, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 34.9, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.1, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 10.7, - "mass": 0.19, - "unit": "kg" - } - ], - "components": [ - { - "id": 2331, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2332, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2333, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 233, - "url": "https://via.placeholder.com/400x300/09cc27/ffffff?text=PureLife+Coffee+Maker+108Z", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-08-10T09:16:11.462925Z", - "updated_at": "2025-06-01T09:16:11.462925Z" - }, - { - "id": 234, - "name": "CleanWave Toaster 110Y", - "description": "CleanWave Toaster 110Y is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1805, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 41.7, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 58.3, - "mass": 1.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 2341, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2342, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2343, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 234, - "url": "https://via.placeholder.com/400x300/0d484f/ffffff?text=CleanWave+Toaster+110Y", - "description": "Toaster product image" - } - ], - "created_at": "2025-03-05T09:16:11.462964Z", - "updated_at": "2025-06-17T09:16:11.462964Z" - }, - { - "id": 235, - "name": "ZenGear Coffee Maker 902S", - "description": "ZenGear Coffee Maker 902S is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.44, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 849, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 45.2, - "mass": 1.62, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 54.8, - "mass": 0.8, - "unit": "kg" - } - ], - "components": [ - { - "id": 2351, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2352, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 235, - "url": "https://via.placeholder.com/400x300/0a5032/ffffff?text=ZenGear+Coffee+Maker+902S", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-06-30T09:16:11.462994Z", - "updated_at": "2025-06-09T09:16:11.462994Z" - }, - { - "id": 236, - "name": "CleanWave Coffee Maker 399S", - "description": "CleanWave Coffee Maker 399S is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.28, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1225, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 32.3, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.9, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 10.1, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.6, - "mass": 0.92, - "unit": "kg" - } - ], - "components": [ - { - "id": 2361, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2362, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2363, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2364, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2365, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 236, - "url": "https://via.placeholder.com/400x300/07e9a0/ffffff?text=CleanWave+Coffee+Maker+399S", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-05-17T09:16:11.463033Z", - "updated_at": "2025-07-16T09:16:11.463033Z" - }, - { - "id": 237, - "name": "AquaPro Fan 303A", - "description": "AquaPro Fan 303A is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.93, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 35.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1400, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 31.8, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 37.1, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 31.1, - "mass": 1.15, - "unit": "kg" - } - ], - "components": [ - { - "id": 2371, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2372, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 237, - "url": "https://via.placeholder.com/400x300/0758b4/ffffff?text=AquaPro+Fan+303A", - "description": "Fan product image" - } - ], - "created_at": "2024-12-07T09:16:11.463081Z", - "updated_at": "2025-05-16T09:16:11.463081Z" - }, - { - "id": 238, - "name": "CleanWave Monitor 457V", - "description": "CleanWave Monitor 457V is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 908, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.5, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 15.2, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 24.1, - "mass": 0.96, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 32.3, - "mass": 1.09, - "unit": "kg" - } - ], - "components": [ - { - "id": 2381, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2382, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2383, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2384, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 238, - "url": "https://via.placeholder.com/400x300/0cf971/ffffff?text=CleanWave+Monitor+457V", - "description": "Monitor product image" - } - ], - "created_at": "2024-04-08T09:16:11.463125Z", - "updated_at": "2025-06-29T09:16:11.463125Z" - }, - { - "id": 239, - "name": "NeoCook Fan 246N", - "description": "NeoCook Fan 246N is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.05, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1852, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 56.9, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 43.1, - "mass": 0.76, - "unit": "kg" - } - ], - "components": [ - { - "id": 2391, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2392, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2393, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 2394, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 239, - "url": "https://via.placeholder.com/400x300/08ebf2/ffffff?text=NeoCook+Fan+246N", - "description": "Fan product image" - } - ], - "created_at": "2024-07-13T09:16:11.463381Z", - "updated_at": "2025-06-16T09:16:11.463381Z" - }, - { - "id": 240, - "name": "CleanWave Monitor 716X", - "description": "CleanWave Monitor 716X is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 897, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 29.0, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.9, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 37.1, - "mass": 0.95, - "unit": "kg" - } - ], - "components": [ - { - "id": 2401, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2402, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2403, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2404, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2405, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 240, - "url": "https://via.placeholder.com/400x300/0ba5c3/ffffff?text=CleanWave+Monitor+716X", - "description": "Monitor product image" - } - ], - "created_at": "2024-05-21T09:16:11.463459Z", - "updated_at": "2025-04-25T09:16:11.463459Z" - }, - { - "id": 241, - "name": "ZenGear Coffee Maker 829T", - "description": "ZenGear Coffee Maker 829T is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.9, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1990, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.0, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 11.3, - "mass": 0.17, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 30.5, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.2, - "mass": 1.08, - "unit": "kg" - } - ], - "components": [ - { - "id": 2411, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2412, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2413, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2414, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2415, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 241, - "url": "https://via.placeholder.com/400x300/02931e/ffffff?text=ZenGear+Coffee+Maker+829T", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-09-17T09:16:11.463501Z", - "updated_at": "2025-04-29T09:16:11.463501Z" - }, - { - "id": 242, - "name": "SmartHome Coffee Maker 492Z", - "description": "SmartHome Coffee Maker 492Z is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2025, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.4, - "mass": 1.37, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 10.9, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 13.2, - "mass": 0.21, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.5, - "mass": 1.56, - "unit": "kg" - } - ], - "components": [ - { - "id": 2421, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2422, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2423, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2424, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2425, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 242, - "url": "https://via.placeholder.com/400x300/0db02f/ffffff?text=SmartHome+Coffee+Maker+492Z", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-10-18T09:16:11.463557Z", - "updated_at": "2025-06-13T09:16:11.463557Z" - }, - { - "id": 243, - "name": "CleanWave Iron 762S", - "description": "CleanWave Iron 762S is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.72, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 912, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 60.0, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 40.0, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 2431, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 2432, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 2433, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 2434, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 2435, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 243, - "url": "https://via.placeholder.com/400x300/0c3636/ffffff?text=CleanWave+Iron+762S", - "description": "Iron product image" - } - ], - "created_at": "2025-04-09T09:16:11.463601Z", - "updated_at": "2025-06-21T09:16:11.463601Z" - }, - { - "id": 244, - "name": "ZenGear Blender 900X", - "description": "ZenGear Blender 900X is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.21, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2149, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 32.6, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 40.7, - "mass": 1.28, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 26.7, - "mass": 0.74, - "unit": "kg" - } - ], - "components": [ - { - "id": 2441, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2442, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2443, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2444, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 244, - "url": "https://via.placeholder.com/400x300/0ac2f2/ffffff?text=ZenGear+Blender+900X", - "description": "Blender product image" - } - ], - "created_at": "2024-11-23T09:16:11.463632Z", - "updated_at": "2025-07-25T09:16:11.463632Z" - }, - { - "id": 245, - "name": "CleanWave Rice Cooker 166O", - "description": "CleanWave Rice Cooker 166O is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1670, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 32.8, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 67.2, - "mass": 0.8, - "unit": "kg" - } - ], - "components": [ - { - "id": 2451, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2452, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2453, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2454, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2455, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 245, - "url": "https://via.placeholder.com/400x300/097866/ffffff?text=CleanWave+Rice+Cooker+166O", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-04-17T09:16:11.463662Z", - "updated_at": "2025-07-13T09:16:11.463662Z" - }, - { - "id": 246, - "name": "AquaPro Air Purifier 638V", - "description": "AquaPro Air Purifier 638V is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1080, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 14.3, - "mass": 0.24, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 19.0, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 26.5, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.1, - "mass": 0.63, - "unit": "kg" - } - ], - "components": [ - { - "id": 2461, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2462, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2463, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2464, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2465, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 246, - "url": "https://via.placeholder.com/400x300/0a3080/ffffff?text=AquaPro+Air+Purifier+638V", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-08-03T09:16:11.463706Z", - "updated_at": "2025-06-08T09:16:11.463706Z" - }, - { - "id": 247, - "name": "CleanWave Kettle 403Y", - "description": "CleanWave Kettle 403Y is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1151, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 23.3, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.4, - "mass": 0.52, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 59.3, - "mass": 1.72, - "unit": "kg" - } - ], - "components": [ - { - "id": 2471, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2472, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 247, - "url": "https://via.placeholder.com/400x300/0dbac5/ffffff?text=CleanWave+Kettle+403Y", - "description": "Kettle product image" - } - ], - "created_at": "2024-05-19T09:16:11.463762Z", - "updated_at": "2025-06-30T09:16:11.463762Z" - }, - { - "id": 248, - "name": "CleanWave Coffee Maker 397Y", - "description": "CleanWave Coffee Maker 397Y is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1273, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 41.0, - "mass": 1.04, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 12.2, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 39.6, - "mass": 1.18, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 7.2, - "mass": 0.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 2481, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2482, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2483, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2484, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 248, - "url": "https://via.placeholder.com/400x300/0c9a01/ffffff?text=CleanWave+Coffee+Maker+397Y", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-06-20T09:16:11.463809Z", - "updated_at": "2025-05-25T09:16:11.463809Z" - }, - { - "id": 249, - "name": "CleanWave Monitor 619K", - "description": "CleanWave Monitor 619K is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.55, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1101, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 34.8, - "mass": 1.17, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 65.2, - "mass": 1.74, - "unit": "kg" - } - ], - "components": [ - { - "id": 2491, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2492, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2493, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2494, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2495, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 249, - "url": "https://via.placeholder.com/400x300/0a3344/ffffff?text=CleanWave+Monitor+619K", - "description": "Monitor product image" - } - ], - "created_at": "2025-01-28T09:16:11.463855Z", - "updated_at": "2025-07-14T09:16:11.463855Z" - }, - { - "id": 250, - "name": "AquaPro Air Purifier 504C", - "description": "AquaPro Air Purifier 504C is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1246, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.0, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 28.0, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 43.0, - "mass": 1.0, - "unit": "kg" - } - ], - "components": [ - { - "id": 2501, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2502, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2503, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 250, - "url": "https://via.placeholder.com/400x300/052d6b/ffffff?text=AquaPro+Air+Purifier+504C", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-12-19T09:16:11.463920Z", - "updated_at": "2025-05-24T09:16:11.463920Z" - }, - { - "id": 251, - "name": "SmartHome Monitor 713U", - "description": "SmartHome Monitor 713U is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.9, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1438, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.6, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.1, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 44.4, - "mass": 1.5, - "unit": "kg" - } - ], - "components": [ - { - "id": 2511, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2512, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 251, - "url": "https://via.placeholder.com/400x300/068125/ffffff?text=SmartHome+Monitor+713U", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-22T09:16:11.463968Z", - "updated_at": "2025-07-11T09:16:11.463968Z" - }, - { - "id": 252, - "name": "SmartHome Humidifier 873Q", - "description": "SmartHome Humidifier 873Q is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.41, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1562, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.5, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 19.8, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 52.7, - "mass": 1.26, - "unit": "kg" - } - ], - "components": [ - { - "id": 2521, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2522, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 252, - "url": "https://via.placeholder.com/400x300/0a4e8a/ffffff?text=SmartHome+Humidifier+873Q", - "description": "Humidifier product image" - } - ], - "created_at": "2024-10-31T09:16:11.464031Z", - "updated_at": "2025-07-03T09:16:11.464031Z" - }, - { - "id": 253, - "name": "EcoTech Fan 327S", - "description": "EcoTech Fan 327S is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.66, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1386, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 15.0, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 39.8, - "mass": 1.46, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 45.1, - "mass": 1.22, - "unit": "kg" - } - ], - "components": [ - { - "id": 2531, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2532, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2533, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 2534, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 253, - "url": "https://via.placeholder.com/400x300/0625c2/ffffff?text=EcoTech+Fan+327S", - "description": "Fan product image" - } - ], - "created_at": "2024-03-30T09:16:11.464084Z", - "updated_at": "2025-07-26T09:16:11.464084Z" - }, - { - "id": 254, - "name": "SmartHome Iron 836G", - "description": "SmartHome Iron 836G is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.72, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1489, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 23.3, - "mass": 0.7, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 76.7, - "mass": 1.06, - "unit": "kg" - } - ], - "components": [ - { - "id": 2541, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 2542, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 2543, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 2544, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 254, - "url": "https://via.placeholder.com/400x300/0f190b/ffffff?text=SmartHome+Iron+836G", - "description": "Iron product image" - } - ], - "created_at": "2025-03-27T09:16:11.464144Z", - "updated_at": "2025-05-15T09:16:11.464144Z" - }, - { - "id": 255, - "name": "EcoTech Rice Cooker 371T", - "description": "EcoTech Rice Cooker 371T is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.77, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 912, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.0, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.3, - "mass": 0.47, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 55.7, - "mass": 1.23, - "unit": "kg" - } - ], - "components": [ - { - "id": 2551, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2552, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2553, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 255, - "url": "https://via.placeholder.com/400x300/0dbca9/ffffff?text=EcoTech+Rice+Cooker+371T", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-01-01T09:16:11.464212Z", - "updated_at": "2025-05-09T09:16:11.464212Z" - }, - { - "id": 256, - "name": "SmartHome Rice Cooker 865X", - "description": "SmartHome Rice Cooker 865X is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.02, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 913, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 35.7, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 36.4, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 27.9, - "mass": 1.04, - "unit": "kg" - } - ], - "components": [ - { - "id": 2561, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2562, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2563, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 256, - "url": "https://via.placeholder.com/400x300/036c99/ffffff?text=SmartHome+Rice+Cooker+865X", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-03-23T09:16:11.464310Z", - "updated_at": "2025-05-10T09:16:11.464310Z" - }, - { - "id": 257, - "name": "PureLife Kettle 821Z", - "description": "PureLife Kettle 821Z is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.93, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1831, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 12.6, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 40.9, - "mass": 0.88, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 46.5, - "mass": 1.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 2571, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2572, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2573, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2574, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 257, - "url": "https://via.placeholder.com/400x300/023c5a/ffffff?text=PureLife+Kettle+821Z", - "description": "Kettle product image" - } - ], - "created_at": "2024-08-18T09:16:11.464367Z", - "updated_at": "2025-06-14T09:16:11.464367Z" - }, - { - "id": 258, - "name": "EcoTech Rice Cooker 869X", - "description": "EcoTech Rice Cooker 869X is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.09, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 928, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 35.4, - "mass": 1.14, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.8, - "mass": 1.01, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 36.8, - "mass": 1.28, - "unit": "kg" - } - ], - "components": [ - { - "id": 2581, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2582, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 258, - "url": "https://via.placeholder.com/400x300/0b7f3c/ffffff?text=EcoTech+Rice+Cooker+869X", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-09-22T09:16:11.464435Z", - "updated_at": "2025-06-30T09:16:11.464435Z" - }, - { - "id": 259, - "name": "ZenGear Humidifier 787T", - "description": "ZenGear Humidifier 787T is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.09, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2044, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 31.7, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 33.3, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 20.8, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 14.2, - "mass": 0.54, - "unit": "kg" - } - ], - "components": [ - { - "id": 2591, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2592, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 259, - "url": "https://via.placeholder.com/400x300/0f1fa5/ffffff?text=ZenGear+Humidifier+787T", - "description": "Humidifier product image" - } - ], - "created_at": "2024-08-25T09:16:11.464493Z", - "updated_at": "2025-05-03T09:16:11.464493Z" - }, - { - "id": 260, - "name": "EcoTech Kettle 165T", - "description": "EcoTech Kettle 165T is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1532, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 54.2, - "mass": 0.55, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 45.8, - "mass": 0.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 2601, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2602, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2603, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2604, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2605, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 260, - "url": "https://via.placeholder.com/400x300/080cf2/ffffff?text=EcoTech+Kettle+165T", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-28T09:16:11.464549Z", - "updated_at": "2025-05-16T09:16:11.464549Z" - }, - { - "id": 261, - "name": "NeoCook Coffee Maker 295E", - "description": "NeoCook Coffee Maker 295E is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.1, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1318, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 40.2, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 59.8, - "mass": 1.97, - "unit": "kg" - } - ], - "components": [ - { - "id": 2611, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2612, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2613, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 261, - "url": "https://via.placeholder.com/400x300/07d5e7/ffffff?text=NeoCook+Coffee+Maker+295E", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-11-26T09:16:11.464610Z", - "updated_at": "2025-05-06T09:16:11.464610Z" - }, - { - "id": 262, - "name": "AquaPro Blender 981U", - "description": "AquaPro Blender 981U is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.93, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1731, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 30.7, - "mass": 1.23, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 59.4, - "mass": 1.63, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 9.9, - "mass": 0.12, - "unit": "kg" - } - ], - "components": [ - { - "id": 2621, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2622, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2623, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2624, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 2625, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 262, - "url": "https://via.placeholder.com/400x300/091f8c/ffffff?text=AquaPro+Blender+981U", - "description": "Blender product image" - } - ], - "created_at": "2024-03-26T09:16:11.464678Z", - "updated_at": "2025-05-06T09:16:11.464678Z" - }, - { - "id": 263, - "name": "EcoTech Monitor 313N", - "description": "EcoTech Monitor 313N is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.8, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1380, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 78.3, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 21.7, - "mass": 0.67, - "unit": "kg" - } - ], - "components": [ - { - "id": 2631, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2632, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2633, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 263, - "url": "https://via.placeholder.com/400x300/0cacdf/ffffff?text=EcoTech+Monitor+313N", - "description": "Monitor product image" - } - ], - "created_at": "2024-10-02T09:16:11.464733Z", - "updated_at": "2025-06-11T09:16:11.464733Z" - }, - { - "id": 264, - "name": "PureLife Monitor 694H", - "description": "PureLife Monitor 694H is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.12, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1466, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 44.4, - "mass": 1.66, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 55.6, - "mass": 2.1, - "unit": "kg" - } - ], - "components": [ - { - "id": 2641, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2642, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 264, - "url": "https://via.placeholder.com/400x300/05f976/ffffff?text=PureLife+Monitor+694H", - "description": "Monitor product image" - } - ], - "created_at": "2024-10-22T09:16:11.464781Z", - "updated_at": "2025-07-09T09:16:11.464781Z" - }, - { - "id": 265, - "name": "PureLife Toaster 748C", - "description": "PureLife Toaster 748C is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.23, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1253, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 19.8, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.4, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 21.6, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 30.2, - "mass": 0.49, - "unit": "kg" - } - ], - "components": [ - { - "id": 2651, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2652, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2653, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2654, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2655, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 265, - "url": "https://via.placeholder.com/400x300/0d721e/ffffff?text=PureLife+Toaster+748C", - "description": "Toaster product image" - } - ], - "created_at": "2024-03-27T09:16:11.464862Z", - "updated_at": "2025-05-08T09:16:11.464862Z" - }, - { - "id": 266, - "name": "PureLife Humidifier 160T", - "description": "PureLife Humidifier 160T is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.21, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2032, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 76.5, - "mass": 1.65, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 23.5, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 2661, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2662, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 266, - "url": "https://via.placeholder.com/400x300/05145a/ffffff?text=PureLife+Humidifier+160T", - "description": "Humidifier product image" - } - ], - "created_at": "2024-03-19T09:16:11.464975Z", - "updated_at": "2025-07-30T09:16:11.464975Z" - }, - { - "id": 267, - "name": "ChefMate Air Purifier 951O", - "description": "ChefMate Air Purifier 951O is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1994, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 11.0, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 39.0, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 32.2, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 17.8, - "mass": 0.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 2671, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2672, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2673, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2674, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2675, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 267, - "url": "https://via.placeholder.com/400x300/095092/ffffff?text=ChefMate+Air+Purifier+951O", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-09-04T09:16:11.465048Z", - "updated_at": "2025-04-30T09:16:11.465048Z" - }, - { - "id": 268, - "name": "AquaPro Air Purifier 116R", - "description": "AquaPro Air Purifier 116R is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1660, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.0, - "mass": 1.22, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 50.0, - "mass": 1.5, - "unit": "kg" - } - ], - "components": [ - { - "id": 2681, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2682, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 268, - "url": "https://via.placeholder.com/400x300/058df3/ffffff?text=AquaPro+Air+Purifier+116R", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-12-31T09:16:11.465104Z", - "updated_at": "2025-06-17T09:16:11.465104Z" - }, - { - "id": 269, - "name": "ChefMate Toaster 145U", - "description": "ChefMate Toaster 145U is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1634, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 55.3, - "mass": 1.88, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 44.7, - "mass": 1.59, - "unit": "kg" - } - ], - "components": [ - { - "id": 2691, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2692, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 269, - "url": "https://via.placeholder.com/400x300/08ac73/ffffff?text=ChefMate+Toaster+145U", - "description": "Toaster product image" - } - ], - "created_at": "2024-07-02T09:16:11.465165Z", - "updated_at": "2025-06-18T09:16:11.465165Z" - }, - { - "id": 270, - "name": "CleanWave Rice Cooker 594W", - "description": "CleanWave Rice Cooker 594W is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 890, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 22.8, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 39.1, - "mass": 0.87, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 38.0, - "mass": 0.79, - "unit": "kg" - } - ], - "components": [ - { - "id": 2701, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2702, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2703, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 270, - "url": "https://via.placeholder.com/400x300/08ccc2/ffffff?text=CleanWave+Rice+Cooker+594W", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-09-30T09:16:11.465217Z", - "updated_at": "2025-07-18T09:16:11.465217Z" - }, - { - "id": 271, - "name": "NeoCook Coffee Maker 582N", - "description": "NeoCook Coffee Maker 582N is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1082, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 17.9, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 46.2, - "mass": 0.78, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 35.9, - "mass": 0.78, - "unit": "kg" - } - ], - "components": [ - { - "id": 2711, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2712, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 271, - "url": "https://via.placeholder.com/400x300/07ce5c/ffffff?text=NeoCook+Coffee+Maker+582N", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-11-03T09:16:11.465278Z", - "updated_at": "2025-06-03T09:16:11.465278Z" - }, - { - "id": 272, - "name": "EcoTech Blender 933K", - "description": "EcoTech Blender 933K is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.01, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1007, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 53.4, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 20.5, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 26.1, - "mass": 0.73, - "unit": "kg" - } - ], - "components": [ - { - "id": 2721, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2722, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2723, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2724, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 272, - "url": "https://via.placeholder.com/400x300/0b3398/ffffff?text=EcoTech+Blender+933K", - "description": "Blender product image" - } - ], - "created_at": "2025-04-17T09:16:11.465341Z", - "updated_at": "2025-06-02T09:16:11.465341Z" - }, - { - "id": 273, - "name": "CleanWave Coffee Maker 371O", - "description": "CleanWave Coffee Maker 371O is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.21, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1380, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 43.4, - "mass": 0.47, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.7, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.9, - "mass": 0.64, - "unit": "kg" - } - ], - "components": [ - { - "id": 2731, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2732, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2733, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2734, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2735, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 273, - "url": "https://via.placeholder.com/400x300/0aa952/ffffff?text=CleanWave+Coffee+Maker+371O", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-11-13T09:16:11.465394Z", - "updated_at": "2025-06-16T09:16:11.465394Z" - }, - { - "id": 274, - "name": "CleanWave Rice Cooker 295I", - "description": "CleanWave Rice Cooker 295I is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.48, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1775, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 12.9, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 41.9, - "mass": 1.47, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 45.2, - "mass": 1.69, - "unit": "kg" - } - ], - "components": [ - { - "id": 2741, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2742, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 274, - "url": "https://via.placeholder.com/400x300/0a3127/ffffff?text=CleanWave+Rice+Cooker+295I", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-02-11T09:16:11.465465Z", - "updated_at": "2025-06-06T09:16:11.465465Z" - }, - { - "id": 275, - "name": "CleanWave Air Purifier 656B", - "description": "CleanWave Air Purifier 656B is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1657, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 55.7, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 44.3, - "mass": 1.49, - "unit": "kg" - } - ], - "components": [ - { - "id": 2751, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2752, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2753, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2754, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 275, - "url": "https://via.placeholder.com/400x300/04e70f/ffffff?text=CleanWave+Air+Purifier+656B", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-05-07T09:16:11.465515Z", - "updated_at": "2025-06-24T09:16:11.465515Z" - }, - { - "id": 276, - "name": "EcoTech Coffee Maker 165P", - "description": "EcoTech Coffee Maker 165P is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.29, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1181, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.2, - "mass": 1.0, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 7.1, - "mass": 0.12, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 32.7, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 32.1, - "mass": 0.44, - "unit": "kg" - } - ], - "components": [ - { - "id": 2761, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2762, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2763, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2764, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2765, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 276, - "url": "https://via.placeholder.com/400x300/0978e5/ffffff?text=EcoTech+Coffee+Maker+165P", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-08-27T09:16:11.465587Z", - "updated_at": "2025-04-26T09:16:11.465587Z" - }, - { - "id": 277, - "name": "CleanWave Fan 815K", - "description": "CleanWave Fan 815K is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.69, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1768, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 13.6, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 30.0, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.9, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.6, - "mass": 1.02, - "unit": "kg" - } - ], - "components": [ - { - "id": 2771, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 2772, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 2773, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 277, - "url": "https://via.placeholder.com/400x300/09935b/ffffff?text=CleanWave+Fan+815K", - "description": "Fan product image" - } - ], - "created_at": "2025-04-17T09:16:11.465651Z", - "updated_at": "2025-05-10T09:16:11.465651Z" - }, - { - "id": 278, - "name": "NeoCook Toaster 826V", - "description": "NeoCook Toaster 826V is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 986, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 11.5, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 35.4, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 42.3, - "mass": 1.13, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 10.8, - "mass": 0.13, - "unit": "kg" - } - ], - "components": [ - { - "id": 2781, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2782, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2783, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 278, - "url": "https://via.placeholder.com/400x300/0b3fe6/ffffff?text=NeoCook+Toaster+826V", - "description": "Toaster product image" - } - ], - "created_at": "2024-08-19T09:16:11.465712Z", - "updated_at": "2025-06-23T09:16:11.465712Z" - }, - { - "id": 279, - "name": "PureLife Monitor 317U", - "description": "PureLife Monitor 317U is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.86, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2167, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 27.3, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.4, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 10.7, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.6, - "mass": 0.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 2791, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2792, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 279, - "url": "https://via.placeholder.com/400x300/0f07a9/ffffff?text=PureLife+Monitor+317U", - "description": "Monitor product image" - } - ], - "created_at": "2025-04-23T09:16:11.465768Z", - "updated_at": "2025-06-12T09:16:11.465768Z" - }, - { - "id": 280, - "name": "SmartHome Rice Cooker 990X", - "description": "SmartHome Rice Cooker 990X is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.81, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1068, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 44.7, - "mass": 1.58, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 55.3, - "mass": 0.61, - "unit": "kg" - } - ], - "components": [ - { - "id": 2801, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2802, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2803, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2804, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2805, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 280, - "url": "https://via.placeholder.com/400x300/0316f5/ffffff?text=SmartHome+Rice+Cooker+990X", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-12-02T09:16:11.465843Z", - "updated_at": "2025-06-13T09:16:11.465843Z" - }, - { - "id": 281, - "name": "ChefMate Humidifier 455V", - "description": "ChefMate Humidifier 455V is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1805, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 56.2, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.2, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 12.5, - "mass": 0.34, - "unit": "kg" - } - ], - "components": [ - { - "id": 2811, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2812, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2813, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2814, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 2815, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 281, - "url": "https://via.placeholder.com/400x300/034834/ffffff?text=ChefMate+Humidifier+455V", - "description": "Humidifier product image" - } - ], - "created_at": "2025-04-08T09:16:11.465899Z", - "updated_at": "2025-06-16T09:16:11.465899Z" - }, - { - "id": 282, - "name": "NeoCook Monitor 125C", - "description": "NeoCook Monitor 125C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.09, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1787, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.4, - "mass": 1.55, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 38.7, - "mass": 1.48, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 10.9, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 2821, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2822, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 282, - "url": "https://via.placeholder.com/400x300/0c4961/ffffff?text=NeoCook+Monitor+125C", - "description": "Monitor product image" - } - ], - "created_at": "2024-12-20T09:16:11.466151Z", - "updated_at": "2025-05-23T09:16:11.466151Z" - }, - { - "id": 283, - "name": "EcoTech Iron 197P", - "description": "EcoTech Iron 197P is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.44, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2065, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 36.4, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 41.7, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.0, - "mass": 0.39, - "unit": "kg" - } - ], - "components": [ - { - "id": 2831, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 2832, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 2833, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 2834, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 2835, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 283, - "url": "https://via.placeholder.com/400x300/01b85e/ffffff?text=EcoTech+Iron+197P", - "description": "Iron product image" - } - ], - "created_at": "2025-04-19T09:16:11.466258Z", - "updated_at": "2025-07-28T09:16:11.466258Z" - }, - { - "id": 284, - "name": "CleanWave Monitor 381B", - "description": "CleanWave Monitor 381B is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.32, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1101, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 41.2, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 17.6, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 41.2, - "mass": 1.6, - "unit": "kg" - } - ], - "components": [ - { - "id": 2841, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2842, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2843, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2844, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2845, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 284, - "url": "https://via.placeholder.com/400x300/0dc916/ffffff?text=CleanWave+Monitor+381B", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-22T09:16:11.466327Z", - "updated_at": "2025-07-06T09:16:11.466327Z" - }, - { - "id": 285, - "name": "EcoTech Monitor 150C", - "description": "EcoTech Monitor 150C is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1475, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 44.0, - "mass": 1.02, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 21.6, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 34.4, - "mass": 1.14, - "unit": "kg" - } - ], - "components": [ - { - "id": 2851, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2852, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2853, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2854, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 285, - "url": "https://via.placeholder.com/400x300/0bd853/ffffff?text=EcoTech+Monitor+150C", - "description": "Monitor product image" - } - ], - "created_at": "2024-07-26T09:16:11.466367Z", - "updated_at": "2025-07-05T09:16:11.466367Z" - }, - { - "id": 286, - "name": "AquaPro Monitor 255R", - "description": "AquaPro Monitor 255R is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.88, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1632, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.5, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 27.5, - "mass": 1.1, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 44.9, - "mass": 1.29, - "unit": "kg" - } - ], - "components": [ - { - "id": 2861, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2862, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2863, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 286, - "url": "https://via.placeholder.com/400x300/0309c4/ffffff?text=AquaPro+Monitor+255R", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-16T09:16:11.466413Z", - "updated_at": "2025-07-24T09:16:11.466413Z" - }, - { - "id": 287, - "name": "ChefMate Monitor 552Y", - "description": "ChefMate Monitor 552Y is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1820, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 19.5, - "mass": 0.22, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.9, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 18.0, - "mass": 0.2, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.6, - "mass": 1.33, - "unit": "kg" - } - ], - "components": [ - { - "id": 2871, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2872, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2873, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2874, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2875, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 287, - "url": "https://via.placeholder.com/400x300/0e39d7/ffffff?text=ChefMate+Monitor+552Y", - "description": "Monitor product image" - } - ], - "created_at": "2024-05-08T09:16:11.466604Z", - "updated_at": "2025-07-26T09:16:11.466604Z" - }, - { - "id": 288, - "name": "EcoTech Monitor 479I", - "description": "EcoTech Monitor 479I is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1754, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.7, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 32.2, - "mass": 1.28, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 18.6, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 19.5, - "mass": 0.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 2881, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2882, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2883, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2884, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2885, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 288, - "url": "https://via.placeholder.com/400x300/05e5d9/ffffff?text=EcoTech+Monitor+479I", - "description": "Monitor product image" - } - ], - "created_at": "2025-01-24T09:16:11.466670Z", - "updated_at": "2025-07-20T09:16:11.466670Z" - }, - { - "id": 289, - "name": "AquaPro Blender 305E", - "description": "AquaPro Blender 305E is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1115, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 24.8, - "mass": 0.96, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 55.0, - "mass": 1.2, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 20.2, - "mass": 0.76, - "unit": "kg" - } - ], - "components": [ - { - "id": 2891, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2892, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 2893, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 2894, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 2895, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 289, - "url": "https://via.placeholder.com/400x300/01a1d2/ffffff?text=AquaPro+Blender+305E", - "description": "Blender product image" - } - ], - "created_at": "2024-12-27T09:16:11.466726Z", - "updated_at": "2025-06-27T09:16:11.466726Z" - }, - { - "id": 290, - "name": "AquaPro Monitor 506U", - "description": "AquaPro Monitor 506U is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1646, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.1, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.3, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 25.0, - "mass": 0.82, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 19.6, - "mass": 0.29, - "unit": "kg" - } - ], - "components": [ - { - "id": 2901, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2902, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2903, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 290, - "url": "https://via.placeholder.com/400x300/02fd13/ffffff?text=AquaPro+Monitor+506U", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-11T09:16:11.466786Z", - "updated_at": "2025-04-26T09:16:11.466786Z" - }, - { - "id": 291, - "name": "PureLife Air Purifier 874X", - "description": "PureLife Air Purifier 874X is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.68, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1175, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 53.2, - "mass": 1.52, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 46.8, - "mass": 0.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 2911, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 2912, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 291, - "url": "https://via.placeholder.com/400x300/0dc724/ffffff?text=PureLife+Air+Purifier+874X", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-05-23T09:16:11.466843Z", - "updated_at": "2025-06-19T09:16:11.466843Z" - }, - { - "id": 292, - "name": "SmartHome Blender 307I", - "description": "SmartHome Blender 307I is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.66, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1648, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 17.8, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 37.8, - "mass": 1.09, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 16.7, - "mass": 0.2, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.8, - "mass": 1.09, - "unit": "kg" - } - ], - "components": [ - { - "id": 2921, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 2922, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 292, - "url": "https://via.placeholder.com/400x300/05fb28/ffffff?text=SmartHome+Blender+307I", - "description": "Blender product image" - } - ], - "created_at": "2025-02-28T09:16:11.466936Z", - "updated_at": "2025-06-02T09:16:11.466936Z" - }, - { - "id": 293, - "name": "PureLife Coffee Maker 936G", - "description": "PureLife Coffee Maker 936G is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.33, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1181, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 51.7, - "mass": 1.13, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 48.3, - "mass": 0.78, - "unit": "kg" - } - ], - "components": [ - { - "id": 2931, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 2932, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 293, - "url": "https://via.placeholder.com/400x300/07d5d7/ffffff?text=PureLife+Coffee+Maker+936G", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-02-22T09:16:11.466985Z", - "updated_at": "2025-05-19T09:16:11.466985Z" - }, - { - "id": 294, - "name": "NeoCook Toaster 589A", - "description": "NeoCook Toaster 589A is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1699, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 39.1, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 26.3, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 34.6, - "mass": 1.04, - "unit": "kg" - } - ], - "components": [ - { - "id": 2941, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2942, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2943, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2944, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 2945, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 294, - "url": "https://via.placeholder.com/400x300/047deb/ffffff?text=NeoCook+Toaster+589A", - "description": "Toaster product image" - } - ], - "created_at": "2024-10-07T09:16:11.467037Z", - "updated_at": "2025-07-23T09:16:11.467037Z" - }, - { - "id": 295, - "name": "ZenGear Kettle 454K", - "description": "ZenGear Kettle 454K is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.01, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2171, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 41.6, - "mass": 1.1, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 28.0, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.4, - "mass": 0.73, - "unit": "kg" - } - ], - "components": [ - { - "id": 2951, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2952, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2953, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2954, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 295, - "url": "https://via.placeholder.com/400x300/070ae7/ffffff?text=ZenGear+Kettle+454K", - "description": "Kettle product image" - } - ], - "created_at": "2024-12-22T09:16:11.467091Z", - "updated_at": "2025-05-29T09:16:11.467091Z" - }, - { - "id": 296, - "name": "SmartHome Rice Cooker 275T", - "description": "SmartHome Rice Cooker 275T is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.3, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1067, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 50.7, - "mass": 0.78, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 49.3, - "mass": 1.11, - "unit": "kg" - } - ], - "components": [ - { - "id": 2961, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2962, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2963, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 296, - "url": "https://via.placeholder.com/400x300/09afef/ffffff?text=SmartHome+Rice+Cooker+275T", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-11-07T09:16:11.467155Z", - "updated_at": "2025-05-23T09:16:11.467155Z" - }, - { - "id": 297, - "name": "ChefMate Monitor 548Y", - "description": "ChefMate Monitor 548Y is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 4.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2052, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.0, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 75.0, - "mass": 2.75, - "unit": "kg" - } - ], - "components": [ - { - "id": 2971, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2972, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2973, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2974, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 2975, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 297, - "url": "https://via.placeholder.com/400x300/02c755/ffffff?text=ChefMate+Monitor+548Y", - "description": "Monitor product image" - } - ], - "created_at": "2024-04-09T09:16:11.467200Z", - "updated_at": "2025-07-02T09:16:11.467200Z" - }, - { - "id": 298, - "name": "CleanWave Kettle 973Q", - "description": "CleanWave Kettle 973Q is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 915, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 71.4, - "mass": 0.94, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.6, - "mass": 1.03, - "unit": "kg" - } - ], - "components": [ - { - "id": 2981, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2982, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2983, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 2984, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 298, - "url": "https://via.placeholder.com/400x300/0d57f8/ffffff?text=CleanWave+Kettle+973Q", - "description": "Kettle product image" - } - ], - "created_at": "2024-12-02T09:16:11.467253Z", - "updated_at": "2025-05-08T09:16:11.467253Z" - }, - { - "id": 299, - "name": "NeoCook Rice Cooker 872D", - "description": "NeoCook Rice Cooker 872D is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1987, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 57.7, - "mass": 1.71, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 42.3, - "mass": 1.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 2991, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2992, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2993, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 2994, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 299, - "url": "https://via.placeholder.com/400x300/0b9d23/ffffff?text=NeoCook+Rice+Cooker+872D", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-02-17T09:16:11.467285Z", - "updated_at": "2025-07-15T09:16:11.467285Z" - }, - { - "id": 300, - "name": "EcoTech Toaster 625V", - "description": "EcoTech Toaster 625V is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.88, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 940, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 83.9, - "mass": 2.22, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 16.1, - "mass": 0.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 3001, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3002, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 300, - "url": "https://via.placeholder.com/400x300/0b4f56/ffffff?text=EcoTech+Toaster+625V", - "description": "Toaster product image" - } - ], - "created_at": "2024-09-30T09:16:11.467366Z", - "updated_at": "2025-07-14T09:16:11.467366Z" - }, - { - "id": 301, - "name": "NeoCook Toaster 322Y", - "description": "NeoCook Toaster 322Y is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1158, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 23.0, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 26.3, - "mass": 0.85, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 21.7, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.9, - "mass": 0.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 3011, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3012, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3013, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3014, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3015, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 301, - "url": "https://via.placeholder.com/400x300/0431a4/ffffff?text=NeoCook+Toaster+322Y", - "description": "Toaster product image" - } - ], - "created_at": "2024-12-13T09:16:11.467421Z", - "updated_at": "2025-06-18T09:16:11.467421Z" - }, - { - "id": 302, - "name": "ChefMate Coffee Maker 427Z", - "description": "ChefMate Coffee Maker 427Z is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1856, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 44.1, - "mass": 1.46, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 41.9, - "mass": 1.2, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 14.0, - "mass": 0.43, - "unit": "kg" - } - ], - "components": [ - { - "id": 3021, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3022, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 302, - "url": "https://via.placeholder.com/400x300/01c354/ffffff?text=ChefMate+Coffee+Maker+427Z", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-10-29T09:16:11.467455Z", - "updated_at": "2025-06-09T09:16:11.467455Z" - }, - { - "id": 303, - "name": "CleanWave Rice Cooker 554H", - "description": "CleanWave Rice Cooker 554H is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 4.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1741, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 66.7, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 33.3, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 3031, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3032, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3033, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3034, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 303, - "url": "https://via.placeholder.com/400x300/0258f8/ffffff?text=CleanWave+Rice+Cooker+554H", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-04-17T09:16:11.467490Z", - "updated_at": "2025-06-12T09:16:11.467490Z" - }, - { - "id": 304, - "name": "PureLife Monitor 896D", - "description": "PureLife Monitor 896D is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.66, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1539, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 65.6, - "mass": 2.59, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 34.4, - "mass": 0.86, - "unit": "kg" - } - ], - "components": [ - { - "id": 3041, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3042, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3043, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3044, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3045, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 304, - "url": "https://via.placeholder.com/400x300/0d9540/ffffff?text=PureLife+Monitor+896D", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-17T09:16:11.467526Z", - "updated_at": "2025-06-22T09:16:11.467526Z" - }, - { - "id": 305, - "name": "ChefMate Humidifier 361F", - "description": "ChefMate Humidifier 361F is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.48, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1646, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 15.8, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 52.6, - "mass": 0.87, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 31.6, - "mass": 1.04, - "unit": "kg" - } - ], - "components": [ - { - "id": 3051, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3052, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3053, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3054, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 305, - "url": "https://via.placeholder.com/400x300/0df883/ffffff?text=ChefMate+Humidifier+361F", - "description": "Humidifier product image" - } - ], - "created_at": "2024-09-09T09:16:11.467566Z", - "updated_at": "2025-05-13T09:16:11.467566Z" - }, - { - "id": 306, - "name": "ZenGear Air Purifier 689E", - "description": "ZenGear Air Purifier 689E is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.31, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1283, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 12.6, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 52.9, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 34.5, - "mass": 1.09, - "unit": "kg" - } - ], - "components": [ - { - "id": 3061, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3062, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 306, - "url": "https://via.placeholder.com/400x300/0ace8f/ffffff?text=ZenGear+Air+Purifier+689E", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-10-12T09:16:11.467594Z", - "updated_at": "2025-06-04T09:16:11.467594Z" - }, - { - "id": 307, - "name": "ZenGear Kettle 641K", - "description": "ZenGear Kettle 641K is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1182, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.4, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 59.6, - "mass": 1.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 3071, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3072, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3073, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 307, - "url": "https://via.placeholder.com/400x300/0a6acf/ffffff?text=ZenGear+Kettle+641K", - "description": "Kettle product image" - } - ], - "created_at": "2025-01-07T09:16:11.467646Z", - "updated_at": "2025-05-29T09:16:11.467646Z" - }, - { - "id": 308, - "name": "SmartHome Fan 241E", - "description": "SmartHome Fan 241E is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.27, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2101, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 82.1, - "mass": 1.56, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 17.9, - "mass": 0.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 3081, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3082, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3083, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3084, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 308, - "url": "https://via.placeholder.com/400x300/044a03/ffffff?text=SmartHome+Fan+241E", - "description": "Fan product image" - } - ], - "created_at": "2025-04-22T09:16:11.467680Z", - "updated_at": "2025-05-15T09:16:11.467680Z" - }, - { - "id": 309, - "name": "EcoTech Humidifier 836F", - "description": "EcoTech Humidifier 836F is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.62, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1662, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 15.4, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 57.1, - "mass": 1.72, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.5, - "mass": 0.62, - "unit": "kg" - } - ], - "components": [ - { - "id": 3091, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3092, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3093, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 309, - "url": "https://via.placeholder.com/400x300/035fdb/ffffff?text=EcoTech+Humidifier+836F", - "description": "Humidifier product image" - } - ], - "created_at": "2024-11-18T09:16:11.467739Z", - "updated_at": "2025-06-03T09:16:11.467739Z" - }, - { - "id": 310, - "name": "NeoCook Toaster 361H", - "description": "NeoCook Toaster 361H is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.34, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1621, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 34.9, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 11.6, - "mass": 0.26, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 23.3, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.2, - "mass": 0.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 3101, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3102, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3103, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3104, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 310, - "url": "https://via.placeholder.com/400x300/097dcc/ffffff?text=NeoCook+Toaster+361H", - "description": "Toaster product image" - } - ], - "created_at": "2024-09-18T09:16:11.467794Z", - "updated_at": "2025-07-13T09:16:11.467794Z" - }, - { - "id": 311, - "name": "EcoTech Rice Cooker 509M", - "description": "EcoTech Rice Cooker 509M is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.11, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2053, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 39.5, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 60.5, - "mass": 2.08, - "unit": "kg" - } - ], - "components": [ - { - "id": 3111, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3112, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 311, - "url": "https://via.placeholder.com/400x300/0dddfe/ffffff?text=EcoTech+Rice+Cooker+509M", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-03-01T09:16:11.467839Z", - "updated_at": "2025-07-27T09:16:11.467839Z" - }, - { - "id": 312, - "name": "ZenGear Rice Cooker 661J", - "description": "ZenGear Rice Cooker 661J is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.28, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 802, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 21.1, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 25.2, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.2, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.5, - "mass": 0.57, - "unit": "kg" - } - ], - "components": [ - { - "id": 3121, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3122, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 312, - "url": "https://via.placeholder.com/400x300/0acc10/ffffff?text=ZenGear+Rice+Cooker+661J", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-11-11T09:16:11.467887Z", - "updated_at": "2025-07-20T09:16:11.467887Z" - }, - { - "id": 313, - "name": "CleanWave Fan 262Y", - "description": "CleanWave Fan 262Y is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 802, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 28.3, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 32.6, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 39.1, - "mass": 1.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 3131, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3132, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3133, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3134, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 313, - "url": "https://via.placeholder.com/400x300/08e7cd/ffffff?text=CleanWave+Fan+262Y", - "description": "Fan product image" - } - ], - "created_at": "2024-06-04T09:16:11.467924Z", - "updated_at": "2025-05-01T09:16:11.467924Z" - }, - { - "id": 314, - "name": "ZenGear Monitor 319J", - "description": "ZenGear Monitor 319J is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.14, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1444, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 50.0, - "mass": 1.06, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.0, - "mass": 1.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 3141, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3142, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3143, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3144, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3145, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 314, - "url": "https://via.placeholder.com/400x300/053af8/ffffff?text=ZenGear+Monitor+319J", - "description": "Monitor product image" - } - ], - "created_at": "2025-01-25T09:16:11.467976Z", - "updated_at": "2025-06-08T09:16:11.467976Z" - }, - { - "id": 315, - "name": "PureLife Toaster 140K", - "description": "PureLife Toaster 140K is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.57, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 816, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 30.3, - "mass": 1.2, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 51.5, - "mass": 1.44, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 18.2, - "mass": 0.46, - "unit": "kg" - } - ], - "components": [ - { - "id": 3151, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3152, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3153, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3154, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 315, - "url": "https://via.placeholder.com/400x300/0dc4e0/ffffff?text=PureLife+Toaster+140K", - "description": "Toaster product image" - } - ], - "created_at": "2024-10-24T09:16:11.468019Z", - "updated_at": "2025-06-30T09:16:11.468019Z" - }, - { - "id": 316, - "name": "CleanWave Fan 867B", - "description": "CleanWave Fan 867B is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2099, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 51.0, - "mass": 1.63, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 49.0, - "mass": 1.23, - "unit": "kg" - } - ], - "components": [ - { - "id": 3161, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3162, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3163, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3164, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 316, - "url": "https://via.placeholder.com/400x300/0b33d5/ffffff?text=CleanWave+Fan+867B", - "description": "Fan product image" - } - ], - "created_at": "2024-08-31T09:16:11.468074Z", - "updated_at": "2025-05-09T09:16:11.468074Z" - }, - { - "id": 317, - "name": "AquaPro Rice Cooker 996T", - "description": "AquaPro Rice Cooker 996T is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1717, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 20.5, - "mass": 0.22, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 21.9, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 57.5, - "mass": 0.89, - "unit": "kg" - } - ], - "components": [ - { - "id": 3171, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3172, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3173, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3174, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3175, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 317, - "url": "https://via.placeholder.com/400x300/0b37e0/ffffff?text=AquaPro+Rice+Cooker+996T", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-12-25T09:16:11.468115Z", - "updated_at": "2025-06-01T09:16:11.468115Z" - }, - { - "id": 318, - "name": "ZenGear Iron 445Y", - "description": "ZenGear Iron 445Y is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.57, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 925, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 16.9, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.4, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 17.8, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 39.8, - "mass": 0.91, - "unit": "kg" - } - ], - "components": [ - { - "id": 3181, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3182, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3183, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 318, - "url": "https://via.placeholder.com/400x300/042de4/ffffff?text=ZenGear+Iron+445Y", - "description": "Iron product image" - } - ], - "created_at": "2025-01-15T09:16:11.468166Z", - "updated_at": "2025-06-28T09:16:11.468166Z" - }, - { - "id": 319, - "name": "EcoTech Humidifier 976X", - "description": "EcoTech Humidifier 976X is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1850, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 17.6, - "mass": 0.7, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 45.6, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 36.8, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 3191, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3192, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3193, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3194, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 319, - "url": "https://via.placeholder.com/400x300/078ec7/ffffff?text=EcoTech+Humidifier+976X", - "description": "Humidifier product image" - } - ], - "created_at": "2024-11-28T09:16:11.468216Z", - "updated_at": "2025-07-17T09:16:11.468216Z" - }, - { - "id": 320, - "name": "NeoCook Rice Cooker 354J", - "description": "NeoCook Rice Cooker 354J is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.97, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1112, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 24.0, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.0, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.0, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.0, - "mass": 0.79, - "unit": "kg" - } - ], - "components": [ - { - "id": 3201, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3202, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3203, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 320, - "url": "https://via.placeholder.com/400x300/0a8999/ffffff?text=NeoCook+Rice+Cooker+354J", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-04-16T09:16:11.468269Z", - "updated_at": "2025-07-30T09:16:11.468269Z" - }, - { - "id": 321, - "name": "ZenGear Blender 330X", - "description": "ZenGear Blender 330X is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.27, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1383, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 36.8, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 34.7, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 28.5, - "mass": 0.36, - "unit": "kg" - } - ], - "components": [ - { - "id": 3211, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3212, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 321, - "url": "https://via.placeholder.com/400x300/0269b9/ffffff?text=ZenGear+Blender+330X", - "description": "Blender product image" - } - ], - "created_at": "2025-04-20T09:16:11.468306Z", - "updated_at": "2025-05-31T09:16:11.468306Z" - }, - { - "id": 322, - "name": "ZenGear Toaster 810D", - "description": "ZenGear Toaster 810D is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.33, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 809, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 19.9, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 28.3, - "mass": 0.98, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 26.7, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 25.1, - "mass": 0.43, - "unit": "kg" - } - ], - "components": [ - { - "id": 3221, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3222, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3223, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3224, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3225, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 322, - "url": "https://via.placeholder.com/400x300/0588a4/ffffff?text=ZenGear+Toaster+810D", - "description": "Toaster product image" - } - ], - "created_at": "2024-11-23T09:16:11.468350Z", - "updated_at": "2025-06-10T09:16:11.468350Z" - }, - { - "id": 323, - "name": "ChefMate Toaster 771L", - "description": "ChefMate Toaster 771L is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.31, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1775, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 21.8, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 29.1, - "mass": 1.12, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.5, - "mass": 0.97, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 20.6, - "mass": 0.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 3231, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3232, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 323, - "url": "https://via.placeholder.com/400x300/0a5a35/ffffff?text=ChefMate+Toaster+771L", - "description": "Toaster product image" - } - ], - "created_at": "2024-04-26T09:16:11.468385Z", - "updated_at": "2025-04-26T09:16:11.468385Z" - }, - { - "id": 324, - "name": "EcoTech Iron 631B", - "description": "EcoTech Iron 631B is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.48, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1057, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 65.1, - "mass": 2.38, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 34.9, - "mass": 0.46, - "unit": "kg" - } - ], - "components": [ - { - "id": 3241, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3242, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 324, - "url": "https://via.placeholder.com/400x300/024df6/ffffff?text=EcoTech+Iron+631B", - "description": "Iron product image" - } - ], - "created_at": "2024-07-03T09:16:11.468418Z", - "updated_at": "2025-06-16T09:16:11.468418Z" - }, - { - "id": 325, - "name": "CleanWave Iron 517C", - "description": "CleanWave Iron 517C is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1697, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 64.3, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 35.7, - "mass": 1.13, - "unit": "kg" - } - ], - "components": [ - { - "id": 3251, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3252, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3253, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 325, - "url": "https://via.placeholder.com/400x300/06e906/ffffff?text=CleanWave+Iron+517C", - "description": "Iron product image" - } - ], - "created_at": "2025-01-12T09:16:11.468465Z", - "updated_at": "2025-06-30T09:16:11.468465Z" - }, - { - "id": 326, - "name": "ChefMate Kettle 300J", - "description": "ChefMate Kettle 300J is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.36, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 30.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 822, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 76.9, - "mass": 2.51, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 23.1, - "mass": 0.88, - "unit": "kg" - } - ], - "components": [ - { - "id": 3261, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3262, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3263, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3264, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3265, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 326, - "url": "https://via.placeholder.com/400x300/029df8/ffffff?text=ChefMate+Kettle+300J", - "description": "Kettle product image" - } - ], - "created_at": "2024-11-26T09:16:11.468516Z", - "updated_at": "2025-05-03T09:16:11.468516Z" - }, - { - "id": 327, - "name": "PureLife Rice Cooker 128T", - "description": "PureLife Rice Cooker 128T is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1137, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 35.4, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 64.6, - "mass": 2.37, - "unit": "kg" - } - ], - "components": [ - { - "id": 3271, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3272, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3273, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3274, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 327, - "url": "https://via.placeholder.com/400x300/0410ea/ffffff?text=PureLife+Rice+Cooker+128T", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-07-29T09:16:11.468557Z", - "updated_at": "2025-05-23T09:16:11.468557Z" - }, - { - "id": 328, - "name": "AquaPro Humidifier 967Z", - "description": "AquaPro Humidifier 967Z is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.87, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2188, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 38.6, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 45.5, - "mass": 1.74, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 15.9, - "mass": 0.61, - "unit": "kg" - } - ], - "components": [ - { - "id": 3281, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3282, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 328, - "url": "https://via.placeholder.com/400x300/098807/ffffff?text=AquaPro+Humidifier+967Z", - "description": "Humidifier product image" - } - ], - "created_at": "2025-02-27T09:16:11.469090Z", - "updated_at": "2025-07-07T09:16:11.469090Z" - }, - { - "id": 329, - "name": "EcoTech Monitor 924D", - "description": "EcoTech Monitor 924D is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.96, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1508, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 51.3, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 15.4, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 33.3, - "mass": 0.57, - "unit": "kg" - } - ], - "components": [ - { - "id": 3291, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3292, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3293, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 329, - "url": "https://via.placeholder.com/400x300/0721a4/ffffff?text=EcoTech+Monitor+924D", - "description": "Monitor product image" - } - ], - "created_at": "2025-03-20T09:16:11.469187Z", - "updated_at": "2025-06-09T09:16:11.469187Z" - }, - { - "id": 330, - "name": "NeoCook Humidifier 208N", - "description": "NeoCook Humidifier 208N is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 845, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.3, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 33.3, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 38.3, - "mass": 1.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 3301, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3302, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 330, - "url": "https://via.placeholder.com/400x300/089931/ffffff?text=NeoCook+Humidifier+208N", - "description": "Humidifier product image" - } - ], - "created_at": "2025-01-03T09:16:11.469239Z", - "updated_at": "2025-05-30T09:16:11.469239Z" - }, - { - "id": 331, - "name": "PureLife Humidifier 211K", - "description": "PureLife Humidifier 211K is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.03, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1525, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 19.6, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 62.5, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.9, - "mass": 0.24, - "unit": "kg" - } - ], - "components": [ - { - "id": 3311, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3312, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3313, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 331, - "url": "https://via.placeholder.com/400x300/07c140/ffffff?text=PureLife+Humidifier+211K", - "description": "Humidifier product image" - } - ], - "created_at": "2025-01-08T09:16:11.469310Z", - "updated_at": "2025-07-10T09:16:11.469310Z" - }, - { - "id": 332, - "name": "CleanWave Iron 692Y", - "description": "CleanWave Iron 692Y is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.2, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1968, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 47.5, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 31.3, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 21.2, - "mass": 0.58, - "unit": "kg" - } - ], - "components": [ - { - "id": 3321, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3322, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3323, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 3324, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 3325, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 332, - "url": "https://via.placeholder.com/400x300/02bae0/ffffff?text=CleanWave+Iron+692Y", - "description": "Iron product image" - } - ], - "created_at": "2024-11-27T09:16:11.469375Z", - "updated_at": "2025-06-11T09:16:11.469375Z" - }, - { - "id": 333, - "name": "AquaPro Rice Cooker 980I", - "description": "AquaPro Rice Cooker 980I is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.7, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1593, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 30.7, - "mass": 0.45, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 27.1, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 24.5, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.7, - "mass": 0.62, - "unit": "kg" - } - ], - "components": [ - { - "id": 3331, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3332, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 333, - "url": "https://via.placeholder.com/400x300/05a542/ffffff?text=AquaPro+Rice+Cooker+980I", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-11-06T09:16:11.469437Z", - "updated_at": "2025-06-18T09:16:11.469437Z" - }, - { - "id": 334, - "name": "SmartHome Toaster 935W", - "description": "SmartHome Toaster 935W is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1198, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 72.1, - "mass": 0.88, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 27.9, - "mass": 0.51, - "unit": "kg" - } - ], - "components": [ - { - "id": 3341, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3342, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3343, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3344, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3345, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 334, - "url": "https://via.placeholder.com/400x300/03c9b8/ffffff?text=SmartHome+Toaster+935W", - "description": "Toaster product image" - } - ], - "created_at": "2025-03-31T09:16:11.469489Z", - "updated_at": "2025-07-21T09:16:11.469489Z" - }, - { - "id": 335, - "name": "NeoCook Kettle 572F", - "description": "NeoCook Kettle 572F is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.88, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1811, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 53.6, - "mass": 0.72, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 46.4, - "mass": 1.7, - "unit": "kg" - } - ], - "components": [ - { - "id": 3351, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3352, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3353, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 335, - "url": "https://via.placeholder.com/400x300/0afd50/ffffff?text=NeoCook+Kettle+572F", - "description": "Kettle product image" - } - ], - "created_at": "2024-10-28T09:16:11.469630Z", - "updated_at": "2025-07-11T09:16:11.469630Z" - }, - { - "id": 336, - "name": "ZenGear Humidifier 669I", - "description": "ZenGear Humidifier 669I is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.34, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1315, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 52.6, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 47.4, - "mass": 0.97, - "unit": "kg" - } - ], - "components": [ - { - "id": 3361, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3362, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3363, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 336, - "url": "https://via.placeholder.com/400x300/056c2d/ffffff?text=ZenGear+Humidifier+669I", - "description": "Humidifier product image" - } - ], - "created_at": "2024-03-30T09:16:11.469682Z", - "updated_at": "2025-06-13T09:16:11.469682Z" - }, - { - "id": 337, - "name": "SmartHome Toaster 335A", - "description": "SmartHome Toaster 335A is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 995, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 60.0, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 12.9, - "mass": 0.19, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.1, - "mass": 0.78, - "unit": "kg" - } - ], - "components": [ - { - "id": 3371, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3372, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3373, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3374, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 337, - "url": "https://via.placeholder.com/400x300/0a8fc6/ffffff?text=SmartHome+Toaster+335A", - "description": "Toaster product image" - } - ], - "created_at": "2024-09-03T09:16:11.469753Z", - "updated_at": "2025-07-02T09:16:11.469753Z" - }, - { - "id": 338, - "name": "NeoCook Iron 651R", - "description": "NeoCook Iron 651R is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.93, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1183, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 50.6, - "mass": 0.75, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 19.8, - "mass": 0.21, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 29.6, - "mass": 0.79, - "unit": "kg" - } - ], - "components": [ - { - "id": 3381, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3382, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3383, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 338, - "url": "https://via.placeholder.com/400x300/050ddd/ffffff?text=NeoCook+Iron+651R", - "description": "Iron product image" - } - ], - "created_at": "2025-04-12T09:16:11.469825Z", - "updated_at": "2025-05-11T09:16:11.469825Z" - }, - { - "id": 339, - "name": "ZenGear Humidifier 469E", - "description": "ZenGear Humidifier 469E is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.97, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1106, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 40.9, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 59.1, - "mass": 2.05, - "unit": "kg" - } - ], - "components": [ - { - "id": 3391, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3392, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3393, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 339, - "url": "https://via.placeholder.com/400x300/079a20/ffffff?text=ZenGear+Humidifier+469E", - "description": "Humidifier product image" - } - ], - "created_at": "2024-04-27T09:16:11.469889Z", - "updated_at": "2025-06-16T09:16:11.469889Z" - }, - { - "id": 340, - "name": "ChefMate Blender 406Y", - "description": "ChefMate Blender 406Y is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.22, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2101, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.3, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 78.7, - "mass": 2.06, - "unit": "kg" - } - ], - "components": [ - { - "id": 3401, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3402, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 340, - "url": "https://via.placeholder.com/400x300/08513b/ffffff?text=ChefMate+Blender+406Y", - "description": "Blender product image" - } - ], - "created_at": "2024-10-28T09:16:11.469934Z", - "updated_at": "2025-06-25T09:16:11.469934Z" - }, - { - "id": 341, - "name": "SmartHome Iron 936J", - "description": "SmartHome Iron 936J is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.04, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 895, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 37.1, - "mass": 1.16, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 14.3, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 37.1, - "mass": 0.99, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 11.4, - "mass": 0.31, - "unit": "kg" - } - ], - "components": [ - { - "id": 3411, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3412, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3413, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 3414, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 341, - "url": "https://via.placeholder.com/400x300/07dffb/ffffff?text=SmartHome+Iron+936J", - "description": "Iron product image" - } - ], - "created_at": "2024-05-08T09:16:11.469997Z", - "updated_at": "2025-07-20T09:16:11.469997Z" - }, - { - "id": 342, - "name": "ChefMate Coffee Maker 531I", - "description": "ChefMate Coffee Maker 531I is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.39, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1245, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 49.1, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.9, - "mass": 1.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 3421, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3422, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3423, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 342, - "url": "https://via.placeholder.com/400x300/04f9ff/ffffff?text=ChefMate+Coffee+Maker+531I", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-07-21T09:16:11.470050Z", - "updated_at": "2025-05-17T09:16:11.470050Z" - }, - { - "id": 343, - "name": "AquaPro Rice Cooker 663X", - "description": "AquaPro Rice Cooker 663X is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.33, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 941, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.1, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 8.6, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.1, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 43.2, - "mass": 1.44, - "unit": "kg" - } - ], - "components": [ - { - "id": 3431, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3432, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3433, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3434, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3435, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 343, - "url": "https://via.placeholder.com/400x300/0d122c/ffffff?text=AquaPro+Rice+Cooker+663X", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-10-30T09:16:11.470170Z", - "updated_at": "2025-07-29T09:16:11.470170Z" - }, - { - "id": 344, - "name": "SmartHome Iron 640P", - "description": "SmartHome Iron 640P is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.27, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1638, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.1, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 38.4, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 31.5, - "mass": 0.65, - "unit": "kg" - } - ], - "components": [ - { - "id": 3441, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3442, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 344, - "url": "https://via.placeholder.com/400x300/0a81f0/ffffff?text=SmartHome+Iron+640P", - "description": "Iron product image" - } - ], - "created_at": "2025-01-02T09:16:11.470225Z", - "updated_at": "2025-07-22T09:16:11.470225Z" - }, - { - "id": 345, - "name": "EcoTech Fan 472L", - "description": "EcoTech Fan 472L is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.23, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1905, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 29.3, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 47.2, - "mass": 1.88, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 23.6, - "mass": 0.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 3451, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3452, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3453, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3454, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 3455, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 345, - "url": "https://via.placeholder.com/400x300/05d410/ffffff?text=EcoTech+Fan+472L", - "description": "Fan product image" - } - ], - "created_at": "2025-03-19T09:16:11.470289Z", - "updated_at": "2025-07-27T09:16:11.470289Z" - }, - { - "id": 346, - "name": "CleanWave Coffee Maker 440K", - "description": "CleanWave Coffee Maker 440K is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.77, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2028, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 34.0, - "mass": 1.19, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 9.6, - "mass": 0.12, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 19.9, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 36.5, - "mass": 1.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 3461, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3462, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3463, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3464, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 346, - "url": "https://via.placeholder.com/400x300/08f70c/ffffff?text=CleanWave+Coffee+Maker+440K", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-08-03T09:16:11.470354Z", - "updated_at": "2025-07-03T09:16:11.470354Z" - }, - { - "id": 347, - "name": "SmartHome Blender 611E", - "description": "SmartHome Blender 611E is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1140, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 50.0, - "mass": 1.26, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 50.0, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 3471, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3472, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 3473, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 3474, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 347, - "url": "https://via.placeholder.com/400x300/02da67/ffffff?text=SmartHome+Blender+611E", - "description": "Blender product image" - } - ], - "created_at": "2025-02-28T09:16:11.470422Z", - "updated_at": "2025-04-26T09:16:11.470422Z" - }, - { - "id": 348, - "name": "SmartHome Monitor 422N", - "description": "SmartHome Monitor 422N is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.66, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1839, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 34.7, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 14.7, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.3, - "mass": 1.01, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 17.3, - "mass": 0.38, - "unit": "kg" - } - ], - "components": [ - { - "id": 3481, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3482, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3483, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3484, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3485, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 348, - "url": "https://via.placeholder.com/400x300/0ef575/ffffff?text=SmartHome+Monitor+422N", - "description": "Monitor product image" - } - ], - "created_at": "2025-04-14T09:16:11.470632Z", - "updated_at": "2025-05-28T09:16:11.470632Z" - }, - { - "id": 349, - "name": "AquaPro Coffee Maker 808N", - "description": "AquaPro Coffee Maker 808N is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1915, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 37.3, - "mass": 1.32, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 62.7, - "mass": 1.98, - "unit": "kg" - } - ], - "components": [ - { - "id": 3491, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3492, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3493, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3494, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 349, - "url": "https://via.placeholder.com/400x300/0e9510/ffffff?text=AquaPro+Coffee+Maker+808N", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-07-22T09:16:11.470689Z", - "updated_at": "2025-06-18T09:16:11.470689Z" - }, - { - "id": 350, - "name": "PureLife Monitor 123W", - "description": "PureLife Monitor 123W is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.78, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1123, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 23.2, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 34.8, - "mass": 1.28, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 42.0, - "mass": 1.1, - "unit": "kg" - } - ], - "components": [ - { - "id": 3501, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3502, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 350, - "url": "https://via.placeholder.com/400x300/0cb13a/ffffff?text=PureLife+Monitor+123W", - "description": "Monitor product image" - } - ], - "created_at": "2025-03-22T09:16:11.470746Z", - "updated_at": "2025-06-22T09:16:11.470746Z" - }, - { - "id": 351, - "name": "SmartHome Iron 238Z", - "description": "SmartHome Iron 238Z is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.3, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1109, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 32.2, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 48.3, - "mass": 1.78, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 8.5, - "mass": 0.33, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 11.0, - "mass": 0.29, - "unit": "kg" - } - ], - "components": [ - { - "id": 3511, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3512, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3513, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 3514, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 351, - "url": "https://via.placeholder.com/400x300/02560e/ffffff?text=SmartHome+Iron+238Z", - "description": "Iron product image" - } - ], - "created_at": "2024-11-15T09:16:11.470806Z", - "updated_at": "2025-06-15T09:16:11.470806Z" - }, - { - "id": 352, - "name": "ChefMate Fan 327O", - "description": "ChefMate Fan 327O is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.69, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1372, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 17.0, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 38.5, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 44.4, - "mass": 0.59, - "unit": "kg" - } - ], - "components": [ - { - "id": 3521, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3522, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3523, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3524, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 3525, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 352, - "url": "https://via.placeholder.com/400x300/05c0db/ffffff?text=ChefMate+Fan+327O", - "description": "Fan product image" - } - ], - "created_at": "2024-07-22T09:16:11.470863Z", - "updated_at": "2025-04-29T09:16:11.470863Z" - }, - { - "id": 353, - "name": "ChefMate Iron 598X", - "description": "ChefMate Iron 598X is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.19, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1061, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.3, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.3, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 22.3, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 27.1, - "mass": 1.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 3531, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3532, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3533, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 3534, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 3535, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 353, - "url": "https://via.placeholder.com/400x300/0c2aa5/ffffff?text=ChefMate+Iron+598X", - "description": "Iron product image" - } - ], - "created_at": "2025-03-23T09:16:11.470937Z", - "updated_at": "2025-05-05T09:16:11.470937Z" - }, - { - "id": 354, - "name": "EcoTech Blender 942N", - "description": "EcoTech Blender 942N is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.47, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1573, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 19.3, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 51.1, - "mass": 2.0, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 29.5, - "mass": 1.15, - "unit": "kg" - } - ], - "components": [ - { - "id": 3541, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3542, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 354, - "url": "https://via.placeholder.com/400x300/08e40f/ffffff?text=EcoTech+Blender+942N", - "description": "Blender product image" - } - ], - "created_at": "2024-08-25T09:16:11.470986Z", - "updated_at": "2025-06-16T09:16:11.470986Z" - }, - { - "id": 355, - "name": "SmartHome Fan 524Y", - "description": "SmartHome Fan 524Y is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.76, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1854, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 35.4, - "mass": 0.64, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 34.8, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.7, - "mass": 0.93, - "unit": "kg" - } - ], - "components": [ - { - "id": 3551, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3552, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3553, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 355, - "url": "https://via.placeholder.com/400x300/02a511/ffffff?text=SmartHome+Fan+524Y", - "description": "Fan product image" - } - ], - "created_at": "2025-02-24T09:16:11.471032Z", - "updated_at": "2025-06-02T09:16:11.471032Z" - }, - { - "id": 356, - "name": "CleanWave Humidifier 360F", - "description": "CleanWave Humidifier 360F is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.08, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1294, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 30.0, - "mass": 0.85, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 41.4, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.6, - "mass": 0.75, - "unit": "kg" - } - ], - "components": [ - { - "id": 3561, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3562, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3563, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3564, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 356, - "url": "https://via.placeholder.com/400x300/03beb5/ffffff?text=CleanWave+Humidifier+360F", - "description": "Humidifier product image" - } - ], - "created_at": "2024-06-26T09:16:11.471106Z", - "updated_at": "2025-04-24T09:16:11.471106Z" - }, - { - "id": 357, - "name": "AquaPro Air Purifier 208Z", - "description": "AquaPro Air Purifier 208Z is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.52, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1745, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 24.8, - "mass": 0.27, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 38.3, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 36.9, - "mass": 0.9, - "unit": "kg" - } - ], - "components": [ - { - "id": 3571, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3572, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3573, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3574, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3575, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 357, - "url": "https://via.placeholder.com/400x300/06862a/ffffff?text=AquaPro+Air+Purifier+208Z", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-10-18T09:16:11.471158Z", - "updated_at": "2025-05-26T09:16:11.471158Z" - }, - { - "id": 358, - "name": "ZenGear Monitor 388P", - "description": "ZenGear Monitor 388P is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.92, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1352, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 43.2, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 56.8, - "mass": 1.15, - "unit": "kg" - } - ], - "components": [ - { - "id": 3581, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3582, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3583, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3584, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3585, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 358, - "url": "https://via.placeholder.com/400x300/0d6e75/ffffff?text=ZenGear+Monitor+388P", - "description": "Monitor product image" - } - ], - "created_at": "2024-09-01T09:16:11.471211Z", - "updated_at": "2025-04-24T09:16:11.471211Z" - }, - { - "id": 359, - "name": "NeoCook Monitor 255A", - "description": "NeoCook Monitor 255A is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.84, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1604, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 53.5, - "mass": 2.08, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 46.5, - "mass": 1.38, - "unit": "kg" - } - ], - "components": [ - { - "id": 3591, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3592, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3593, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 359, - "url": "https://via.placeholder.com/400x300/07c03d/ffffff?text=NeoCook+Monitor+255A", - "description": "Monitor product image" - } - ], - "created_at": "2024-12-14T09:16:11.471268Z", - "updated_at": "2025-07-09T09:16:11.471268Z" - }, - { - "id": 360, - "name": "CleanWave Humidifier 538D", - "description": "CleanWave Humidifier 538D is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.23, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1871, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.3, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 6.7, - "mass": 0.17, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.0, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 38.0, - "mass": 1.19, - "unit": "kg" - } - ], - "components": [ - { - "id": 3601, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3602, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 360, - "url": "https://via.placeholder.com/400x300/0e3101/ffffff?text=CleanWave+Humidifier+538D", - "description": "Humidifier product image" - } - ], - "created_at": "2024-11-25T09:16:11.471333Z", - "updated_at": "2025-04-29T09:16:11.471333Z" - }, - { - "id": 361, - "name": "PureLife Monitor 424K", - "description": "PureLife Monitor 424K is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.74, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1874, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 52.7, - "mass": 1.52, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 47.3, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 3611, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3612, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 361, - "url": "https://via.placeholder.com/400x300/09916e/ffffff?text=PureLife+Monitor+424K", - "description": "Monitor product image" - } - ], - "created_at": "2024-11-05T09:16:11.471377Z", - "updated_at": "2025-07-01T09:16:11.471377Z" - }, - { - "id": 362, - "name": "CleanWave Rice Cooker 678K", - "description": "CleanWave Rice Cooker 678K is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1960, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 54.3, - "mass": 1.96, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 17.1, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.6, - "mass": 1.07, - "unit": "kg" - } - ], - "components": [ - { - "id": 3621, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3622, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3623, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3624, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3625, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 362, - "url": "https://via.placeholder.com/400x300/038519/ffffff?text=CleanWave+Rice+Cooker+678K", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-08-31T09:16:11.471443Z", - "updated_at": "2025-06-22T09:16:11.471443Z" - }, - { - "id": 363, - "name": "AquaPro Coffee Maker 546D", - "description": "AquaPro Coffee Maker 546D is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.91, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1654, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 43.8, - "mass": 1.41, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 9.1, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 9.1, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.0, - "mass": 0.79, - "unit": "kg" - } - ], - "components": [ - { - "id": 3631, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3632, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 363, - "url": "https://via.placeholder.com/400x300/066cc4/ffffff?text=AquaPro+Coffee+Maker+546D", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-05-23T09:16:11.471496Z", - "updated_at": "2025-06-15T09:16:11.471496Z" - }, - { - "id": 364, - "name": "AquaPro Blender 920U", - "description": "AquaPro Blender 920U is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.71, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2069, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 26.5, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 27.2, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.9, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.4, - "mass": 0.57, - "unit": "kg" - } - ], - "components": [ - { - "id": 3641, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3642, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 364, - "url": "https://via.placeholder.com/400x300/066b68/ffffff?text=AquaPro+Blender+920U", - "description": "Blender product image" - } - ], - "created_at": "2025-02-28T09:16:11.471553Z", - "updated_at": "2025-07-21T09:16:11.471553Z" - }, - { - "id": 365, - "name": "CleanWave Blender 815R", - "description": "CleanWave Blender 815R is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.14, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2136, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 30.0, - "mass": 0.84, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.8, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 42.2, - "mass": 1.52, - "unit": "kg" - } - ], - "components": [ - { - "id": 3651, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3652, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 3653, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 3654, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 3655, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 365, - "url": "https://via.placeholder.com/400x300/0c5cff/ffffff?text=CleanWave+Blender+815R", - "description": "Blender product image" - } - ], - "created_at": "2024-11-28T09:16:11.471615Z", - "updated_at": "2025-07-06T09:16:11.471615Z" - }, - { - "id": 366, - "name": "EcoTech Monitor 667L", - "description": "EcoTech Monitor 667L is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2017, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 64.4, - "mass": 2.18, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 35.6, - "mass": 0.46, - "unit": "kg" - } - ], - "components": [ - { - "id": 3661, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3662, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3663, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3664, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3665, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 366, - "url": "https://via.placeholder.com/400x300/06274f/ffffff?text=EcoTech+Monitor+667L", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-03T09:16:11.471675Z", - "updated_at": "2025-05-25T09:16:11.471675Z" - }, - { - "id": 367, - "name": "ChefMate Monitor 439N", - "description": "ChefMate Monitor 439N is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.54, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1726, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 9.6, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 17.3, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 53.8, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 19.2, - "mass": 0.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 3671, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3672, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3673, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3674, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 367, - "url": "https://via.placeholder.com/400x300/099931/ffffff?text=ChefMate+Monitor+439N", - "description": "Monitor product image" - } - ], - "created_at": "2025-02-28T09:16:11.471733Z", - "updated_at": "2025-07-09T09:16:11.471733Z" - }, - { - "id": 368, - "name": "NeoCook Kettle 142Q", - "description": "NeoCook Kettle 142Q is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.7, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1096, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 39.9, - "mass": 1.17, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 33.6, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 26.6, - "mass": 0.47, - "unit": "kg" - } - ], - "components": [ - { - "id": 3681, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3682, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3683, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3684, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 368, - "url": "https://via.placeholder.com/400x300/0c4a10/ffffff?text=NeoCook+Kettle+142Q", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-01T09:16:11.471797Z", - "updated_at": "2025-06-07T09:16:11.471797Z" - }, - { - "id": 369, - "name": "NeoCook Coffee Maker 650W", - "description": "NeoCook Coffee Maker 650W is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.74, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2057, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 20.6, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 39.7, - "mass": 1.33, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 39.7, - "mass": 0.69, - "unit": "kg" - } - ], - "components": [ - { - "id": 3691, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3692, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 369, - "url": "https://via.placeholder.com/400x300/0f0d19/ffffff?text=NeoCook+Coffee+Maker+650W", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-07-02T09:16:11.471846Z", - "updated_at": "2025-05-22T09:16:11.471846Z" - }, - { - "id": 370, - "name": "SmartHome Air Purifier 272A", - "description": "SmartHome Air Purifier 272A is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.79, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1877, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 35.7, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 39.7, - "mass": 1.05, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 9.5, - "mass": 0.27, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 15.1, - "mass": 0.55, - "unit": "kg" - } - ], - "components": [ - { - "id": 3701, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3702, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3703, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3704, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 370, - "url": "https://via.placeholder.com/400x300/071d71/ffffff?text=SmartHome+Air+Purifier+272A", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-10-06T09:16:11.471966Z", - "updated_at": "2025-05-21T09:16:11.471966Z" - }, - { - "id": 371, - "name": "ChefMate Fan 167R", - "description": "ChefMate Fan 167R is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.21, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2157, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 8.5, - "mass": 0.23, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 33.9, - "mass": 1.01, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 33.1, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 24.6, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 3711, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3712, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3713, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3714, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 3715, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 371, - "url": "https://via.placeholder.com/400x300/0b339f/ffffff?text=ChefMate+Fan+167R", - "description": "Fan product image" - } - ], - "created_at": "2024-10-28T09:16:11.472033Z", - "updated_at": "2025-06-05T09:16:11.472033Z" - }, - { - "id": 372, - "name": "EcoTech Humidifier 695R", - "description": "EcoTech Humidifier 695R is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.96, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2083, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.1, - "mass": 0.74, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 76.9, - "mass": 1.74, - "unit": "kg" - } - ], - "components": [ - { - "id": 3721, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3722, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 372, - "url": "https://via.placeholder.com/400x300/01f9d2/ffffff?text=EcoTech+Humidifier+695R", - "description": "Humidifier product image" - } - ], - "created_at": "2025-02-15T09:16:11.472084Z", - "updated_at": "2025-06-02T09:16:11.472084Z" - }, - { - "id": 373, - "name": "ZenGear Kettle 958R", - "description": "ZenGear Kettle 958R is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2123, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 10.4, - "mass": 0.25, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.9, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.6, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 33.0, - "mass": 1.03, - "unit": "kg" - } - ], - "components": [ - { - "id": 3731, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3732, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3733, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3734, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 373, - "url": "https://via.placeholder.com/400x300/0ab909/ffffff?text=ZenGear+Kettle+958R", - "description": "Kettle product image" - } - ], - "created_at": "2025-01-02T09:16:11.472136Z", - "updated_at": "2025-04-24T09:16:11.472136Z" - }, - { - "id": 374, - "name": "SmartHome Iron 659S", - "description": "SmartHome Iron 659S is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1523, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 59.1, - "mass": 1.08, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 40.9, - "mass": 0.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 3741, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3742, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3743, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 3744, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 3745, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 374, - "url": "https://via.placeholder.com/400x300/0503bf/ffffff?text=SmartHome+Iron+659S", - "description": "Iron product image" - } - ], - "created_at": "2025-01-23T09:16:11.472197Z", - "updated_at": "2025-05-30T09:16:11.472197Z" - }, - { - "id": 375, - "name": "PureLife Iron 683G", - "description": "PureLife Iron 683G is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.72, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2034, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 36.5, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 63.5, - "mass": 1.51, - "unit": "kg" - } - ], - "components": [ - { - "id": 3751, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3752, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 375, - "url": "https://via.placeholder.com/400x300/063462/ffffff?text=PureLife+Iron+683G", - "description": "Iron product image" - } - ], - "created_at": "2024-11-09T09:16:11.472251Z", - "updated_at": "2025-05-24T09:16:11.472251Z" - }, - { - "id": 376, - "name": "SmartHome Air Purifier 358B", - "description": "SmartHome Air Purifier 358B is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1995, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 50.0, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 50.0, - "mass": 1.76, - "unit": "kg" - } - ], - "components": [ - { - "id": 3761, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3762, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3763, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 376, - "url": "https://via.placeholder.com/400x300/09c5e0/ffffff?text=SmartHome+Air+Purifier+358B", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-09-01T09:16:11.472304Z", - "updated_at": "2025-06-26T09:16:11.472304Z" - }, - { - "id": 377, - "name": "ZenGear Kettle 126C", - "description": "ZenGear Kettle 126C is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.87, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1718, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 57.1, - "mass": 1.46, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 24.8, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.1, - "mass": 0.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 3771, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3772, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3773, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 377, - "url": "https://via.placeholder.com/400x300/08d421/ffffff?text=ZenGear+Kettle+126C", - "description": "Kettle product image" - } - ], - "created_at": "2024-04-06T09:16:11.472370Z", - "updated_at": "2025-07-02T09:16:11.472370Z" - }, - { - "id": 378, - "name": "SmartHome Blender 357W", - "description": "SmartHome Blender 357W is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.91, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1383, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 16.0, - "mass": 0.64, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 57.4, - "mass": 2.22, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.6, - "mass": 0.87, - "unit": "kg" - } - ], - "components": [ - { - "id": 3781, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3782, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 3783, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 3784, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 378, - "url": "https://via.placeholder.com/400x300/0c34c2/ffffff?text=SmartHome+Blender+357W", - "description": "Blender product image" - } - ], - "created_at": "2024-07-27T09:16:11.472426Z", - "updated_at": "2025-04-25T09:16:11.472426Z" - }, - { - "id": 379, - "name": "AquaPro Coffee Maker 348Z", - "description": "AquaPro Coffee Maker 348Z is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.79, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1153, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 33.8, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 66.2, - "mass": 1.81, - "unit": "kg" - } - ], - "components": [ - { - "id": 3791, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3792, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3793, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3794, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 379, - "url": "https://via.placeholder.com/400x300/051b95/ffffff?text=AquaPro+Coffee+Maker+348Z", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-12-15T09:16:11.472483Z", - "updated_at": "2025-05-07T09:16:11.472483Z" - }, - { - "id": 380, - "name": "NeoCook Humidifier 197O", - "description": "NeoCook Humidifier 197O is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.02, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1534, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 32.7, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 36.0, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 18.0, - "mass": 0.62, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 13.3, - "mass": 0.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 3801, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3802, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3803, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3804, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 380, - "url": "https://via.placeholder.com/400x300/0e9df9/ffffff?text=NeoCook+Humidifier+197O", - "description": "Humidifier product image" - } - ], - "created_at": "2024-12-07T09:16:11.472543Z", - "updated_at": "2025-07-03T09:16:11.472543Z" - }, - { - "id": 381, - "name": "NeoCook Iron 597T", - "description": "NeoCook Iron 597T is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 925, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 43.2, - "mass": 0.82, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 44.0, - "mass": 1.49, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 12.8, - "mass": 0.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 3811, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3812, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3813, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 381, - "url": "https://via.placeholder.com/400x300/0a5cca/ffffff?text=NeoCook+Iron+597T", - "description": "Iron product image" - } - ], - "created_at": "2024-07-15T09:16:11.472600Z", - "updated_at": "2025-06-25T09:16:11.472600Z" - }, - { - "id": 382, - "name": "AquaPro Toaster 280U", - "description": "AquaPro Toaster 280U is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.9, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1366, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 34.1, - "mass": 1.22, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 21.4, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 44.4, - "mass": 1.35, - "unit": "kg" - } - ], - "components": [ - { - "id": 3821, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3822, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3823, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3824, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 382, - "url": "https://via.placeholder.com/400x300/0db2e3/ffffff?text=AquaPro+Toaster+280U", - "description": "Toaster product image" - } - ], - "created_at": "2024-12-08T09:16:11.472654Z", - "updated_at": "2025-07-31T09:16:11.472654Z" - }, - { - "id": 383, - "name": "NeoCook Monitor 940X", - "description": "NeoCook Monitor 940X is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.66, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2049, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 22.3, - "mass": 0.75, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 35.1, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 42.6, - "mass": 1.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 3831, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3832, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3833, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3834, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 383, - "url": "https://via.placeholder.com/400x300/0c1ffc/ffffff?text=NeoCook+Monitor+940X", - "description": "Monitor product image" - } - ], - "created_at": "2024-08-02T09:16:11.472717Z", - "updated_at": "2025-04-28T09:16:11.472717Z" - }, - { - "id": 384, - "name": "NeoCook Coffee Maker 940C", - "description": "NeoCook Coffee Maker 940C is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.41, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1729, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 40.0, - "mass": 1.34, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.0, - "mass": 1.17, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 21.0, - "mass": 0.79, - "unit": "kg" - } - ], - "components": [ - { - "id": 3841, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3842, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3843, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3844, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3845, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 384, - "url": "https://via.placeholder.com/400x300/03ccf6/ffffff?text=NeoCook+Coffee+Maker+940C", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-04-05T09:16:11.472782Z", - "updated_at": "2025-07-25T09:16:11.472782Z" - }, - { - "id": 385, - "name": "NeoCook Blender 767P", - "description": "NeoCook Blender 767P is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.58, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1909, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 51.5, - "mass": 2.01, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.4, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.2, - "mass": 1.05, - "unit": "kg" - } - ], - "components": [ - { - "id": 3851, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3852, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 3853, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 3854, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 3855, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 385, - "url": "https://via.placeholder.com/400x300/0ae109/ffffff?text=NeoCook+Blender+767P", - "description": "Blender product image" - } - ], - "created_at": "2025-01-18T09:16:11.472846Z", - "updated_at": "2025-06-17T09:16:11.472846Z" - }, - { - "id": 386, - "name": "PureLife Fan 397H", - "description": "PureLife Fan 397H is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.35, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1268, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.6, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 27.8, - "mass": 0.41, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 48.6, - "mass": 0.53, - "unit": "kg" - } - ], - "components": [ - { - "id": 3861, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3862, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3863, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3864, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 386, - "url": "https://via.placeholder.com/400x300/0d571e/ffffff?text=PureLife+Fan+397H", - "description": "Fan product image" - } - ], - "created_at": "2024-09-30T09:16:11.472900Z", - "updated_at": "2025-07-10T09:16:11.472900Z" - }, - { - "id": 387, - "name": "SmartHome Coffee Maker 502Q", - "description": "SmartHome Coffee Maker 502Q is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.27, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1794, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 40.5, - "mass": 0.98, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 9.5, - "mass": 0.22, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 34.5, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 15.5, - "mass": 0.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 3871, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3872, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 3873, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 387, - "url": "https://via.placeholder.com/400x300/061a97/ffffff?text=SmartHome+Coffee+Maker+502Q", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-03-28T09:16:11.472959Z", - "updated_at": "2025-07-11T09:16:11.472959Z" - }, - { - "id": 388, - "name": "SmartHome Humidifier 753A", - "description": "SmartHome Humidifier 753A is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.31, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 30.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2195, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 27.8, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 24.7, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 20.4, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.2, - "mass": 0.92, - "unit": "kg" - } - ], - "components": [ - { - "id": 3881, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3882, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3883, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3884, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 388, - "url": "https://via.placeholder.com/400x300/0a9019/ffffff?text=SmartHome+Humidifier+753A", - "description": "Humidifier product image" - } - ], - "created_at": "2024-07-02T09:16:11.473018Z", - "updated_at": "2025-07-14T09:16:11.473018Z" - }, - { - "id": 389, - "name": "ChefMate Rice Cooker 395K", - "description": "ChefMate Rice Cooker 395K is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.35, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1871, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 52.8, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 47.2, - "mass": 1.43, - "unit": "kg" - } - ], - "components": [ - { - "id": 3891, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3892, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3893, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 3894, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 389, - "url": "https://via.placeholder.com/400x300/0adc29/ffffff?text=ChefMate+Rice+Cooker+395K", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-04-17T09:16:11.473086Z", - "updated_at": "2025-06-27T09:16:11.473086Z" - }, - { - "id": 390, - "name": "EcoTech Iron 792K", - "description": "EcoTech Iron 792K is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1683, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 77.6, - "mass": 1.25, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 22.4, - "mass": 0.29, - "unit": "kg" - } - ], - "components": [ - { - "id": 3901, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 3902, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 3903, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 390, - "url": "https://via.placeholder.com/400x300/02f7a7/ffffff?text=EcoTech+Iron+792K", - "description": "Iron product image" - } - ], - "created_at": "2024-06-14T09:16:11.473133Z", - "updated_at": "2025-05-16T09:16:11.473133Z" - }, - { - "id": 391, - "name": "CleanWave Kettle 221G", - "description": "CleanWave Kettle 221G is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.68, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1875, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 48.8, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 51.2, - "mass": 0.61, - "unit": "kg" - } - ], - "components": [ - { - "id": 3911, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3912, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3913, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 391, - "url": "https://via.placeholder.com/400x300/07f59d/ffffff?text=CleanWave+Kettle+221G", - "description": "Kettle product image" - } - ], - "created_at": "2024-05-31T09:16:11.473184Z", - "updated_at": "2025-07-12T09:16:11.473184Z" - }, - { - "id": 392, - "name": "SmartHome Air Purifier 987Y", - "description": "SmartHome Air Purifier 987Y is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.7, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1843, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 48.6, - "mass": 1.82, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 51.4, - "mass": 1.48, - "unit": "kg" - } - ], - "components": [ - { - "id": 3921, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3922, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3923, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 3924, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 392, - "url": "https://via.placeholder.com/400x300/0b5a99/ffffff?text=SmartHome+Air+Purifier+987Y", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-12-17T09:16:11.473240Z", - "updated_at": "2025-05-15T09:16:11.473240Z" - }, - { - "id": 393, - "name": "EcoTech Blender 261Q", - "description": "EcoTech Blender 261Q is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2061, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 30.2, - "mass": 1.19, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 30.9, - "mass": 0.63, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 21.6, - "mass": 0.58, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 17.3, - "mass": 0.68, - "unit": "kg" - } - ], - "components": [ - { - "id": 3931, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 3932, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 3933, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 3934, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 393, - "url": "https://via.placeholder.com/400x300/0a62ec/ffffff?text=EcoTech+Blender+261Q", - "description": "Blender product image" - } - ], - "created_at": "2025-03-27T09:16:11.473313Z", - "updated_at": "2025-07-30T09:16:11.473313Z" - }, - { - "id": 394, - "name": "ZenGear Monitor 970W", - "description": "ZenGear Monitor 970W is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.96, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2050, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 29.3, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 35.9, - "mass": 0.87, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 34.7, - "mass": 1.02, - "unit": "kg" - } - ], - "components": [ - { - "id": 3941, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3942, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 3943, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 394, - "url": "https://via.placeholder.com/400x300/0deb64/ffffff?text=ZenGear+Monitor+970W", - "description": "Monitor product image" - } - ], - "created_at": "2024-06-04T09:16:11.473363Z", - "updated_at": "2025-07-29T09:16:11.473363Z" - }, - { - "id": 395, - "name": "ZenGear Toaster 160N", - "description": "ZenGear Toaster 160N is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.8, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2051, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 15.7, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 84.3, - "mass": 2.15, - "unit": "kg" - } - ], - "components": [ - { - "id": 3951, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 3952, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 395, - "url": "https://via.placeholder.com/400x300/03488f/ffffff?text=ZenGear+Toaster+160N", - "description": "Toaster product image" - } - ], - "created_at": "2025-03-29T09:16:11.473417Z", - "updated_at": "2025-07-12T09:16:11.473417Z" - }, - { - "id": 396, - "name": "CleanWave Kettle 771D", - "description": "CleanWave Kettle 771D is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.15, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1268, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 15.1, - "mass": 0.34, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 17.8, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.8, - "mass": 1.02, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 28.3, - "mass": 0.66, - "unit": "kg" - } - ], - "components": [ - { - "id": 3961, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3962, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3963, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 3964, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 396, - "url": "https://via.placeholder.com/400x300/093dad/ffffff?text=CleanWave+Kettle+771D", - "description": "Kettle product image" - } - ], - "created_at": "2024-07-13T09:16:11.473487Z", - "updated_at": "2025-06-26T09:16:11.473487Z" - }, - { - "id": 397, - "name": "ZenGear Fan 286A", - "description": "ZenGear Fan 286A is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.26, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 865, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 49.1, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.4, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 32.5, - "mass": 1.19, - "unit": "kg" - } - ], - "components": [ - { - "id": 3971, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 3972, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 3973, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 3974, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 397, - "url": "https://via.placeholder.com/400x300/0e9894/ffffff?text=ZenGear+Fan+286A", - "description": "Fan product image" - } - ], - "created_at": "2024-05-28T09:16:11.473541Z", - "updated_at": "2025-06-03T09:16:11.473541Z" - }, - { - "id": 398, - "name": "AquaPro Humidifier 521X", - "description": "AquaPro Humidifier 521X is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1186, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 9.1, - "mass": 0.28, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 43.6, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.0, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 27.3, - "mass": 0.32, - "unit": "kg" - } - ], - "components": [ - { - "id": 3981, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3982, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 398, - "url": "https://via.placeholder.com/400x300/0c8502/ffffff?text=AquaPro+Humidifier+521X", - "description": "Humidifier product image" - } - ], - "created_at": "2024-04-02T09:16:11.473597Z", - "updated_at": "2025-05-03T09:16:11.473597Z" - }, - { - "id": 399, - "name": "CleanWave Humidifier 631V", - "description": "CleanWave Humidifier 631V is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.88, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 866, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.8, - "mass": 1.04, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 36.2, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 30.0, - "mass": 0.7, - "unit": "kg" - } - ], - "components": [ - { - "id": 3991, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3992, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3993, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3994, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 3995, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 399, - "url": "https://via.placeholder.com/400x300/0b1fba/ffffff?text=CleanWave+Humidifier+631V", - "description": "Humidifier product image" - } - ], - "created_at": "2025-03-02T09:16:11.473655Z", - "updated_at": "2025-07-24T09:16:11.473655Z" - }, - { - "id": 400, - "name": "AquaPro Humidifier 657I", - "description": "AquaPro Humidifier 657I is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.06, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1684, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 49.0, - "mass": 1.23, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 16.0, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 35.0, - "mass": 1.08, - "unit": "kg" - } - ], - "components": [ - { - "id": 4001, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4002, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4003, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 400, - "url": "https://via.placeholder.com/400x300/060be2/ffffff?text=AquaPro+Humidifier+657I", - "description": "Humidifier product image" - } - ], - "created_at": "2025-01-22T09:16:11.473711Z", - "updated_at": "2025-07-07T09:16:11.473711Z" - }, - { - "id": 401, - "name": "PureLife Monitor 309E", - "description": "PureLife Monitor 309E is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.32, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1857, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 60.3, - "mass": 1.55, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 39.7, - "mass": 0.97, - "unit": "kg" - } - ], - "components": [ - { - "id": 4011, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4012, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4013, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 401, - "url": "https://via.placeholder.com/400x300/068e9e/ffffff?text=PureLife+Monitor+309E", - "description": "Monitor product image" - } - ], - "created_at": "2024-06-10T09:16:11.473764Z", - "updated_at": "2025-05-09T09:16:11.473764Z" - }, - { - "id": 402, - "name": "ZenGear Blender 708U", - "description": "ZenGear Blender 708U is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.9, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1547, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 54.0, - "mass": 1.9, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 46.0, - "mass": 0.51, - "unit": "kg" - } - ], - "components": [ - { - "id": 4021, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4022, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4023, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 402, - "url": "https://via.placeholder.com/400x300/0458c7/ffffff?text=ZenGear+Blender+708U", - "description": "Blender product image" - } - ], - "created_at": "2025-02-09T09:16:11.473826Z", - "updated_at": "2025-05-21T09:16:11.473826Z" - }, - { - "id": 403, - "name": "ZenGear Kettle 161D", - "description": "ZenGear Kettle 161D is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.2, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1171, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.6, - "mass": 0.52, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 8.5, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.3, - "mass": 0.21, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 34.6, - "mass": 0.89, - "unit": "kg" - } - ], - "components": [ - { - "id": 4031, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4032, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 403, - "url": "https://via.placeholder.com/400x300/0d02aa/ffffff?text=ZenGear+Kettle+161D", - "description": "Kettle product image" - } - ], - "created_at": "2024-12-18T09:16:11.473884Z", - "updated_at": "2025-07-12T09:16:11.473884Z" - }, - { - "id": 404, - "name": "PureLife Monitor 924P", - "description": "PureLife Monitor 924P is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.18, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1457, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.6, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 28.6, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 42.9, - "mass": 1.16, - "unit": "kg" - } - ], - "components": [ - { - "id": 4041, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4042, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4043, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4044, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4045, - "name": "Monitor Component 5", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 404, - "url": "https://via.placeholder.com/400x300/080e63/ffffff?text=PureLife+Monitor+924P", - "description": "Monitor product image" - } - ], - "created_at": "2025-01-31T09:16:11.474008Z", - "updated_at": "2025-05-03T09:16:11.474008Z" - }, - { - "id": 405, - "name": "CleanWave Blender 195Y", - "description": "CleanWave Blender 195Y is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.97, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1167, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 52.5, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 47.5, - "mass": 1.16, - "unit": "kg" - } - ], - "components": [ - { - "id": 4051, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4052, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4053, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 4054, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 405, - "url": "https://via.placeholder.com/400x300/0edf63/ffffff?text=CleanWave+Blender+195Y", - "description": "Blender product image" - } - ], - "created_at": "2025-03-31T09:16:11.474063Z", - "updated_at": "2025-07-21T09:16:11.474063Z" - }, - { - "id": 406, - "name": "PureLife Coffee Maker 549B", - "description": "PureLife Coffee Maker 549B is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.05, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 24.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1923, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 42.3, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 34.5, - "mass": 0.81, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 16.2, - "mass": 0.44, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 7.0, - "mass": 0.22, - "unit": "kg" - } - ], - "components": [ - { - "id": 4061, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4062, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 406, - "url": "https://via.placeholder.com/400x300/0d136f/ffffff?text=PureLife+Coffee+Maker+549B", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-05-21T09:16:11.474123Z", - "updated_at": "2025-07-10T09:16:11.474123Z" - }, - { - "id": 407, - "name": "SmartHome Toaster 629M", - "description": "SmartHome Toaster 629M is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 834, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 38.8, - "mass": 1.37, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 61.2, - "mass": 1.66, - "unit": "kg" - } - ], - "components": [ - { - "id": 4071, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4072, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4073, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4074, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 407, - "url": "https://via.placeholder.com/400x300/0389a8/ffffff?text=SmartHome+Toaster+629M", - "description": "Toaster product image" - } - ], - "created_at": "2024-12-01T09:16:11.474179Z", - "updated_at": "2025-06-15T09:16:11.474179Z" - }, - { - "id": 408, - "name": "PureLife Monitor 231I", - "description": "PureLife Monitor 231I is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.89, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1726, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 53.1, - "mass": 2.0, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 46.9, - "mass": 1.08, - "unit": "kg" - } - ], - "components": [ - { - "id": 4081, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4082, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4083, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4084, - "name": "Monitor Component 4", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 408, - "url": "https://via.placeholder.com/400x300/02248f/ffffff?text=PureLife+Monitor+231I", - "description": "Monitor product image" - } - ], - "created_at": "2024-03-28T09:16:11.474233Z", - "updated_at": "2025-06-17T09:16:11.474233Z" - }, - { - "id": 409, - "name": "ZenGear Fan 109Y", - "description": "ZenGear Fan 109Y is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.37, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1380, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 32.9, - "mass": 0.7, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 67.1, - "mass": 2.06, - "unit": "kg" - } - ], - "components": [ - { - "id": 4091, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 4092, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 4093, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 4094, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 4095, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 409, - "url": "https://via.placeholder.com/400x300/04d8bc/ffffff?text=ZenGear+Fan+109Y", - "description": "Fan product image" - } - ], - "created_at": "2025-04-12T09:16:11.474273Z", - "updated_at": "2025-07-02T09:16:11.474273Z" - }, - { - "id": 410, - "name": "EcoTech Toaster 705C", - "description": "EcoTech Toaster 705C is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.81, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1207, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 24.3, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.1, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 18.8, - "mass": 0.64, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 36.8, - "mass": 1.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 4101, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4102, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4103, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4104, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4105, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 410, - "url": "https://via.placeholder.com/400x300/04bf1f/ffffff?text=EcoTech+Toaster+705C", - "description": "Toaster product image" - } - ], - "created_at": "2024-07-28T09:16:11.474331Z", - "updated_at": "2025-05-27T09:16:11.474331Z" - }, - { - "id": 411, - "name": "AquaPro Kettle 901D", - "description": "AquaPro Kettle 901D is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.97, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1922, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 11.2, - "mass": 0.24, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 14.7, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 32.8, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 41.4, - "mass": 1.2, - "unit": "kg" - } - ], - "components": [ - { - "id": 4111, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4112, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4113, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4114, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4115, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 411, - "url": "https://via.placeholder.com/400x300/0b8b33/ffffff?text=AquaPro+Kettle+901D", - "description": "Kettle product image" - } - ], - "created_at": "2024-03-26T09:16:11.474388Z", - "updated_at": "2025-07-09T09:16:11.474388Z" - }, - { - "id": 412, - "name": "SmartHome Toaster 310E", - "description": "SmartHome Toaster 310E is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.78, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 855, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 51.7, - "mass": 1.13, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 48.3, - "mass": 1.22, - "unit": "kg" - } - ], - "components": [ - { - "id": 4121, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4122, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4123, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4124, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 412, - "url": "https://via.placeholder.com/400x300/0d4db9/ffffff?text=SmartHome+Toaster+310E", - "description": "Toaster product image" - } - ], - "created_at": "2024-10-30T09:16:11.474434Z", - "updated_at": "2025-07-21T09:16:11.474434Z" - }, - { - "id": 413, - "name": "EcoTech Iron 639I", - "description": "EcoTech Iron 639I is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.61, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1597, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 30.4, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 25.5, - "mass": 0.83, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 44.1, - "mass": 1.37, - "unit": "kg" - } - ], - "components": [ - { - "id": 4131, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4132, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 4133, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 4134, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - }, - { - "id": 4135, - "name": "Iron Component 5", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 413, - "url": "https://via.placeholder.com/400x300/06bb74/ffffff?text=EcoTech+Iron+639I", - "description": "Iron product image" - } - ], - "created_at": "2024-09-06T09:16:11.474511Z", - "updated_at": "2025-05-03T09:16:11.474511Z" - }, - { - "id": 414, - "name": "ZenGear Iron 435D", - "description": "ZenGear Iron 435D is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.88, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1253, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 30.6, - "mass": 0.93, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.9, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 21.9, - "mass": 0.8, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 26.5, - "mass": 0.82, - "unit": "kg" - } - ], - "components": [ - { - "id": 4141, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4142, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 414, - "url": "https://via.placeholder.com/400x300/02cc71/ffffff?text=ZenGear+Iron+435D", - "description": "Iron product image" - } - ], - "created_at": "2024-11-02T09:16:11.474567Z", - "updated_at": "2025-07-27T09:16:11.474567Z" - }, - { - "id": 415, - "name": "EcoTech Coffee Maker 881G", - "description": "EcoTech Coffee Maker 881G is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.38, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2149, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.8, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 59.2, - "mass": 1.91, - "unit": "kg" - } - ], - "components": [ - { - "id": 4151, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4152, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4153, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 415, - "url": "https://via.placeholder.com/400x300/0c44f4/ffffff?text=EcoTech+Coffee+Maker+881G", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-12-18T09:16:11.474605Z", - "updated_at": "2025-06-12T09:16:11.474605Z" - }, - { - "id": 416, - "name": "PureLife Blender 214I", - "description": "PureLife Blender 214I is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.86, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1465, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 31.1, - "mass": 1.21, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 23.2, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 37.1, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 8.6, - "mass": 0.14, - "unit": "kg" - } - ], - "components": [ - { - "id": 4161, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4162, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4163, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 4164, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 4165, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 416, - "url": "https://via.placeholder.com/400x300/064489/ffffff?text=PureLife+Blender+214I", - "description": "Blender product image" - } - ], - "created_at": "2024-09-22T09:16:11.474677Z", - "updated_at": "2025-05-30T09:16:11.474677Z" - }, - { - "id": 417, - "name": "ChefMate Monitor 611R", - "description": "ChefMate Monitor 611R is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.56, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1280, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 29.5, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 70.5, - "mass": 0.78, - "unit": "kg" - } - ], - "components": [ - { - "id": 4171, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4172, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 417, - "url": "https://via.placeholder.com/400x300/0e1aee/ffffff?text=ChefMate+Monitor+611R", - "description": "Monitor product image" - } - ], - "created_at": "2024-06-25T09:16:11.474722Z", - "updated_at": "2025-05-08T09:16:11.474722Z" - }, - { - "id": 418, - "name": "ChefMate Air Purifier 787V", - "description": "ChefMate Air Purifier 787V is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.05, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2059, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 13.7, - "mass": 0.43, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 53.7, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 32.6, - "mass": 0.83, - "unit": "kg" - } - ], - "components": [ - { - "id": 4181, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4182, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4183, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 418, - "url": "https://via.placeholder.com/400x300/0aaca8/ffffff?text=ChefMate+Air+Purifier+787V", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-09-02T09:16:11.474769Z", - "updated_at": "2025-07-06T09:16:11.474769Z" - }, - { - "id": 419, - "name": "EcoTech Kettle 870P", - "description": "EcoTech Kettle 870P is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.69, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2120, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 25.4, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 74.6, - "mass": 2.85, - "unit": "kg" - } - ], - "components": [ - { - "id": 4191, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4192, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4193, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4194, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4195, - "name": "Kettle Component 5", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 419, - "url": "https://via.placeholder.com/400x300/08a405/ffffff?text=EcoTech+Kettle+870P", - "description": "Kettle product image" - } - ], - "created_at": "2024-07-24T09:16:11.474814Z", - "updated_at": "2025-07-27T09:16:11.474814Z" - }, - { - "id": 420, - "name": "EcoTech Rice Cooker 467K", - "description": "EcoTech Rice Cooker 467K is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.78, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 30.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1972, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 71.1, - "mass": 2.49, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.9, - "mass": 0.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 4201, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4202, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4203, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 420, - "url": "https://via.placeholder.com/400x300/0cc027/ffffff?text=EcoTech+Rice+Cooker+467K", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-01-10T09:16:11.474879Z", - "updated_at": "2025-06-23T09:16:11.474879Z" - }, - { - "id": 421, - "name": "PureLife Rice Cooker 120N", - "description": "PureLife Rice Cooker 120N is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.86, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1374, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 54.1, - "mass": 1.58, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 45.9, - "mass": 0.78, - "unit": "kg" - } - ], - "components": [ - { - "id": 4211, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4212, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4213, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 421, - "url": "https://via.placeholder.com/400x300/0670be/ffffff?text=PureLife+Rice+Cooker+120N", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-12-15T09:16:11.474921Z", - "updated_at": "2025-06-02T09:16:11.474921Z" - }, - { - "id": 422, - "name": "PureLife Humidifier 155E", - "description": "PureLife Humidifier 155E is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.55, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1766, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 34.4, - "mass": 1.32, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 36.8, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 28.8, - "mass": 0.75, - "unit": "kg" - } - ], - "components": [ - { - "id": 4221, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4222, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4223, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4224, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4225, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 422, - "url": "https://via.placeholder.com/400x300/06a751/ffffff?text=PureLife+Humidifier+155E", - "description": "Humidifier product image" - } - ], - "created_at": "2024-07-31T09:16:11.474964Z", - "updated_at": "2025-07-09T09:16:11.474964Z" - }, - { - "id": 423, - "name": "EcoTech Kettle 934T", - "description": "EcoTech Kettle 934T is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.63, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2062, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 10.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 19.0, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 20.4, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 16.8, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 43.8, - "mass": 0.67, - "unit": "kg" - } - ], - "components": [ - { - "id": 4231, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4232, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4233, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4234, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 423, - "url": "https://via.placeholder.com/400x300/091036/ffffff?text=EcoTech+Kettle+934T", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-03T09:16:11.475197Z", - "updated_at": "2025-07-31T09:16:11.475197Z" - }, - { - "id": 424, - "name": "ChefMate Toaster 365Y", - "description": "ChefMate Toaster 365Y is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.96, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 19.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1283, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 49.5, - "mass": 1.8, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 50.5, - "mass": 1.1, - "unit": "kg" - } - ], - "components": [ - { - "id": 4241, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4242, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4243, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4244, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 424, - "url": "https://via.placeholder.com/400x300/0545fb/ffffff?text=ChefMate+Toaster+365Y", - "description": "Toaster product image" - } - ], - "created_at": "2024-04-28T09:16:11.475317Z", - "updated_at": "2025-07-23T09:16:11.475317Z" - }, - { - "id": 425, - "name": "EcoTech Iron 187U", - "description": "EcoTech Iron 187U is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.35, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1755, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.2, - "mass": 0.25, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 31.3, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 19.0, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 33.5, - "mass": 0.93, - "unit": "kg" - } - ], - "components": [ - { - "id": 4251, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4252, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 4253, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 425, - "url": "https://via.placeholder.com/400x300/0bb191/ffffff?text=EcoTech+Iron+187U", - "description": "Iron product image" - } - ], - "created_at": "2024-09-11T09:16:11.475399Z", - "updated_at": "2025-07-29T09:16:11.475399Z" - }, - { - "id": 426, - "name": "ZenGear Air Purifier 987P", - "description": "ZenGear Air Purifier 987P is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 944, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 28.7, - "mass": 0.91, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 17.6, - "mass": 0.54, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 15.4, - "mass": 0.26, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 38.2, - "mass": 0.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 4261, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4262, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4263, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4264, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4265, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 426, - "url": "https://via.placeholder.com/400x300/02cd55/ffffff?text=ZenGear+Air+Purifier+987P", - "description": "Air Purifier product image" - } - ], - "created_at": "2025-02-27T09:16:11.475469Z", - "updated_at": "2025-07-12T09:16:11.475469Z" - }, - { - "id": 427, - "name": "EcoTech Rice Cooker 648O", - "description": "EcoTech Rice Cooker 648O is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1747, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 51.7, - "mass": 1.88, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 48.3, - "mass": 1.68, - "unit": "kg" - } - ], - "components": [ - { - "id": 4271, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4272, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4273, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4274, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4275, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 427, - "url": "https://via.placeholder.com/400x300/0f147d/ffffff?text=EcoTech+Rice+Cooker+648O", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-11-27T09:16:11.475521Z", - "updated_at": "2025-04-30T09:16:11.475521Z" - }, - { - "id": 428, - "name": "CleanWave Air Purifier 135N", - "description": "CleanWave Air Purifier 135N is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.68, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1657, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 19.8, - "mass": 0.69, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 50.9, - "mass": 1.34, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 29.3, - "mass": 0.59, - "unit": "kg" - } - ], - "components": [ - { - "id": 4281, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4282, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4283, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4284, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4285, - "name": "Air Purifier Component 5", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 428, - "url": "https://via.placeholder.com/400x300/01b0fc/ffffff?text=CleanWave+Air+Purifier+135N", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-12-13T09:16:11.475569Z", - "updated_at": "2025-05-03T09:16:11.475569Z" - }, - { - "id": 429, - "name": "ChefMate Fan 457U", - "description": "ChefMate Fan 457U is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.16, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1623, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 14.7, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 29.3, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 20.0, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 36.0, - "mass": 0.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 4291, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 4292, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 4293, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 4294, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 4295, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 429, - "url": "https://via.placeholder.com/400x300/08100e/ffffff?text=ChefMate+Fan+457U", - "description": "Fan product image" - } - ], - "created_at": "2025-02-25T09:16:11.475633Z", - "updated_at": "2025-06-23T09:16:11.475633Z" - }, - { - "id": 430, - "name": "NeoCook Iron 289A", - "description": "NeoCook Iron 289A is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.6, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2072, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 9.6, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 21.2, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 49.0, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 20.2, - "mass": 0.28, - "unit": "kg" - } - ], - "components": [ - { - "id": 4301, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4302, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 4303, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 430, - "url": "https://via.placeholder.com/400x300/0be98b/ffffff?text=NeoCook+Iron+289A", - "description": "Iron product image" - } - ], - "created_at": "2024-11-30T09:16:11.475715Z", - "updated_at": "2025-06-24T09:16:11.475715Z" - }, - { - "id": 431, - "name": "PureLife Kettle 460L", - "description": "PureLife Kettle 460L is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 29.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1434, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 13.4, - "mass": 0.15, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 33.9, - "mass": 1.3, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 40.2, - "mass": 1.37, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 12.6, - "mass": 0.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 4311, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4312, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 431, - "url": "https://via.placeholder.com/400x300/084271/ffffff?text=PureLife+Kettle+460L", - "description": "Kettle product image" - } - ], - "created_at": "2024-09-19T09:16:11.475778Z", - "updated_at": "2025-05-17T09:16:11.475778Z" - }, - { - "id": 432, - "name": "AquaPro Toaster 281P", - "description": "AquaPro Toaster 281P is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.73, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 870, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 34.2, - "mass": 0.55, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.9, - "mass": 0.66, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 36.8, - "mass": 0.6, - "unit": "kg" - } - ], - "components": [ - { - "id": 4321, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4322, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4323, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4324, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4325, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 432, - "url": "https://via.placeholder.com/400x300/077efd/ffffff?text=AquaPro+Toaster+281P", - "description": "Toaster product image" - } - ], - "created_at": "2024-07-12T09:16:11.475843Z", - "updated_at": "2025-05-11T09:16:11.475843Z" - }, - { - "id": 433, - "name": "SmartHome Monitor 901X", - "description": "SmartHome Monitor 901X is a high-performance monitor designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.0, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1353, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 18.8, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.8, - "mass": 0.68, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 50.5, - "mass": 1.12, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 11.9, - "mass": 0.21, - "unit": "kg" - } - ], - "components": [ - { - "id": 4331, - "name": "Monitor Component 1", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4332, - "name": "Monitor Component 2", - "description": "High-quality part for monitor functionality" - }, - { - "id": 4333, - "name": "Monitor Component 3", - "description": "High-quality part for monitor functionality" - } - ], - "images": [ - { - "id": 433, - "url": "https://via.placeholder.com/400x300/087626/ffffff?text=SmartHome+Monitor+901X", - "description": "Monitor product image" - } - ], - "created_at": "2025-03-06T09:16:11.475897Z", - "updated_at": "2025-07-30T09:16:11.475897Z" - }, - { - "id": 434, - "name": "CleanWave Humidifier 779Q", - "description": "CleanWave Humidifier 779Q is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1038, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 22.6, - "mass": 0.3, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.4, - "mass": 0.35, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 24.5, - "mass": 0.77, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 24.5, - "mass": 0.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 4341, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4342, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4343, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4344, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4345, - "name": "Humidifier Component 5", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 434, - "url": "https://via.placeholder.com/400x300/036e7d/ffffff?text=CleanWave+Humidifier+779Q", - "description": "Humidifier product image" - } - ], - "created_at": "2024-06-24T09:16:11.475966Z", - "updated_at": "2025-05-21T09:16:11.475966Z" - }, - { - "id": 435, - "name": "SmartHome Fan 532N", - "description": "SmartHome Fan 532N is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.69, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1683, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 47.6, - "mass": 0.67, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 22.2, - "mass": 0.48, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 30.2, - "mass": 0.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 4351, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 4352, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 4353, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 4354, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 435, - "url": "https://via.placeholder.com/400x300/09cd55/ffffff?text=SmartHome+Fan+532N", - "description": "Fan product image" - } - ], - "created_at": "2025-04-23T09:16:11.476018Z", - "updated_at": "2025-05-18T09:16:11.476018Z" - }, - { - "id": 436, - "name": "CleanWave Toaster 710M", - "description": "CleanWave Toaster 710M is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.54, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1330, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 16.2, - "mass": 0.53, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 41.9, - "mass": 0.51, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 41.9, - "mass": 1.39, - "unit": "kg" - } - ], - "components": [ - { - "id": 4361, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4362, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4363, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 436, - "url": "https://via.placeholder.com/400x300/02d68b/ffffff?text=CleanWave+Toaster+710M", - "description": "Toaster product image" - } - ], - "created_at": "2024-11-16T09:16:11.476074Z", - "updated_at": "2025-05-08T09:16:11.476074Z" - }, - { - "id": 437, - "name": "PureLife Iron 355C", - "description": "PureLife Iron 355C is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.1, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2087, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 34.0, - "mass": 1.28, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 26.7, - "mass": 0.73, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 39.3, - "mass": 1.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 4371, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4372, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 437, - "url": "https://via.placeholder.com/400x300/08620d/ffffff?text=PureLife+Iron+355C", - "description": "Iron product image" - } - ], - "created_at": "2025-04-11T09:16:11.476135Z", - "updated_at": "2025-05-19T09:16:11.476135Z" - }, - { - "id": 438, - "name": "EcoTech Toaster 539U", - "description": "EcoTech Toaster 539U is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.5, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.2, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1358, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 52.8, - "mass": 1.03, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 47.2, - "mass": 1.8, - "unit": "kg" - } - ], - "components": [ - { - "id": 4381, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4382, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4383, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4384, - "name": "Toaster Component 4", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4385, - "name": "Toaster Component 5", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 438, - "url": "https://via.placeholder.com/400x300/0c9b2b/ffffff?text=EcoTech+Toaster+539U", - "description": "Toaster product image" - } - ], - "created_at": "2024-04-23T09:16:11.476261Z", - "updated_at": "2025-05-24T09:16:11.476261Z" - }, - { - "id": 439, - "name": "SmartHome Rice Cooker 285G", - "description": "SmartHome Rice Cooker 285G is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.53, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 21.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 16.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2181, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 17.7, - "mass": 0.46, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 32.9, - "mass": 0.98, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 32.3, - "mass": 0.36, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 17.1, - "mass": 0.28, - "unit": "kg" - } - ], - "components": [ - { - "id": 4391, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4392, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4393, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4394, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 439, - "url": "https://via.placeholder.com/400x300/07ebf1/ffffff?text=SmartHome+Rice+Cooker+285G", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-06-21T09:16:11.476341Z", - "updated_at": "2025-06-05T09:16:11.476341Z" - }, - { - "id": 440, - "name": "ZenGear Air Purifier 922X", - "description": "ZenGear Air Purifier 922X is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.93, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2132, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 9.6, - "mass": 0.31, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.8, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 31.8, - "mass": 0.92, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.8, - "mass": 0.42, - "unit": "kg" - } - ], - "components": [ - { - "id": 4401, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4402, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 440, - "url": "https://via.placeholder.com/400x300/03ed09/ffffff?text=ZenGear+Air+Purifier+922X", - "description": "Air Purifier product image" - } - ], - "created_at": "2025-03-28T09:16:11.476411Z", - "updated_at": "2025-06-19T09:16:11.476411Z" - }, - { - "id": 441, - "name": "PureLife Fan 359O", - "description": "PureLife Fan 359O is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1357, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 43.4, - "mass": 1.18, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 46.7, - "mass": 1.85, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 9.8, - "mass": 0.22, - "unit": "kg" - } - ], - "components": [ - { - "id": 4411, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 4412, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 4413, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 441, - "url": "https://via.placeholder.com/400x300/0bf962/ffffff?text=PureLife+Fan+359O", - "description": "Fan product image" - } - ], - "created_at": "2025-02-01T09:16:11.476468Z", - "updated_at": "2025-06-18T09:16:11.476468Z" - }, - { - "id": 442, - "name": "AquaPro Air Purifier 832G", - "description": "AquaPro Air Purifier 832G is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.07, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.9, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1964, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 33.8, - "mass": 0.39, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 66.2, - "mass": 0.86, - "unit": "kg" - } - ], - "components": [ - { - "id": 4421, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4422, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4423, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4424, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 442, - "url": "https://via.placeholder.com/400x300/08f9a1/ffffff?text=AquaPro+Air+Purifier+832G", - "description": "Air Purifier product image" - } - ], - "created_at": "2024-11-17T09:16:11.476511Z", - "updated_at": "2025-05-19T09:16:11.476511Z" - }, - { - "id": 443, - "name": "CleanWave Blender 185N", - "description": "CleanWave Blender 185N is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.99, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 27.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 24.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1667, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 54.5, - "mass": 1.31, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 45.5, - "mass": 1.4, - "unit": "kg" - } - ], - "components": [ - { - "id": 4431, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4432, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4433, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 4434, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - }, - { - "id": 4435, - "name": "Blender Component 5", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 443, - "url": "https://via.placeholder.com/400x300/073b00/ffffff?text=CleanWave+Blender+185N", - "description": "Blender product image" - } - ], - "created_at": "2024-10-18T09:16:11.476570Z", - "updated_at": "2025-05-28T09:16:11.476570Z" - }, - { - "id": 444, - "name": "PureLife Coffee Maker 966X", - "description": "PureLife Coffee Maker 966X is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.75, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1017, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 33.3, - "mass": 1.15, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.3, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 31.3, - "mass": 1.02, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 10.0, - "mass": 0.25, - "unit": "kg" - } - ], - "components": [ - { - "id": 4441, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4442, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4443, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4444, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4445, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 444, - "url": "https://via.placeholder.com/400x300/06a2c2/ffffff?text=PureLife+Coffee+Maker+966X", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-04-16T09:16:11.476634Z", - "updated_at": "2025-07-16T09:16:11.476634Z" - }, - { - "id": 445, - "name": "AquaPro Kettle 745H", - "description": "AquaPro Kettle 745H is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.43, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 33.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1142, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 13.2, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 46.2, - "mass": 1.67, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 11.0, - "mass": 0.19, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 29.7, - "mass": 1.1, - "unit": "kg" - } - ], - "components": [ - { - "id": 4451, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4452, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 445, - "url": "https://via.placeholder.com/400x300/0ee055/ffffff?text=AquaPro+Kettle+745H", - "description": "Kettle product image" - } - ], - "created_at": "2024-06-15T09:16:11.476691Z", - "updated_at": "2025-07-29T09:16:11.476691Z" - }, - { - "id": 446, - "name": "ZenGear Fan 678V", - "description": "ZenGear Fan 678V is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.25, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.9, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1730, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.1, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 37.0, - "mass": 1.28, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 21.7, - "mass": 0.42, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 41.3, - "mass": 1.49, - "unit": "kg" - } - ], - "components": [ - { - "id": 4461, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 4462, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 4463, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 4464, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 4465, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 446, - "url": "https://via.placeholder.com/400x300/05d429/ffffff?text=ZenGear+Fan+678V", - "description": "Fan product image" - } - ], - "created_at": "2024-11-08T09:16:11.476753Z", - "updated_at": "2025-05-10T09:16:11.476753Z" - }, - { - "id": 447, - "name": "EcoTech Toaster 720C", - "description": "EcoTech Toaster 720C is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.97, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1057, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.9, - "mass": 0.52, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 27.2, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 23.6, - "mass": 0.38, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 18.3, - "mass": 0.26, - "unit": "kg" - } - ], - "components": [ - { - "id": 4471, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4472, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4473, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 447, - "url": "https://via.placeholder.com/400x300/0a3899/ffffff?text=EcoTech+Toaster+720C", - "description": "Toaster product image" - } - ], - "created_at": "2025-03-05T09:16:11.476805Z", - "updated_at": "2025-06-21T09:16:11.476805Z" - }, - { - "id": 448, - "name": "NeoCook Toaster 243Y", - "description": "NeoCook Toaster 243Y is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.62, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 15.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 824, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 39.2, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.0, - "mass": 0.59, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 40.8, - "mass": 1.27, - "unit": "kg" - } - ], - "components": [ - { - "id": 4481, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4482, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 448, - "url": "https://via.placeholder.com/400x300/0c37ce/ffffff?text=NeoCook+Toaster+243Y", - "description": "Toaster product image" - } - ], - "created_at": "2025-01-01T09:16:11.476854Z", - "updated_at": "2025-07-07T09:16:11.476854Z" - }, - { - "id": 449, - "name": "PureLife Blender 497A", - "description": "PureLife Blender 497A is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2199, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 69.0, - "mass": 2.25, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 31.0, - "mass": 0.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 4491, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4492, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4493, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 449, - "url": "https://via.placeholder.com/400x300/0b6726/ffffff?text=PureLife+Blender+497A", - "description": "Blender product image" - } - ], - "created_at": "2025-04-17T09:16:11.476910Z", - "updated_at": "2025-06-14T09:16:11.476910Z" - }, - { - "id": 450, - "name": "EcoTech Kettle 421L", - "description": "EcoTech Kettle 421L is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.9, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 25.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2084, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 8.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 17.2, - "mass": 0.49, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 21.7, - "mass": 0.65, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 36.9, - "mass": 1.45, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 24.2, - "mass": 0.84, - "unit": "kg" - } - ], - "components": [ - { - "id": 4501, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4502, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 450, - "url": "https://via.placeholder.com/400x300/0b449b/ffffff?text=EcoTech+Kettle+421L", - "description": "Kettle product image" - } - ], - "created_at": "2024-04-19T09:16:11.476957Z", - "updated_at": "2025-07-03T09:16:11.476957Z" - }, - { - "id": 451, - "name": "CleanWave Blender 300G", - "description": "CleanWave Blender 300G is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.51, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1628, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.0, - "mass": 0.6, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 69.0, - "mass": 2.65, - "unit": "kg" - } - ], - "components": [ - { - "id": 4511, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4512, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4513, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - }, - { - "id": 4514, - "name": "Blender Component 4", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 451, - "url": "https://via.placeholder.com/400x300/01dd26/ffffff?text=CleanWave+Blender+300G", - "description": "Blender product image" - } - ], - "created_at": "2024-08-22T09:16:11.477006Z", - "updated_at": "2025-06-23T09:16:11.477006Z" - }, - { - "id": 452, - "name": "EcoTech Coffee Maker 155V", - "description": "EcoTech Coffee Maker 155V is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.3, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1841, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.7, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 11.7, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 31.1, - "mass": 0.5, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 57.3, - "mass": 2.26, - "unit": "kg" - } - ], - "components": [ - { - "id": 4521, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4522, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4523, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4524, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 452, - "url": "https://via.placeholder.com/400x300/061a83/ffffff?text=EcoTech+Coffee+Maker+155V", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-08-10T09:16:11.477058Z", - "updated_at": "2025-05-19T09:16:11.477058Z" - }, - { - "id": 453, - "name": "NeoCook Rice Cooker 568E", - "description": "NeoCook Rice Cooker 568E is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.54, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1288, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.1, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.7, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 27.1, - "mass": 0.71, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 25.2, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 27.5, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.2, - "mass": 0.73, - "unit": "kg" - } - ], - "components": [ - { - "id": 4531, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4532, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4533, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4534, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4535, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 453, - "url": "https://via.placeholder.com/400x300/0f2752/ffffff?text=NeoCook+Rice+Cooker+568E", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-04-12T09:16:11.477115Z", - "updated_at": "2025-04-30T09:16:11.477115Z" - }, - { - "id": 454, - "name": "ZenGear Humidifier 186G", - "description": "ZenGear Humidifier 186G is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.42, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.1, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1727, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 69.5, - "mass": 1.7, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.5, - "mass": 0.77, - "unit": "kg" - } - ], - "components": [ - { - "id": 4541, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4542, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4543, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4544, - "name": "Humidifier Component 4", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 454, - "url": "https://via.placeholder.com/400x300/07f064/ffffff?text=ZenGear+Humidifier+186G", - "description": "Humidifier product image" - } - ], - "created_at": "2025-04-05T09:16:11.477159Z", - "updated_at": "2025-05-20T09:16:11.477159Z" - }, - { - "id": 455, - "name": "EcoTech Iron 315T", - "description": "EcoTech Iron 315T is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.94, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1804, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 26.0, - "mass": 0.32, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 34.7, - "mass": 0.9, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 39.3, - "mass": 1.02, - "unit": "kg" - } - ], - "components": [ - { - "id": 4551, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4552, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 4553, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 455, - "url": "https://via.placeholder.com/400x300/0e01e7/ffffff?text=EcoTech+Iron+315T", - "description": "Iron product image" - } - ], - "created_at": "2025-02-10T09:16:11.477219Z", - "updated_at": "2025-06-16T09:16:11.477219Z" - }, - { - "id": 456, - "name": "PureLife Rice Cooker 170F", - "description": "PureLife Rice Cooker 170F is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.05, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 2053, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.9, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 33.8, - "mass": 0.57, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 16.2, - "mass": 0.19, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 50.0, - "mass": 1.01, - "unit": "kg" - } - ], - "components": [ - { - "id": 4561, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4562, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4563, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4564, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4565, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 456, - "url": "https://via.placeholder.com/400x300/0f326d/ffffff?text=PureLife+Rice+Cooker+170F", - "description": "Rice Cooker product image" - } - ], - "created_at": "2025-03-09T09:16:11.477266Z", - "updated_at": "2025-05-13T09:16:11.477266Z" - }, - { - "id": 457, - "name": "ZenGear Blender 780C", - "description": "ZenGear Blender 780C is a high-performance blender designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.14, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1230, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 71.2, - "mass": 1.89, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 28.8, - "mass": 0.33, - "unit": "kg" - } - ], - "components": [ - { - "id": 4571, - "name": "Blender Component 1", - "description": "High-quality part for blender functionality" - }, - { - "id": 4572, - "name": "Blender Component 2", - "description": "High-quality part for blender functionality" - }, - { - "id": 4573, - "name": "Blender Component 3", - "description": "High-quality part for blender functionality" - } - ], - "images": [ - { - "id": 457, - "url": "https://via.placeholder.com/400x300/070d39/ffffff?text=ZenGear+Blender+780C", - "description": "Blender product image" - } - ], - "created_at": "2024-09-13T09:16:11.477320Z", - "updated_at": "2025-04-29T09:16:11.477320Z" - }, - { - "id": 458, - "name": "EcoTech Kettle 891Z", - "description": "EcoTech Kettle 891Z is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.46, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1452, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.3, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 10.8, - "mass": 0.23, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 14.4, - "mass": 0.19, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 32.4, - "mass": 0.37, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 42.3, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 4581, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4582, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 458, - "url": "https://via.placeholder.com/400x300/029982/ffffff?text=EcoTech+Kettle+891Z", - "description": "Kettle product image" - } - ], - "created_at": "2025-03-13T09:16:11.477371Z", - "updated_at": "2025-07-20T09:16:11.477371Z" - }, - { - "id": 459, - "name": "CleanWave Coffee Maker 655L", - "description": "CleanWave Coffee Maker 655L is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.85, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 20.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1232, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.2, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 26.4, - "mass": 0.74, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 50.9, - "mass": 1.5, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 22.6, - "mass": 0.44, - "unit": "kg" - } - ], - "components": [ - { - "id": 4591, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4592, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 459, - "url": "https://via.placeholder.com/400x300/05470f/ffffff?text=CleanWave+Coffee+Maker+655L", - "description": "Coffee Maker product image" - } - ], - "created_at": "2025-03-18T09:16:11.477427Z", - "updated_at": "2025-05-30T09:16:11.477427Z" - }, - { - "id": 460, - "name": "NeoCook Iron 823D", - "description": "NeoCook Iron 823D is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 5, - "name": "Office Equipment" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.98, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.3, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1364, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 40.8, - "mass": 0.76, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 41.5, - "mass": 1.54, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 7.7, - "mass": 0.19, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 9.9, - "mass": 0.3, - "unit": "kg" - } - ], - "components": [ - { - "id": 4601, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4602, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 4603, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - }, - { - "id": 4604, - "name": "Iron Component 4", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 460, - "url": "https://via.placeholder.com/400x300/0b962a/ffffff?text=NeoCook+Iron+823D", - "description": "Iron product image" - } - ], - "created_at": "2024-09-19T09:16:11.477485Z", - "updated_at": "2025-07-04T09:16:11.477485Z" - }, - { - "id": 461, - "name": "SmartHome Air Purifier 791E", - "description": "SmartHome Air Purifier 791E is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.4, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 23.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 17.8, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1403, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.5, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 23.3, - "mass": 0.88, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 76.7, - "mass": 1.54, - "unit": "kg" - } - ], - "components": [ - { - "id": 4611, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4612, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4613, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 461, - "url": "https://via.placeholder.com/400x300/03ea2a/ffffff?text=SmartHome+Air+Purifier+791E", - "description": "Air Purifier product image" - } - ], - "created_at": "2025-01-14T09:16:11.477537Z", - "updated_at": "2025-07-26T09:16:11.477537Z" - }, - { - "id": 462, - "name": "AquaPro Fan 807U", - "description": "AquaPro Fan 807U is a high-performance fan designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.57, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 23.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1872, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.0, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 28.1, - "mass": 1.11, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 25.0, - "mass": 0.75, - "unit": "kg" - }, - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 31.2, - "mass": 1.24, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 15.6, - "mass": 0.45, - "unit": "kg" - } - ], - "components": [ - { - "id": 4621, - "name": "Fan Component 1", - "description": "High-quality part for fan functionality" - }, - { - "id": 4622, - "name": "Fan Component 2", - "description": "High-quality part for fan functionality" - }, - { - "id": 4623, - "name": "Fan Component 3", - "description": "High-quality part for fan functionality" - }, - { - "id": 4624, - "name": "Fan Component 4", - "description": "High-quality part for fan functionality" - }, - { - "id": 4625, - "name": "Fan Component 5", - "description": "High-quality part for fan functionality" - } - ], - "images": [ - { - "id": 462, - "url": "https://via.placeholder.com/400x300/063653/ffffff?text=AquaPro+Fan+807U", - "description": "Fan product image" - } - ], - "created_at": "2024-12-13T09:16:11.477595Z", - "updated_at": "2025-04-27T09:16:11.477595Z" - }, - { - "id": 463, - "name": "ChefMate Air Purifier 355V", - "description": "ChefMate Air Purifier 355V is a high-performance air purifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.02, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 22.6, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.3, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 915, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 6.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 41.2, - "mass": 1.03, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 33.8, - "mass": 0.7, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 25.0, - "mass": 0.65, - "unit": "kg" - } - ], - "components": [ - { - "id": 4631, - "name": "Air Purifier Component 1", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4632, - "name": "Air Purifier Component 2", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4633, - "name": "Air Purifier Component 3", - "description": "High-quality part for air purifier functionality" - }, - { - "id": 4634, - "name": "Air Purifier Component 4", - "description": "High-quality part for air purifier functionality" - } - ], - "images": [ - { - "id": 463, - "url": "https://via.placeholder.com/400x300/05b042/ffffff?text=ChefMate+Air+Purifier+355V", - "description": "Air Purifier product image" - } - ], - "created_at": "2025-02-27T09:16:11.477641Z", - "updated_at": "2025-05-30T09:16:11.477641Z" - }, - { - "id": 464, - "name": "ChefMate Iron 529H", - "description": "ChefMate Iron 529H is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 1, - "name": "Kitchen Appliance" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.67, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 29.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 27.1, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1102, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.8, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.4, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.2, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 53.1, - "mass": 1.48, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 46.9, - "mass": 1.68, - "unit": "kg" - } - ], - "components": [ - { - "id": 4641, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4642, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - }, - { - "id": 4643, - "name": "Iron Component 3", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 464, - "url": "https://via.placeholder.com/400x300/02967a/ffffff?text=ChefMate+Iron+529H", - "description": "Iron product image" - } - ], - "created_at": "2024-07-21T09:16:11.477693Z", - "updated_at": "2025-07-07T09:16:11.477693Z" - }, - { - "id": 465, - "name": "CleanWave Rice Cooker 115N", - "description": "CleanWave Rice Cooker 115N is a high-performance rice cooker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.64, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.8, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 26.7, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1949, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.3, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.6, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.9, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 75.0, - "mass": 2.68, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 25.0, - "mass": 0.91, - "unit": "kg" - } - ], - "components": [ - { - "id": 4651, - "name": "Rice Cooker Component 1", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4652, - "name": "Rice Cooker Component 2", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4653, - "name": "Rice Cooker Component 3", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4654, - "name": "Rice Cooker Component 4", - "description": "High-quality part for rice cooker functionality" - }, - { - "id": 4655, - "name": "Rice Cooker Component 5", - "description": "High-quality part for rice cooker functionality" - } - ], - "images": [ - { - "id": 465, - "url": "https://via.placeholder.com/400x300/0d635d/ffffff?text=CleanWave+Rice+Cooker+115N", - "description": "Rice Cooker product image" - } - ], - "created_at": "2024-05-12T09:16:11.477737Z", - "updated_at": "2025-07-09T09:16:11.477737Z" - }, - { - "id": 466, - "name": "PureLife Coffee Maker 908L", - "description": "PureLife Coffee Maker 908L is a high-performance coffee maker designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.18, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 25.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 20.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1209, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.4, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 33.8, - "mass": 0.82, - "unit": "kg" - }, - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 66.2, - "mass": 1.98, - "unit": "kg" - } - ], - "components": [ - { - "id": 4661, - "name": "Coffee Maker Component 1", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4662, - "name": "Coffee Maker Component 2", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4663, - "name": "Coffee Maker Component 3", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4664, - "name": "Coffee Maker Component 4", - "description": "High-quality part for coffee maker functionality" - }, - { - "id": 4665, - "name": "Coffee Maker Component 5", - "description": "High-quality part for coffee maker functionality" - } - ], - "images": [ - { - "id": 466, - "url": "https://via.placeholder.com/400x300/051590/ffffff?text=PureLife+Coffee+Maker+908L", - "description": "Coffee Maker product image" - } - ], - "created_at": "2024-03-21T09:16:11.477795Z", - "updated_at": "2025-07-02T09:16:11.477795Z" - }, - { - "id": 467, - "name": "CleanWave Humidifier 336J", - "description": "CleanWave Humidifier 336J is a high-performance humidifier designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 2, - "name": "Home Electronics" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 1.65, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 34.7, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 28.6, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 833, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 7.6, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 5.9, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 8.6, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 25.5, - "mass": 0.79, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 38.2, - "mass": 0.93, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 36.4, - "mass": 0.81, - "unit": "kg" - } - ], - "components": [ - { - "id": 4671, - "name": "Humidifier Component 1", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4672, - "name": "Humidifier Component 2", - "description": "High-quality part for humidifier functionality" - }, - { - "id": 4673, - "name": "Humidifier Component 3", - "description": "High-quality part for humidifier functionality" - } - ], - "images": [ - { - "id": 467, - "url": "https://via.placeholder.com/400x300/02e324/ffffff?text=CleanWave+Humidifier+336J", - "description": "Humidifier product image" - } - ], - "created_at": "2024-10-29T09:16:11.477849Z", - "updated_at": "2025-07-14T09:16:11.477849Z" - }, - { - "id": 468, - "name": "ZenGear Kettle 160L", - "description": "ZenGear Kettle 160L is a high-performance kettle designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 3, - "name": "Personal Care Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 3.26, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 26.2, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 21.4, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1922, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 9.5, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 8.3, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 6.1, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 1, - "name": "Stainless Steel", - "type": "Metal" - }, - "percentage": 20.4, - "mass": 0.56, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 20.9, - "mass": 0.61, - "unit": "kg" - }, - { - "material": { - "id": 3, - "name": "Copper Wire", - "type": "Metal" - }, - "percentage": 30.6, - "mass": 0.95, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 28.1, - "mass": 0.99, - "unit": "kg" - } - ], - "components": [ - { - "id": 4681, - "name": "Kettle Component 1", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4682, - "name": "Kettle Component 2", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4683, - "name": "Kettle Component 3", - "description": "High-quality part for kettle functionality" - }, - { - "id": 4684, - "name": "Kettle Component 4", - "description": "High-quality part for kettle functionality" - } - ], - "images": [ - { - "id": 468, - "url": "https://via.placeholder.com/400x300/0be076/ffffff?text=ZenGear+Kettle+160L", - "description": "Kettle product image" - } - ], - "created_at": "2024-09-09T09:16:11.477905Z", - "updated_at": "2025-05-21T09:16:11.477905Z" - }, - { - "id": 469, - "name": "CleanWave Iron 879A", - "description": "CleanWave Iron 879A is a high-performance iron designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.39, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 31.0, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 22.0, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1804, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.2, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 7.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 9.5, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 16.0, - "mass": 0.27, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 31.2, - "mass": 0.86, - "unit": "kg" - }, - { - "material": { - "id": 5, - "name": "Borosilicate Glass", - "type": "Glass" - }, - "percentage": 12.5, - "mass": 0.29, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 40.3, - "mass": 0.64, - "unit": "kg" - } - ], - "components": [ - { - "id": 4691, - "name": "Iron Component 1", - "description": "High-quality part for iron functionality" - }, - { - "id": 4692, - "name": "Iron Component 2", - "description": "High-quality part for iron functionality" - } - ], - "images": [ - { - "id": 469, - "url": "https://via.placeholder.com/400x300/0695b3/ffffff?text=CleanWave+Iron+879A", - "description": "Iron product image" - } - ], - "created_at": "2025-02-03T09:16:11.477957Z", - "updated_at": "2025-07-07T09:16:11.477957Z" - }, - { - "id": 470, - "name": "EcoTech Toaster 210H", - "description": "EcoTech Toaster 210H is a high-performance toaster designed for modern households, featuring advanced controls and energy-efficient components.", - "product_type": { - "id": 4, - "name": "Smart Home Device" - }, - "physical_properties": [ - { - "property_name": "Weight", - "value": 2.84, - "unit": "kg" - }, - { - "property_name": "Height", - "value": 32.5, - "unit": "cm" - }, - { - "property_name": "Width", - "value": 18.5, - "unit": "cm" - }, - { - "property_name": "Power", - "value": 1042, - "unit": "W" - } - ], - "circularity_properties": [ - { - "property_name": "Recyclability Score", - "value": 6.7, - "unit": "/10" - }, - { - "property_name": "Repairability Score", - "value": 9.0, - "unit": "/10" - }, - { - "property_name": "Energy Efficiency", - "value": 7.8, - "unit": "/10" - } - ], - "bill_of_materials": [ - { - "material": { - "id": 7, - "name": "Silicone", - "type": "Elastomer" - }, - "percentage": 34.7, - "mass": 1.34, - "unit": "kg" - }, - { - "material": { - "id": 6, - "name": "Polypropylene", - "type": "Plastic" - }, - "percentage": 27.1, - "mass": 0.89, - "unit": "kg" - }, - { - "material": { - "id": 4, - "name": "Mica", - "type": "Mineral" - }, - "percentage": 24.7, - "mass": 0.4, - "unit": "kg" - }, - { - "material": { - "id": 2, - "name": "ABS Plastic", - "type": "Plastic" - }, - "percentage": 13.5, - "mass": 0.41, - "unit": "kg" - } - ], - "components": [ - { - "id": 4701, - "name": "Toaster Component 1", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4702, - "name": "Toaster Component 2", - "description": "High-quality part for toaster functionality" - }, - { - "id": 4703, - "name": "Toaster Component 3", - "description": "High-quality part for toaster functionality" - } - ], - "images": [ - { - "id": 470, - "url": "https://via.placeholder.com/400x300/01f0e0/ffffff?text=EcoTech+Toaster+210H", - "description": "Toaster product image" - } - ], - "created_at": "2025-04-08T09:16:11.478016Z", - "updated_at": "2025-04-28T09:16:11.478016Z" - }, - ] -} diff --git a/frontend-app/src/assets/data/products.json b/frontend-app/src/assets/data/products.json deleted file mode 100644 index 9fcc26d..0000000 --- a/frontend-app/src/assets/data/products.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "1": - { - "created_at": "2025-06-30T11:02:56.661198Z", - "updated_at": "2025-06-30T11:02:56.661198Z", - "name": "iPhone 12", - "description": "Apple smartphone.", - "brand": "Apple", - "model": "A2403", - "id": 1, - "physical_properties": { - "created_at": "2025-06-30T11:02:56Z", - "updated_at": "2025-06-30T11:02:56Z", - "weight_kg": 0.164, - "height_cm": 14.7, - "width_cm": 7.15, - "depth_cm": 0.74, - "id": 1, - "volume_cm3": 77.777 - }, - "product_type": { - "id": 1, - "name": "32250000-0", - "description": "Mobile telephones" - }, - "images": [{ - "id": 1, - "image_url": "https://reebelo.com/_next/image?url=https%3A%2F%2Fcdn.reebelo.com%2Fpim%2Fproducts%2FP-IPHONE12MINI%2FBLA-image-0.jpg&w=256&q=75", - "description": "iPhone 12 Image" - }], - "videos": [], - "files": [], - "bill_of_materials": [], - "components": [] - }, - "2": - { - "created_at": "2025-06-30T11:02:56.693608Z", - "updated_at": "2025-06-30T11:02:56.693608Z", - "name": "Dell XPS 13", - "description": "Dell laptop.", - "brand": "Dell", - "model": "XPS9380", - "dismantling_notes": null, - "dismantling_time_start": "2025-06-30T11:02:56Z", - "dismantling_time_end": null, - "id": 2, - "physical_properties": { - "created_at": "2025-06-30T11:02:56Z", - "updated_at": "2025-06-30T11:02:56Z", - "weight_kg": 0.164, - "height_cm": 14.7, - "width_cm": 7.15, - "depth_cm": 0.74, - "id": 1, - "volume_cm3": 77.777 - }, - "product_type": { - "id": 1, - "name": "32250000-0", - "description": "Mobile telephones" - }, - "images": [], - "videos": [], - "files": [], - "bill_of_materials": [], - "components": [ - { - "id": 3, - "name": "Screen Assembly", - "description": "LED 4k Touch screen assembly" - } - ] - }, - "3": - { - "created_at": "2025-06-30T11:02:56.693608Z", - "updated_at": "2025-06-30T11:02:56.693608Z", - "name": "Screen Assembly", - "description": "LED 4k Touch screen assembly", - "brand": "Dell", - "model": "bb-asd4-123234", - "dismantling_notes": null, - "dismantling_time_start": "2025-06-30T11:02:56Z", - "dismantling_time_end": null, - "id": 3, - "product_type_id": 2, - "physical_properties": { - "created_at": "2025-06-30T11:02:56Z", - "updated_at": "2025-06-30T11:02:56Z", - "weight_kg": 0.164, - "height_cm": 14.7, - "width_cm": 7.15, - "depth_cm": 0.74, - "id": 1, - "volume_cm3": 77.777 - }, - "product_type": { - "id": 1, - "name": "32250000-0", - "description": "Mobile telephones" - }, - "images": [], - "videos": [], - "files": [], - "bill_of_materials": [], - "components": [] - } -} diff --git a/frontend-app/src/components/product/ProductCircularityProperties.tsx b/frontend-app/src/components/product/ProductCircularityProperties.tsx index acfe890..3166d81 100644 --- a/frontend-app/src/components/product/ProductCircularityProperties.tsx +++ b/frontend-app/src/components/product/ProductCircularityProperties.tsx @@ -1,10 +1,10 @@ -import { MaterialCommunityIcons } from '@expo/vector-icons'; -import { Fragment, useState } from 'react'; -import { View, StyleSheet, Pressable, useColorScheme } from 'react-native'; -import { Chip, InfoTooltip, Text, TextInput } from '@/components/base'; -import { CircularityProperties, Product } from '@/types/Product'; import DarkTheme from '@/assets/themes/dark'; import LightTheme from '@/assets/themes/light'; +import { Chip, InfoTooltip, Text, TextInput } from '@/components/base'; +import { CircularityProperties, Product } from '@/types/Product'; +import { MaterialCommunityIcons } from '@expo/vector-icons'; +import { Fragment, useState } from 'react'; +import { Pressable, StyleSheet, useColorScheme, View } from 'react-native'; interface Props { product: Product; @@ -31,11 +31,7 @@ interface CircularityPropertySectionProps { onUpdateField: (field: 'comment' | 'observation' | 'reference', value: string) => void; } -export default function ProductCircularityProperties({ - product, - editMode, - onChangeCircularityProperties, -}: Props) { +export default function ProductCircularityProperties({ product, editMode, onChangeCircularityProperties }: Props) { const darkMode = useColorScheme() === 'dark'; const theme = darkMode ? DarkTheme : LightTheme; const [expandedProperty, setExpandedProperty] = useState(null); @@ -66,12 +62,17 @@ export default function ProductCircularityProperties({ (typeof reference === 'string' && reference.trim() !== '') || // Also consider it as "having data" if comment or reference are empty strings (not null) // This means the property was added but not yet filled in - (comment !== null || reference !== null) + comment !== null || + reference !== null ); }; // Helper function to update a specific field - const updateField = (type: CircularityPropertyType, field: 'comment' | 'observation' | 'reference', value: string) => { + const updateField = ( + type: CircularityPropertyType, + field: 'comment' | 'observation' | 'reference', + value: string, + ) => { if (!product.circularityProperties) return; const key = `${type}${field.charAt(0).toUpperCase()}${field.slice(1)}` as keyof CircularityProperties; @@ -121,8 +122,6 @@ export default function ProductCircularityProperties({ setExpandedProperty(null); }; - - const propertyTypes = ['recyclability', 'remanufacturability', 'repairability'] as CircularityPropertyType[]; const chipsToShow = editMode ? propertyTypes.filter((type) => !hasPropertyData(type)) : []; const expandedPropertiesToShow = propertyTypes.filter((type) => hasPropertyData(type)); @@ -157,134 +156,135 @@ export default function ProductCircularityProperties({ )} {/* Render expanded properties */} - {expandedPropertiesToShow.map((type) => ( - product.circularityProperties && ( - setExpandedProperty(expandedProperty === type ? null : type)} - onRemove={() => removeProperty(type)} - onUpdateField={(field, value) => updateField(type, field, value)} - /> - ) - ))} + {expandedPropertiesToShow.map( + (type) => + product.circularityProperties && ( + setExpandedProperty(expandedProperty === type ? null : type)} + onRemove={() => removeProperty(type)} + onUpdateField={(field, value) => updateField(type, field, value)} + /> + ), + )} ); } function CircularityPropertySection({ - type, - circularityProperties, - editMode, - isExpanded, - onToggleExpanded, - onRemove, - onUpdateField, - }: CircularityPropertySectionProps) { - const darkMode = useColorScheme() === 'dark'; - const theme = darkMode ? DarkTheme : LightTheme; - - const commentKey = `${type}Comment` as keyof CircularityProperties; - const observationKey = `${type}Observation` as keyof CircularityProperties; - const referenceKey = `${type}Reference` as keyof CircularityProperties; - - // Helper to check if a field has content - const hasContent = (value: string | null | undefined): boolean => { - return typeof value === 'string' && value.trim() !== ''; - }; + type, + circularityProperties, + editMode, + isExpanded, + onToggleExpanded, + onRemove, + onUpdateField, +}: CircularityPropertySectionProps) { + const darkMode = useColorScheme() === 'dark'; + const theme = darkMode ? DarkTheme : LightTheme; - const observation = circularityProperties[observationKey]; - const comment = circularityProperties[commentKey]; - const reference = circularityProperties[referenceKey]; + const commentKey = `${type}Comment` as keyof CircularityProperties; + const observationKey = `${type}Observation` as keyof CircularityProperties; + const referenceKey = `${type}Reference` as keyof CircularityProperties; - return ( - - - - - {propertyLabels[type]} - - [styles.iconButton, pressed && styles.iconButtonPressed]} - > - - - {editMode && ( - [styles.iconButton, pressed && styles.iconButtonPressed]} - > - - - )} - - + // Helper to check if a field has content + const hasContent = (value: string | null | undefined): boolean => { + return typeof value === 'string' && value.trim() !== ''; + }; - {isExpanded && ( - - {/* Observation field - always show in edit mode, only show if has content in view mode */} - {(editMode || hasContent(observation)) && ( - - Observation (Required) - onUpdateField('observation', text)} - multiline - numberOfLines={3} - editable={editMode} - style={[ - styles.input, - styles.multilineInput, - darkMode && styles.inputDark, - editMode && !observation && styles.inputError, - editMode && !observation && darkMode && styles.inputErrorDark, - ]} - errorOnEmpty={editMode && !observation} - /> - - )} + const observation = circularityProperties[observationKey]; + const comment = circularityProperties[commentKey]; + const reference = circularityProperties[referenceKey]; - {/* Comment field - always show in edit mode, only show if has content in view mode */} - {(editMode || hasContent(comment)) && ( - - Comment (Optional) - onUpdateField('comment', text)} - multiline - numberOfLines={2} - editable={editMode} - style={[styles.input, styles.multilineInput, darkMode && styles.inputDark]} - /> - - )} + return ( + + + + + {propertyLabels[type]} + + [styles.iconButton, pressed && styles.iconButtonPressed]} + > + + + {editMode && ( + [styles.iconButton, pressed && styles.iconButtonPressed]} + > + + + )} + + - {/* Reference field - always show in edit mode, only show if has content in view mode */} - {(editMode || hasContent(reference)) && ( - - Reference (Optional) - onUpdateField('reference', text)} - editable={editMode} - style={[styles.input, darkMode && styles.inputDark]} - placeholder="e.g., ISO 14021:2016" - /> - - )} - - )} - - - ); + {isExpanded && ( + + {/* Observation field - always show in edit mode, only show if has content in view mode */} + {(editMode || hasContent(observation)) && ( + + Observation (Required) + onUpdateField('observation', text)} + multiline + numberOfLines={3} + editable={editMode} + style={[ + styles.input, + styles.multilineInput, + darkMode && styles.inputDark, + editMode && !observation && styles.inputError, + editMode && !observation && darkMode && styles.inputErrorDark, + ]} + errorOnEmpty={editMode && !observation} + /> + + )} + + {/* Comment field - always show in edit mode, only show if has content in view mode */} + {(editMode || hasContent(comment)) && ( + + Comment (Optional) + onUpdateField('comment', text)} + multiline + numberOfLines={2} + editable={editMode} + style={[styles.input, styles.multilineInput, darkMode && styles.inputDark]} + /> + + )} + + {/* Reference field - always show in edit mode, only show if has content in view mode */} + {(editMode || hasContent(reference)) && ( + + Reference (Optional) + onUpdateField('reference', text)} + editable={editMode} + style={[styles.input, darkMode && styles.inputDark]} + placeholder="e.g., ISO 14021:2016" + /> + + )} + + )} + + + ); } const styles = StyleSheet.create({ @@ -372,4 +372,3 @@ const styles = StyleSheet.create({ textAlignVertical: 'top', }, }); - diff --git a/frontend-app/src/components/product/ProductComponents.tsx b/frontend-app/src/components/product/ProductComponents.tsx index 6261606..daf9286 100644 --- a/frontend-app/src/components/product/ProductComponents.tsx +++ b/frontend-app/src/components/product/ProductComponents.tsx @@ -7,7 +7,7 @@ import { InfoTooltip, Text } from '@/components/base'; import { useDialog } from '@/components/common/DialogProvider'; import ProductCard from '@/components/common/ProductCard'; import { productComponents } from '@/services/api/fetching'; -import { isValidProductName } from '@/services/api/validation/product'; +import { getProductNameHelperText, validateProductName } from '@/services/api/validation/product'; import { Product } from '@/types/Product'; interface Props { @@ -33,14 +33,25 @@ export default function ProductComponents({ product, editMode }: Props) { dialog.input({ title: 'Create New Component', placeholder: 'Component Name', - helperText: 'Enter a descriptive name between 2 and 100 characters', + helperText: getProductNameHelperText(), buttons: [ { text: 'Cancel' }, { text: 'OK', - disabled: (value) => !isValidProductName(value), + disabled: (value) => { + const result = validateProductName(value); + return !result.isValid; + }, onPress: (componentName) => { const name = typeof componentName === 'string' ? componentName.trim() : ''; + const result = validateProductName(name); + + if (!result.isValid) { + // This shouldn't happen due to disabled check, but handle defensively + alert(result.error); + return; + } + const params = { id: 'new', name, diff --git a/frontend-app/src/components/product/ProductPhysicalProperties.tsx b/frontend-app/src/components/product/ProductPhysicalProperties.tsx index 9b34019..cb855ce 100644 --- a/frontend-app/src/components/product/ProductPhysicalProperties.tsx +++ b/frontend-app/src/components/product/ProductPhysicalProperties.tsx @@ -13,7 +13,7 @@ interface Props { } const unitMap = { - weight: 'kg', + weight: 'g', height: 'cm', width: 'cm', depth: 'cm', diff --git a/frontend-app/src/components/product/ProductVideo.tsx b/frontend-app/src/components/product/ProductVideo.tsx index 3c378a1..0405437 100644 --- a/frontend-app/src/components/product/ProductVideo.tsx +++ b/frontend-app/src/components/product/ProductVideo.tsx @@ -1,141 +1,153 @@ -import { useState } from 'react'; -import { View, Text, TouchableOpacity, Linking } from 'react-native'; -import { MaterialCommunityIcons } from '@expo/vector-icons'; import { InfoTooltip, TextInput } from '@/components/base'; import { useDialog } from '@/components/common/DialogProvider'; -import { Product } from '@/types/Product'; import { isValidUrl } from '@/services/api/validation/product'; +import { Product } from '@/types/Product'; +import { MaterialCommunityIcons } from '@expo/vector-icons'; +import { useState } from 'react'; +import { Linking, Text, TouchableOpacity, View } from 'react-native'; interface Video { - id?: number; - url: string; - title: string; - description: string; + id?: number; + url: string; + title: string; + description: string; } interface Props { - product: Product; - editMode: boolean; - onVideoChange?: (videos: Video[]) => void; + product: Product; + editMode: boolean; + onVideoChange?: (videos: Video[]) => void; } export default function ProductVideo({ product, editMode, onVideoChange }: Props) { - const [videos, setVideos] = useState(product.videos || []); - const dialog = useDialog(); + const [videos, setVideos] = useState(product.videos || []); + const dialog = useDialog(); - const handleVideoChange = (idx: number, field: 'url' | 'title' | 'description', value: string) => { - const updated = videos.map((v, i) => i === idx ? { ...v, [field]: value } : v); - setVideos(updated); - onVideoChange?.(updated); - }; + const handleVideoChange = (idx: number, field: 'url' | 'title' | 'description', value: string) => { + const updated = videos.map((v, i) => (i === idx ? { ...v, [field]: value } : v)); + setVideos(updated); + onVideoChange?.(updated); + }; - const handleRemove = (idx: number) => { - const updated = videos.filter((_, i) => i !== idx); - setVideos(updated); - onVideoChange?.(updated); - }; + const handleRemove = (idx: number) => { + const updated = videos.filter((_, i) => i !== idx); + setVideos(updated); + onVideoChange?.(updated); + }; - const handleAdd = () => { - dialog.input({ - title: 'Add Recording', - placeholder: 'Video URL', - helperText: 'Paste a video URL (YouTube)', - buttons: [ - { text: 'Cancel' }, - { - text: 'Add', - disabled: (value) => !value || !value.trim() || !isValidUrl(value), - onPress: (url) => { - if (!url || !isValidUrl(url)) return; - const updated = [...videos, { url: url.trim(), title: '', description: '' }]; - setVideos(updated); - onVideoChange?.(updated); - } - } - ] - }); - }; + const handleAdd = () => { + dialog.input({ + title: 'Add Recording', + placeholder: 'Video URL', + helperText: 'Paste a video URL (YouTube)', + buttons: [ + { text: 'Cancel' }, + { + text: 'Add', + disabled: (value) => !value || !value.trim() || !isValidUrl(value), + onPress: (url) => { + if (!url || !isValidUrl(url)) return; + const updated = [...videos, { url: url.trim(), title: '', description: '' }]; + setVideos(updated); + onVideoChange?.(updated); + }, + }, + ], + }); + }; - return ( - - + return ( + + + + Recordings + + {editMode && ( + + Add recording + + )} + + + {videos.length === 0 && ( + + This product has no associated recordings. + + )} + + {videos.map((video, idx) => ( + + + handleVideoChange(idx, 'title', val)} + editable={editMode} + errorOnEmpty + /> + {editMode ? ( + handleVideoChange(idx, 'url', val)} + errorOnEmpty + customValidation={isValidUrl} + editable={editMode} + /> + ) : ( + Linking.openURL(video.url)}> - Recordings + {video.url} - {editMode && ( - - Add recording - - )} - - - {videos.length === 0 && ( - This product has no associated recordings. + )} - - {videos.map((video, idx) => ( - - - handleVideoChange(idx, 'title', val)} - editable={editMode} - errorOnEmpty - /> - {editMode ? ( - handleVideoChange(idx, 'url', val)} - errorOnEmpty - customValidation={isValidUrl} - editable={editMode} - /> - ) : ( - Linking.openURL(video.url)}> - - {video.url} - - - )} - {(editMode || Boolean(video.description)) && handleVideoChange(idx, 'description', val)} - editable={editMode} - />} - - {editMode && ( - handleRemove(idx)} - style={{ - padding: 14, - justifyContent: 'center', - alignItems: 'center' - }} - > - - - )} - - ))} + {(editMode || Boolean(video.description)) && ( + handleVideoChange(idx, 'description', val)} + editable={editMode} + /> + )} + + {editMode && ( + handleRemove(idx)} + style={{ + padding: 14, + justifyContent: 'center', + alignItems: 'center', + }} + > + + + )} - ); + ))} + + ); } diff --git a/frontend-app/src/services/api/authentication.ts b/frontend-app/src/services/api/authentication.ts index e7973ff..f720cc3 100644 --- a/frontend-app/src/services/api/authentication.ts +++ b/frontend-app/src/services/api/authentication.ts @@ -1,12 +1,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { User } from '@/types/User'; -const baseUrl = `${process.env.EXPO_PUBLIC_API_URL}`; +const apiURL = `${process.env.EXPO_PUBLIC_API_URL}`; let token: string | undefined; let user: User | undefined; export async function login(username: string, password: string): Promise { - const url = new URL(baseUrl + '/auth/bearer/login'); + const url = new URL(apiURL + '/auth/bearer/login'); const headers = { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' }; const body = new URLSearchParams({ username, password }).toString(); @@ -74,7 +74,7 @@ export async function getUser(): Promise { return user; } - const url = new URL(baseUrl + '/users/me'); + const url = new URL(apiURL + '/users/me'); const authToken = await getToken(); if (!authToken) { return undefined; @@ -112,8 +112,12 @@ export async function getUser(): Promise { } } -export async function register(username: string, email: string, password: string): Promise { - const url = new URL(baseUrl + '/auth/register'); +export async function register( + username: string, + email: string, + password: string, +): Promise<{ success: boolean; error?: string }> { + const url = new URL(apiURL + '/auth/register'); const headers = { 'Content-Type': 'application/json', Accept: 'application/json' }; const body = { @@ -122,12 +126,25 @@ export async function register(username: string, email: string, password: string password: password, }; - const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body) }); - return response.ok; + try { + const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body) }); + + if (response.ok) { + return { success: true }; + } + + const errorData = await response.json(); + const errorMessage = errorData.detail?.reason || errorData.detail || 'Registration failed. Please try again.'; + + return { success: false, error: errorMessage }; + } catch (error) { + console.error('Registration error:', error); + return { success: false, error: 'Network error. Please check your connection and try again.' }; + } } export async function verify(email: string): Promise { - const url = new URL(baseUrl + '/auth/request-verify-token'); + const url = new URL(apiURL + '/auth/request-verify-token'); const headers = { 'Content-Type': 'application/json', Accept: 'application/json' }; const body = { diff --git a/frontend-app/src/services/api/fetching.ts b/frontend-app/src/services/api/fetching.ts index a46662b..ba932b0 100644 --- a/frontend-app/src/services/api/fetching.ts +++ b/frontend-app/src/services/api/fetching.ts @@ -15,7 +15,7 @@ type ProductData = { created_at: string; updated_at: string; product_type_id: number; - physical_properties: { weight_kg: number; height_cm: number; width_cm: number; depth_cm: number }; + physical_properties: { weight_g: number; height_cm: number; width_cm: number; depth_cm: number }; circularity_properties: { recyclability_comment: string | null; recyclability_observation: string; @@ -63,7 +63,7 @@ async function toProduct(data: ProductData): Promise { ownedBy: data.owner_id === meId ? 'me' : data.owner_id, amountInParent: data.amount_in_parent ?? undefined, physicalProperties: { - weight: data.physical_properties.weight_kg, + weight: data.physical_properties.weight_g, height: data.physical_properties.height_cm, width: data.physical_properties.width_cm, depth: data.physical_properties.depth_cm, @@ -203,8 +203,7 @@ export async function myProducts( const data = await response.json(); - // TODO: Update to data.items when adding pagination to /users/me/products endpoint - const product_data = data as ProductData[]; + const product_data = data.items as ProductData[]; return Promise.all(product_data.map((data) => toProduct(data))); } diff --git a/frontend-app/src/services/api/saving.ts b/frontend-app/src/services/api/saving.ts index 8c9936e..364bd56 100644 --- a/frontend-app/src/services/api/saving.ts +++ b/frontend-app/src/services/api/saving.ts @@ -15,10 +15,11 @@ function toNewProduct(product: Product): any { brand: product.brand, model: product.model, description: product.description, + // TODO: Handle bill of materials properly bill_of_materials: [ { quantity: 42, - unit: 'kg', + unit: 'g', material_id: 1, }, ], @@ -47,7 +48,7 @@ function toUpdateProduct(product: Product): any { function toUpdatePhysicalProperties(product: Product): any { return { - weight_kg: product.physicalProperties.weight || null, + weight_g: product.physicalProperties.weight || null, height_cm: product.physicalProperties.height || null, width_cm: product.physicalProperties.width || null, depth_cm: product.physicalProperties.depth || null, @@ -55,7 +56,7 @@ function toUpdatePhysicalProperties(product: Product): any { } function toUpdateCircularityProperties(product: Product): any { - return { + const out = { recyclability_comment: product.circularityProperties.recyclabilityComment ?? null, recyclability_observation: product.circularityProperties.recyclabilityObservation, recyclability_reference: product.circularityProperties.recyclabilityReference ?? null, @@ -66,6 +67,10 @@ function toUpdateCircularityProperties(product: Product): any { repairability_observation: product.circularityProperties.repairabilityObservation, repairability_reference: product.circularityProperties.repairabilityReference ?? null, }; + + // If all values are null, return null so the caller can omit the object + const hasAny = Object.values(out).some((v) => v !== null); + return hasAny ? out : null; } export async function saveProduct(product: Product): Promise { @@ -170,13 +175,15 @@ async function addImage(product: Product, image: { url: string; description: str } else if (image.url.startsWith('file:')) { console.log(image.url); body.append('file', { uri: image.url, name: 'image.png', type: 'image/png' } as any); - } else if (image.url.startsWith('blob:')) { - // Fetch the blob from the blob URL + } else if (image.url.startsWith('blob:') || image.url.startsWith('http')) { + // Web blob or URL - fetch and convert to blob const response = await fetch(image.url); const blob = await response.blob(); body.append('file', blob, 'image.png'); } + console.log('[AddImage] Uploading image:', image.url); + await fetch(url, { method: 'POST', headers: headers, body: body }); } @@ -200,10 +207,7 @@ async function updateProductVideos(product: Product) { const videosToAdd = product.videos.filter((vid) => !vid.id); const videosToUpdate = product.videos.filter((vid) => { const orig = currentVideos.find((v) => v.id === vid.id); - return orig && (orig.url !== vid.url - || orig.description !== vid.description - || orig.title !== vid.title - ); + return orig && (orig.url !== vid.url || orig.description !== vid.description || orig.title !== vid.title); }); for (const vid of videosToDelete) { @@ -217,7 +221,7 @@ async function updateProductVideos(product: Product) { } } -async function addVideo(product: Product, video: { url: string; description: string, title: string, }) { +async function addVideo(product: Product, video: { url: string; description: string; title: string }) { const url = new URL(baseUrl + `/products/${product.id}/videos`); const token = await getToken(); const headers = { @@ -243,7 +247,7 @@ async function deleteVideo(product: Product, video: { id?: number }) { await fetch(url, { method: 'DELETE', headers }); } -async function updateVideo(product: Product, video: { id?: number; url: string; description: string, title: string, }) { +async function updateVideo(product: Product, video: { id?: number; url: string; description: string; title: string }) { if (!video.id) { return; } diff --git a/frontend-app/src/services/api/validation/product.ts b/frontend-app/src/services/api/validation/product.ts index 37c9658..b55c6b0 100644 --- a/frontend-app/src/services/api/validation/product.ts +++ b/frontend-app/src/services/api/validation/product.ts @@ -7,9 +7,33 @@ import { Product } from '@/types/Product'; export const PRODUCT_NAME_MIN_LENGTH = 2; export const PRODUCT_NAME_MAX_LENGTH = 100; -export function isValidProductName(value: string | undefined): boolean { +export type ValidationResult = { + isValid: boolean; + error?: string; +}; + +export function validateProductName(value: string | undefined): ValidationResult { const name = typeof value === 'string' ? value.trim() : ''; - return name.length >= PRODUCT_NAME_MIN_LENGTH && name.length <= PRODUCT_NAME_MAX_LENGTH; + + if (!name) { + return { isValid: false, error: 'Product name is required' }; + } + + if (name.length < PRODUCT_NAME_MIN_LENGTH) { + return { + isValid: false, + error: `Product name must be at least ${PRODUCT_NAME_MIN_LENGTH} characters`, + }; + } + + if (name.length > PRODUCT_NAME_MAX_LENGTH) { + return { + isValid: false, + error: `Product name must be at most ${PRODUCT_NAME_MAX_LENGTH} characters`, + }; + } + + return { isValid: true }; } export function getProductNameHelperText(): string { @@ -31,27 +55,88 @@ export function isValidUrl(value: string | undefined): boolean { } } -export function isProductValid(product: Product): boolean { - const { weight, width, height, depth } = product.physicalProperties; - - // Allow undefined dimensions, but if provided, they must be positive numbers - const isValidDimension = (val: number | undefined) => { - return val == null || Number.isNaN(val) || (typeof val === 'number' && val > 0); - }; - - // Validate that all videos have non-empty titles and valid URLs - const areVideosValid = product.videos.every(video => { - return video.title.trim().length > 0 && isValidUrl(video.url); - }); - - return ( - isValidProductName(product.name) && - typeof weight === 'number' && - !Number.isNaN(weight) && - weight > 0 && - isValidDimension(width) && - isValidDimension(height) && - isValidDimension(depth) && - areVideosValid - ); +export function validateProductDimension(value: number | undefined, dimensionName: string): ValidationResult { + if (value == null || Number.isNaN(value)) { + return { isValid: true }; // Optional field + } + + if (typeof value !== 'number' || value <= 0) { + return { + isValid: false, + error: `${dimensionName} must be a positive number`, + }; + } + + return { isValid: true }; +} + +export function validateProductWeight(value: number | undefined): ValidationResult { + if (value == null || Number.isNaN(value)) { + return { isValid: false, error: 'Weight is required' }; + } + + if (typeof value !== 'number' || value <= 0) { + return { isValid: false, error: 'Weight must be a positive number' }; + } + + return { isValid: true }; +} + +export function validateProductVideos(videos: { title: string; url: string }[]): ValidationResult { + for (const video of videos) { + if (video.title.trim().length === 0) { + return { isValid: false, error: 'Video title cannot be empty' }; + } + if (!isValidUrl(video.url)) { + return { isValid: false, error: `Invalid URL for video titled "${video.title}"` }; + } + } + return { isValid: true }; +} + +export function validateProduct(product: Product): ValidationResult { + // Handle undefined or incomplete product + if (!product || typeof product !== 'object') { + return { isValid: false, error: 'Invalid product data' }; + } + + // Validate product name + const nameResult = validateProductName(product.name); + if (!nameResult.isValid) { + return nameResult; + } + + // Safely access physicalProperties with fallback + const physicalProperties = product.physicalProperties || {}; + const { weight, width, height, depth } = physicalProperties; + + // Validate weight + const weightResult = validateProductWeight(weight); + if (!weightResult.isValid) { + return weightResult; + } + + // Validate dimensions + const widthResult = validateProductDimension(width, 'Width'); + if (!widthResult.isValid) { + return widthResult; + } + + const heightResult = validateProductDimension(height, 'Height'); + if (!heightResult.isValid) { + return heightResult; + } + + const depthResult = validateProductDimension(depth, 'Depth'); + if (!depthResult.isValid) { + return depthResult; + } + + // Validate product videos + const videosResult = validateProductVideos(product.videos); + if (!videosResult.isValid) { + return videosResult; + } + + return { isValid: true }; } diff --git a/frontend-app/src/services/api/validation/user.ts b/frontend-app/src/services/api/validation/user.ts new file mode 100644 index 0000000..7ed6057 --- /dev/null +++ b/frontend-app/src/services/api/validation/user.ts @@ -0,0 +1,123 @@ +/** + * User validation utilities + */ + +// Constants +export const USERNAME_MIN_LENGTH = 2; +export const USERNAME_MAX_LENGTH = 50; +export const USERNAME_PATTERN = /^\w+$/; // Only letters, numbers, and underscores + +export const PASSWORD_MIN_LENGTH = 8; +export const PASSWORD_MAX_LENGTH = 128; + +// Types +export type ValidationResult = { + isValid: boolean; + error?: string; +}; + +// Methods +export function validateUsername(value: string | undefined): ValidationResult { + const username = typeof value === 'string' ? value.trim() : ''; + + if (!username) { + return { isValid: false, error: 'Username is required' }; + } + + if (username.length < USERNAME_MIN_LENGTH) { + return { + isValid: false, + error: `Username must be at least ${USERNAME_MIN_LENGTH} characters`, + }; + } + + if (username.length > USERNAME_MAX_LENGTH) { + return { + isValid: false, + error: `Username must be at most ${USERNAME_MAX_LENGTH} characters`, + }; + } + + if (!USERNAME_PATTERN.test(username)) { + return { + isValid: false, + error: 'Username can only contain letters, numbers, and underscores', + }; + } + + return { isValid: true }; +} + +export function getUsernameHelperText(): string { + return `Username must be ${USERNAME_MIN_LENGTH}-${USERNAME_MAX_LENGTH} characters and contain only letters, numbers, and underscores`; +} + +export function validateEmail(value: string | undefined): ValidationResult { + const email = typeof value === 'string' ? value.trim() : ''; + + if (!email) { + return { isValid: false, error: 'Email is required' }; + } + + // Basic email format validation + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailPattern.test(email)) { + return { isValid: false, error: 'Please enter a valid email address' }; + } + + return { isValid: true }; +} + +export function getEmailHelperText(): string { + return 'Enter a valid email address'; +} + +export function validatePassword(value: string | undefined, username?: string, email?: string): ValidationResult { + const password = typeof value === 'string' ? value : ''; + + if (!password) { + return { isValid: false, error: 'Password is required' }; + } + + if (password.length < PASSWORD_MIN_LENGTH) { + return { + isValid: false, + error: `Password must be at least ${PASSWORD_MIN_LENGTH} characters`, + }; + } + + if (password.length > PASSWORD_MAX_LENGTH) { + return { + isValid: false, + error: `Password must be at most ${PASSWORD_MAX_LENGTH} characters`, + }; + } + + // Check if password contains username + if (username && password.toLowerCase().includes(username.toLowerCase())) { + return { + isValid: false, + error: 'Password cannot contain your username', + }; + } + + // Check if password contains email or email username part + if (email) { + const emailUsername = email.split('@')[0]; + if ( + password.toLowerCase().includes(email.toLowerCase()) || + password.toLowerCase().includes(emailUsername.toLowerCase()) + ) { + return { + isValid: false, + error: 'Password cannot contain your email address', + }; + } + } + + return { isValid: true }; +} + +export function getPasswordHelperText(): string { + return `Password must be ${PASSWORD_MIN_LENGTH}-${PASSWORD_MAX_LENGTH} characters and cannot contain your username or email`; +} diff --git a/frontend-app/src/types/Product.ts b/frontend-app/src/types/Product.ts index df35132..a90e6c1 100644 --- a/frontend-app/src/types/Product.ts +++ b/frontend-app/src/types/Product.ts @@ -12,7 +12,7 @@ export type Product = { physicalProperties: PhysicalProperties; circularityProperties: CircularityProperties; images: { id?: number; url: string; description: string }[]; - videos: { id?: number; url: string; description: string; title: string; }[]; + videos: { id?: number; url: string; description: string; title: string }[]; ownedBy: 'me' | string; amountInParent?: number; }; @@ -35,4 +35,3 @@ export type CircularityProperties = { repairabilityObservation: string; repairabilityReference?: string | null; }; - diff --git a/renovate.json b/renovate.json index a024b67..0b3d5ad 100644 --- a/renovate.json +++ b/renovate.json @@ -4,34 +4,22 @@ "config:best-practices", ":approveMajorUpdates", ":maintainLockFilesWeekly", + ":preserveSemverRanges", "schedule:weekly" ], "packageRules": [ { "groupName": "backend", - "matchFileNames": [ - "backend/pyproject.toml", - "backend/.python-version", - "backend/Dockerfile*" - ] + "matchFileNames": ["backend/pyproject.toml", "backend/.python-version", "backend/Dockerfile*"] }, { "groupName": "frontend", - "matchFileNames": [ - "frontend/package.json", - "frontend/Dockerfile*" - ] + "matchFileNames": ["frontend/package.json", "frontend/Dockerfile*"] }, { "groupName": "infrastructure", - "matchFileNames": [ - "**/compose.*.yml", - "**/compose.yml" - ] + "matchFileNames": ["**/compose.*.yml", "**/compose.yml"] } ], - "labels": [ - "dependencies", - "renovate" - ] + "labels": ["dependencies", "renovate"] } diff --git a/uv.lock b/uv.lock index a14c571..22883f8 100644 --- a/uv.lock +++ b/uv.lock @@ -4,20 +4,20 @@ requires-python = ">=3.13" [[package]] name = "argcomplete" -version = "3.6.2" +version = "3.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, ] [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] @@ -72,7 +72,7 @@ wheels = [ [[package]] name = "commitizen" -version = "4.9.1" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -88,9 +88,9 @@ dependencies = [ { name = "termcolor" }, { name = "tomlkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/19/927ac5b0eabb9451e2d5bb45b30813915c9a1260713b5b68eeb31358ea23/commitizen-4.9.1.tar.gz", hash = "sha256:b076b24657718f7a35b1068f2083bd39b4065d250164a1398d1dac235c51753b", size = 56610, upload-time = "2025-09-10T14:19:33.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/b3/cc29794fc2ecd7aa7353105773ca18ecd761c3ba5b38879bd106b3fc8840/commitizen-4.10.0.tar.gz", hash = "sha256:cc58067403b9eff21d0423b3d9a29bda05254bd51ad5bdd1fd0594bff31277e1", size = 56820, upload-time = "2025-11-10T14:08:49.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/49/577035b841442fe031b017027c3d99278b46104d227f0353c69dbbe55148/commitizen-4.9.1-py3-none-any.whl", hash = "sha256:4241b2ecae97b8109af8e587c36bc3b805a09b9a311084d159098e12d6ead497", size = 80624, upload-time = "2025-09-10T14:19:32.102Z" }, + { url = "https://files.pythonhosted.org/packages/b3/5d/2bd8881737d6a5652ae3ebc37736893b9a7425f0eb16e605d1ff2957267e/commitizen-4.10.0-py3-none-any.whl", hash = "sha256:3fe56c168b30b30b84b8329cba6b132e77b4eb304a5460bbe2186aad0f78c966", size = 81269, upload-time = "2025-11-10T14:08:48.001Z" }, ] [[package]] @@ -104,14 +104,14 @@ wheels = [ [[package]] name = "deprecated" -version = "1.2.18" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, ] [[package]] @@ -234,7 +234,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.3.0" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -243,9 +243,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428, upload-time = "2025-11-22T21:02:42.304Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, ] [[package]] @@ -329,11 +329,11 @@ dev = [ [[package]] name = "termcolor" -version = "3.1.0" +version = "3.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/56/ab275c2b56a5e2342568838f0d5e3e66a32354adcc159b495e374cda43f5/termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58", size = 14423, upload-time = "2025-10-25T19:11:42.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d5/141f53d7c1eb2a80e6d3e9a390228c3222c27705cbe7f048d3623053f3ca/termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6", size = 7698, upload-time = "2025-10-25T19:11:41.536Z" }, ] [[package]] @@ -347,16 +347,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.35.3" +version = "20.35.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/d5/b0ccd381d55c8f45d46f77df6ae59fbc23d19e901e2d523395598e5f4c93/virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44", size = 6002907, upload-time = "2025-10-10T21:23:33.178Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a", size = 5981061, upload-time = "2025-10-10T21:23:30.433Z" }, + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, ] [[package]] @@ -370,39 +370,57 @@ wheels = [ [[package]] name = "wrapt" -version = "1.17.3" +version = "2.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/41af4c46b5e498c90fc87981ab2972fbd9f0bccda597adb99d3d3441b94b/wrapt-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9", size = 78132, upload-time = "2025-11-07T00:44:04.628Z" }, + { url = "https://files.pythonhosted.org/packages/1c/92/d68895a984a5ebbbfb175512b0c0aad872354a4a2484fbd5552e9f275316/wrapt-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f", size = 61211, upload-time = "2025-11-07T00:44:05.626Z" }, + { url = "https://files.pythonhosted.org/packages/e8/26/ba83dc5ae7cf5aa2b02364a3d9cf74374b86169906a1f3ade9a2d03cf21c/wrapt-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218", size = 61689, upload-time = "2025-11-07T00:44:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/cf/67/d7a7c276d874e5d26738c22444d466a3a64ed541f6ef35f740dbd865bab4/wrapt-2.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9", size = 121502, upload-time = "2025-11-07T00:44:09.557Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6b/806dbf6dd9579556aab22fc92908a876636e250f063f71548a8660382184/wrapt-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c", size = 123110, upload-time = "2025-11-07T00:44:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/e5/08/cdbb965fbe4c02c5233d185d070cabed2ecc1f1e47662854f95d77613f57/wrapt-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db", size = 117434, upload-time = "2025-11-07T00:44:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d1/6aae2ce39db4cb5216302fa2e9577ad74424dfbe315bd6669725569e048c/wrapt-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233", size = 121533, upload-time = "2025-11-07T00:44:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/565abf57559fbe0a9155c29879ff43ce8bd28d2ca61033a3a3dd67b70794/wrapt-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2", size = 116324, upload-time = "2025-11-07T00:44:13.28Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e0/53ff5e76587822ee33e560ad55876d858e384158272cd9947abdd4ad42ca/wrapt-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b", size = 120627, upload-time = "2025-11-07T00:44:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7b/38df30fd629fbd7612c407643c63e80e1c60bcc982e30ceeae163a9800e7/wrapt-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7", size = 58252, upload-time = "2025-11-07T00:44:17.814Z" }, + { url = "https://files.pythonhosted.org/packages/85/64/d3954e836ea67c4d3ad5285e5c8fd9d362fd0a189a2db622df457b0f4f6a/wrapt-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3", size = 60500, upload-time = "2025-11-07T00:44:15.561Z" }, + { url = "https://files.pythonhosted.org/packages/89/4e/3c8b99ac93527cfab7f116089db120fef16aac96e5f6cdb724ddf286086d/wrapt-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8", size = 58993, upload-time = "2025-11-07T00:44:16.65Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f4/eff2b7d711cae20d220780b9300faa05558660afb93f2ff5db61fe725b9a/wrapt-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3", size = 82028, upload-time = "2025-11-07T00:44:18.944Z" }, + { url = "https://files.pythonhosted.org/packages/0c/67/cb945563f66fd0f61a999339460d950f4735c69f18f0a87ca586319b1778/wrapt-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1", size = 62949, upload-time = "2025-11-07T00:44:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ca/f63e177f0bbe1e5cf5e8d9b74a286537cd709724384ff20860f8f6065904/wrapt-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d", size = 63681, upload-time = "2025-11-07T00:44:21.345Z" }, + { url = "https://files.pythonhosted.org/packages/39/a1/1b88fcd21fd835dca48b556daef750952e917a2794fa20c025489e2e1f0f/wrapt-2.0.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7", size = 152696, upload-time = "2025-11-07T00:44:24.318Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/d9185500c1960d9f5f77b9c0b890b7fc62282b53af7ad1b6bd779157f714/wrapt-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3", size = 158859, upload-time = "2025-11-07T00:44:25.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/60/5d796ed0f481ec003220c7878a1d6894652efe089853a208ea0838c13086/wrapt-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b", size = 146068, upload-time = "2025-11-07T00:44:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/04/f8/75282dd72f102ddbfba137e1e15ecba47b40acff32c08ae97edbf53f469e/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10", size = 155724, upload-time = "2025-11-07T00:44:26.634Z" }, + { url = "https://files.pythonhosted.org/packages/5a/27/fe39c51d1b344caebb4a6a9372157bdb8d25b194b3561b52c8ffc40ac7d1/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf", size = 144413, upload-time = "2025-11-07T00:44:27.939Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/9f6b643fe39d4505c7bf926d7c2595b7cb4b607c8c6b500e56c6b36ac238/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e", size = 150325, upload-time = "2025-11-07T00:44:29.29Z" }, + { url = "https://files.pythonhosted.org/packages/bb/b6/20ffcf2558596a7f58a2e69c89597128781f0b88e124bf5a4cadc05b8139/wrapt-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c", size = 59943, upload-time = "2025-11-07T00:44:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/87/6a/0e56111cbb3320151eed5d3821ee1373be13e05b376ea0870711f18810c3/wrapt-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92", size = 63240, upload-time = "2025-11-07T00:44:30.935Z" }, + { url = "https://files.pythonhosted.org/packages/1d/54/5ab4c53ea1f7f7e5c3e7c1095db92932cc32fd62359d285486d00c2884c3/wrapt-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f", size = 60416, upload-time = "2025-11-07T00:44:32.002Z" }, + { url = "https://files.pythonhosted.org/packages/73/81/d08d83c102709258e7730d3cd25befd114c60e43ef3891d7e6877971c514/wrapt-2.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1", size = 78290, upload-time = "2025-11-07T00:44:34.691Z" }, + { url = "https://files.pythonhosted.org/packages/f6/14/393afba2abb65677f313aa680ff0981e829626fed39b6a7e3ec807487790/wrapt-2.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55", size = 61255, upload-time = "2025-11-07T00:44:35.762Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/a4a1f2fba205a9462e36e708ba37e5ac95f4987a0f1f8fd23f0bf1fc3b0f/wrapt-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0", size = 61797, upload-time = "2025-11-07T00:44:37.22Z" }, + { url = "https://files.pythonhosted.org/packages/12/db/99ba5c37cf1c4fad35349174f1e38bd8d992340afc1ff27f526729b98986/wrapt-2.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509", size = 120470, upload-time = "2025-11-07T00:44:39.425Z" }, + { url = "https://files.pythonhosted.org/packages/30/3f/a1c8d2411eb826d695fc3395a431757331582907a0ec59afce8fe8712473/wrapt-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1", size = 122851, upload-time = "2025-11-07T00:44:40.582Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8d/72c74a63f201768d6a04a8845c7976f86be6f5ff4d74996c272cefc8dafc/wrapt-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970", size = 117433, upload-time = "2025-11-07T00:44:38.313Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/df37cf4042cb13b08256f8e27023e2f9b3d471d553376616591bb99bcb31/wrapt-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c", size = 121280, upload-time = "2025-11-07T00:44:41.69Z" }, + { url = "https://files.pythonhosted.org/packages/54/34/40d6bc89349f9931e1186ceb3e5fbd61d307fef814f09fbbac98ada6a0c8/wrapt-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41", size = 116343, upload-time = "2025-11-07T00:44:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/70/66/81c3461adece09d20781dee17c2366fdf0cb8754738b521d221ca056d596/wrapt-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed", size = 119650, upload-time = "2025-11-07T00:44:44.523Z" }, + { url = "https://files.pythonhosted.org/packages/46/3a/d0146db8be8761a9e388cc9cc1c312b36d583950ec91696f19bbbb44af5a/wrapt-2.0.1-cp314-cp314-win32.whl", hash = "sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0", size = 58701, upload-time = "2025-11-07T00:44:48.277Z" }, + { url = "https://files.pythonhosted.org/packages/1a/38/5359da9af7d64554be63e9046164bd4d8ff289a2dd365677d25ba3342c08/wrapt-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c", size = 60947, upload-time = "2025-11-07T00:44:46.086Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3f/96db0619276a833842bf36343685fa04f987dd6e3037f314531a1e00492b/wrapt-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e", size = 59359, upload-time = "2025-11-07T00:44:47.164Z" }, + { url = "https://files.pythonhosted.org/packages/71/49/5f5d1e867bf2064bf3933bc6cf36ade23505f3902390e175e392173d36a2/wrapt-2.0.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b", size = 82031, upload-time = "2025-11-07T00:44:49.4Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/0009a218d88db66ceb83921e5685e820e2c61b59bbbb1324ba65342668bc/wrapt-2.0.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec", size = 62952, upload-time = "2025-11-07T00:44:50.74Z" }, + { url = "https://files.pythonhosted.org/packages/ae/18/9b968e920dd05d6e44bcc918a046d02afea0fb31b2f1c80ee4020f377cbe/wrapt-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa", size = 63688, upload-time = "2025-11-07T00:44:52.248Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7d/78bdcb75826725885d9ea26c49a03071b10c4c92da93edda612910f150e4/wrapt-2.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815", size = 152706, upload-time = "2025-11-07T00:44:54.613Z" }, + { url = "https://files.pythonhosted.org/packages/dd/77/cac1d46f47d32084a703df0d2d29d47e7eb2a7d19fa5cbca0e529ef57659/wrapt-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa", size = 158866, upload-time = "2025-11-07T00:44:55.79Z" }, + { url = "https://files.pythonhosted.org/packages/8a/11/b521406daa2421508903bf8d5e8b929216ec2af04839db31c0a2c525eee0/wrapt-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef", size = 146148, upload-time = "2025-11-07T00:44:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c0/340b272bed297baa7c9ce0c98ef7017d9c035a17a6a71dce3184b8382da2/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747", size = 155737, upload-time = "2025-11-07T00:44:56.971Z" }, + { url = "https://files.pythonhosted.org/packages/f3/93/bfcb1fb2bdf186e9c2883a4d1ab45ab099c79cbf8f4e70ea453811fa3ea7/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f", size = 144451, upload-time = "2025-11-07T00:44:58.515Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6b/dca504fb18d971139d232652656180e3bd57120e1193d9a5899c3c0b7cdd/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349", size = 150353, upload-time = "2025-11-07T00:44:59.753Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f6/a1de4bd3653afdf91d250ca5c721ee51195df2b61a4603d4b373aa804d1d/wrapt-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c", size = 60609, upload-time = "2025-11-07T00:45:03.315Z" }, + { url = "https://files.pythonhosted.org/packages/01/3a/07cd60a9d26fe73efead61c7830af975dfdba8537632d410462672e4432b/wrapt-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395", size = 64038, upload-time = "2025-11-07T00:45:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/99/8a06b8e17dddbf321325ae4eb12465804120f699cd1b8a355718300c62da/wrapt-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad", size = 60634, upload-time = "2025-11-07T00:45:02.087Z" }, + { url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" }, ]