Skip to content

Commit

Permalink
feat: add mistake report endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
m0wer committed Oct 21, 2024
1 parent a8ee133 commit 890e1ba
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 8 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ repos:

- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.6.8
rev: v0.7.0
hooks:
# Run the linter.
- id: ruff
Expand All @@ -11,7 +11,7 @@ repos:
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.11.2'
rev: 'v1.12.1'
hooks:
- id: mypy
# add types-requests
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

RUN useradd app && \
chown -R app:app /app
USER app

COPY ./app /app/app
COPY ./main.py /app/main.py
COPY ./cli.py /app/cli.py
Expand Down
2 changes: 2 additions & 0 deletions app/celery_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
accept_content=["json"],
task_routes={
"app.worker.find_closest_products_with_preload": {"queue": "high"},
"app.worker.process_wrong_match_report": {"queue": "low"},
"app.worker.process_wrong_nutrition_report": {"queue": "low"},
},
)
28 changes: 28 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ class NutritionalInformation(NutritionalInformationBase, table=True):
product: "Product" = Relationship(back_populates="nutritional_information")


class WrongMatchReport(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
original_name: str
original_price: float
wrong_match_id: str = Field(foreign_key="product.id")
wrong_match: "Product" = Relationship(back_populates="wrong_match_reports")
timestamp: datetime = Field(default_factory=datetime.utcnow)
status: str = Field(default="pending") # pending, reviewed, rejected
notes: str | None = None


class WrongNutritionReport(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
product_id: str = Field(foreign_key="product.id")
product: "Product" = Relationship(back_populates="nutrition_reports")
nutrition_id: int = Field(foreign_key="nutritionalinformation.id")
nutritional_information: NutritionalInformation = Relationship()
timestamp: datetime = Field(default_factory=datetime.utcnow)
status: str = Field(default="pending") # pending, reviewed, rejected
notes: str | None = None


class Product(ProductBase, table=True):
category: Category = Relationship(back_populates="products")
images: List[ProductImage] = Relationship(
Expand All @@ -82,6 +104,12 @@ class Product(ProductBase, table=True):
nutritional_information: Union[NutritionalInformation, None] = Relationship(
back_populates="product", sa_relationship_kwargs={"lazy": "joined"}
)
wrong_match_reports: List[WrongMatchReport] = Relationship(
back_populates="wrong_match"
)
nutrition_reports: List[WrongNutritionReport] = Relationship(
back_populates="product"
)


class PriceHistory(PriceHistoryBase, table=True):
Expand Down
43 changes: 43 additions & 0 deletions app/routers/reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from app.worker import process_wrong_match_report, process_wrong_nutrition_report
from loguru import logger

router = APIRouter(prefix="/reports", tags=["reports"])


class WrongMatchReportRequest(BaseModel):
original_name: str
original_price: float
wrong_match_id: str


class WrongNutritionReportRequest(BaseModel):
product_id: str
nutrition_id: int


@router.post("/wrong-match")
async def report_wrong_match(report: WrongMatchReportRequest):
try:
process_wrong_match_report.delay(
original_name=report.original_name,
original_price=report.original_price,
wrong_match_id=report.wrong_match_id,
)
return {"status": "Report submitted successfully"}
except Exception as e:
logger.error(f"Error submitting wrong match report: {str(e)}")
raise HTTPException(status_code=500, detail="Error submitting report")


@router.post("/wrong-nutrition")
async def report_wrong_nutrition(report: WrongNutritionReportRequest):
try:
process_wrong_nutrition_report.delay(
product_id=report.product_id, nutrition_id=report.nutrition_id
)
return {"status": "Report submitted successfully"}
except Exception as e:
logger.error(f"Error submitting wrong nutrition report: {str(e)}")
raise HTTPException(status_code=500, detail="Error submitting report")
45 changes: 43 additions & 2 deletions app/worker.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# app/worker.py
from loguru import logger

from app.celery_config import celery_app
from app.database import get_session
from app.models import WrongMatchReport, WrongNutritionReport
from app.shared.cache import get_all_products
from app.shared.product_matcher import find_closest_products_task
from loguru import logger

products = []

Expand Down Expand Up @@ -33,3 +34,43 @@ def find_closest_products_with_preload(*args, **kwargs):
result.model_dump()
for result in find_closest_products_task(products, *args, **kwargs)
]


@celery_app.task(
default_retry_delay=30,
max_retries=5,
)
def process_wrong_match_report(
original_name: str, original_price: float, wrong_match_id: str
):
try:
with next(get_session()) as session:
report = WrongMatchReport(
original_name=original_name,
original_price=original_price,
wrong_match_id=wrong_match_id,
)
session.add(report)
session.commit()
logger.info(f"Saved wrong match report for product {wrong_match_id}")
except Exception as e:
logger.error(f"Error saving wrong match report: {str(e)}")
raise


@celery_app.task(
default_retry_delay=30,
max_retries=5,
)
def process_wrong_nutrition_report(product_id: str, nutrition_id: int):
try:
with next(get_session()) as session:
report = WrongNutritionReport(
product_id=product_id, nutrition_id=nutrition_id
)
session.add(report)
session.commit()
logger.info(f"Saved wrong nutrition report for product {product_id}")
except Exception as e:
logger.error(f"Error saving wrong nutrition report: {str(e)}")
raise
31 changes: 28 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:
app:
build: .
image: ghcr.io/m0wer/mercaapi:master
command: fastapi run --reload --port 80
command: python -m fastapi run --reload --port 80
ports:
- "8000:80"
volumes:
Expand All @@ -18,6 +18,8 @@ services:
depends_on:
- redis
- worker-high
- worker-low
user: '1000:1000'
deploy:
resources:
limits:
Expand All @@ -38,6 +40,7 @@ services:
- CELERY_RESULT_BACKEND=redis://redis:6379/0
depends_on:
- redis
user: '1000:1000'
deploy:
resources:
limits:
Expand All @@ -50,6 +53,7 @@ services:
- "6379:6379"
volumes:
- ./redis:/data
user: '1000:1000'
deploy:
resources:
limits:
Expand All @@ -59,7 +63,7 @@ services:

worker-high:
build: .
command: celery -A app.worker worker -Q high -c 8 -l DEBUG
command: python -m celery -A app.worker worker -Q high -c 8 -l DEBUG
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
Expand All @@ -77,9 +81,29 @@ services:
memory: 2G
restart: always

worker-low:
build: .
command: python -m celery -A app.worker worker -Q low -c 1 -l DEBUG
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
- PYTHONPATH=/app
volumes:
- ./app:/app/app
- ./mercadona.db:/app/mercadona.db
depends_on:
- redis
user: '1000:1000'
deploy:
resources:
limits:
cpus: '1'
memory: 300M
restart: always

flower:
image: mher/flower
command: celery flower --broker=redis://redis:6379/0 --port=5555
command: python -m celery flower --broker=redis://redis:6379/0 --port=5555
ports:
- "5555:5555"
environment:
Expand All @@ -88,6 +112,7 @@ services:
depends_on:
- redis
- worker-high
user: '1000:1000'
deploy:
resources:
limits:
Expand Down
3 changes: 2 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from fastapi.responses import RedirectResponse
from loguru import logger

from app.routers import products, categories, ticket
from app.routers import products, categories, ticket, reports

# Configure loguru
logger.remove()
Expand Down Expand Up @@ -34,6 +34,7 @@ async def add_process_time_header(request: Request, call_next):
api_router.include_router(products.router)
api_router.include_router(categories.router)
api_router.include_router(ticket.router)
api_router.include_router(reports.router)

# Mount the API router
app.mount("/api", api_router)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Add wrong match and nutrition reports
Revision ID: feff058e9105
Revises: 07d7883b354c
Create Date: 2024-10-21 17:13:13.996589
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import sqlmodel


# revision identifiers, used by Alembic.
revision: str = "feff058e9105"
down_revision: Union[str, None] = "07d7883b354c"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"wrongmatchreport",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("original_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("original_price", sa.Float(), nullable=False),
sa.Column("wrong_match_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("notes", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.ForeignKeyConstraint(
["wrong_match_id"],
["product.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"wrongnutritionreport",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("product_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("nutrition_id", sa.Integer(), nullable=False),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("status", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("notes", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.ForeignKeyConstraint(
["nutrition_id"],
["nutritionalinformation.id"],
),
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("wrongnutritionreport")
op.drop_table("wrongmatchreport")
# ### end Alembic commands ###

0 comments on commit 890e1ba

Please sign in to comment.