Prathamesh Sable commited on
Commit
5aa5283
·
1 Parent(s): 3846a4f

Databse and model setup

Browse files
db/database.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from sqlalchemy import create_engine
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy.orm import sessionmaker
5
+ from dotenv import load_dotenv
6
+
7
+ # Load environment variables
8
+ load_dotenv()
9
+
10
+ # Get database URL from environment variable
11
+ DATABASE_URL = os.getenv(
12
+ "DATABASE_URL",
13
+ "postgresql://postgres:password@localhost:5432/food_ingredients"
14
+ )
15
+
16
+ # Create engine
17
+ engine = create_engine(DATABASE_URL)
18
+
19
+ # Create session factory
20
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
21
+
22
+ # Create base class for models
23
+ Base = declarative_base()
24
+
25
+ # Dependency to get DB session
26
+ def get_db():
27
+ db = SessionLocal()
28
+ try:
29
+ yield db
30
+ finally:
31
+ db.close()
db/models.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Float, Boolean, Text, JSON, ForeignKey, DateTime
2
+ from sqlalchemy.orm import relationship
3
+ from sqlalchemy.sql import func
4
+ from .database import Base
5
+ from typing import List, Optional
6
+ from datetime import datetime
7
+
8
+
9
+ class Ingredient(Base):
10
+ __tablename__ = "ingredients"
11
+
12
+ id = Column(Integer, primary_key=True, index=True)
13
+ name = Column(String, unique=True, index=True)
14
+ alternate_names = Column(JSON, nullable=True)
15
+ safety_rating = Column(Integer, nullable=True)
16
+ description = Column(Text, nullable=True)
17
+ health_effects = Column(JSON, nullable=True)
18
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
19
+ updated_at = Column(DateTime(timezone=True), onupdate=func.now())
20
+
21
+ # Relationships
22
+ sources = relationship("IngredientSource", back_populates="ingredient")
23
+
24
+ class IngredientSource(Base):
25
+ __tablename__ = "ingredient_sources"
26
+
27
+ id = Column(Integer, primary_key=True, index=True)
28
+ ingredient_id = Column(Integer, ForeignKey("ingredients.id"))
29
+ source_name = Column(String, nullable=False)
30
+ found = Column(Boolean, default=False)
31
+ summary = Column(Text, nullable=True)
32
+ data = Column(JSON, nullable=True)
33
+
34
+ # Relationships
35
+ ingredient = relationship("Ingredient", back_populates="sources")
36
+
37
+ class Product(Base):
38
+ __tablename__ = "products"
39
+
40
+ id = Column(Integer, primary_key=True, index=True)
41
+ product_name = Column(String, nullable=False)
42
+ generic_name = Column(String, nullable=True)
43
+ brands = Column(String, nullable=True)
44
+ ingredients = Column(JSON, nullable=True)
45
+ ingredients_text = Column(String, nullable=True)
46
+ ingredients_analysis = Column(JSON, nullable=True)
47
+ nutriscore = Column(JSON, nullable=True)
48
+ nutrient_levels = Column(JSON, nullable=True)
49
+ nutriments = Column(JSON, nullable=True)
50
+ data_quality_warnings = Column(JSON, nullable=True)
51
+
52
+
53
+ class User(Base):
54
+ __tablename__ = "users"
55
+
56
+ id = Column(Integer, primary_key=True, index=True)
57
+ username = Column(String, unique=True, index=True)
58
+ email = Column(String, unique=True, index=True)
59
+ hashed_password = Column(String, nullable=False)
60
+ is_active = Column(Boolean, default=True)
61
+
62
+ # Relationships
63
+ preferences = relationship(
64
+ "UserPreferences",
65
+ back_populates="user",
66
+ cascade="all, delete-orphan"
67
+ )
68
+ scan_history = relationship(
69
+ "ScanHistory",
70
+ back_populates="user",
71
+ cascade="all, delete-orphan"
72
+ )
73
+
74
+ class UserPreferences(Base):
75
+ __tablename__ = "user_preferences"
76
+
77
+ id = Column(Integer, primary_key=True, index=True)
78
+ user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"))
79
+ dietary_restrictions = Column(String, nullable=True)
80
+ allergens = Column(String, nullable=True)
81
+ preferred_ingredients = Column(String, nullable=True)
82
+ disliked_ingredients = Column(String, nullable=True)
83
+
84
+ # Relationships
85
+ user = relationship("User", back_populates="preferences")
86
+
87
+ class ScanHistory(Base):
88
+ __tablename__ = "scan_history"
89
+
90
+ id = Column(Integer, primary_key=True, index=True)
91
+ user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"))
92
+ product_id = Column(Integer)
93
+ scan_date = Column(DateTime, default=datetime.now)
94
+
95
+ # Relationships
96
+ user = relationship("User", back_populates="scan_history")
db/repositories.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from sqlalchemy import cast, or_, String
3
+ from sqlalchemy.dialects.postgresql import JSONB
4
+ from . import models
5
+ from interfaces.ingredientModels import IngredientAnalysisResult
6
+
7
+ class IngredientRepository:
8
+ def __init__(self, db: Session):
9
+ self.db = db
10
+
11
+ def get_ingredient_by_name(self, name: str):
12
+ exact_match = self.db.query(models.Ingredient).filter(models.Ingredient.name.ilike(name)).first()
13
+
14
+ if exact_match:
15
+ return exact_match
16
+
17
+ # If no exact match, try searching in alternate names
18
+ try:
19
+ # Use .first() to return the model instance, not the query object
20
+ alternate_match = self.db.query(models.Ingredient).filter(
21
+ models.Ingredient.alternate_names.cast(JSONB).op('?')(name)
22
+ ).first()
23
+
24
+ return alternate_match
25
+ except Exception as e:
26
+ from logger_manager import logger
27
+ logger.error(f"Error searching alternate names: {e}")
28
+ return None
29
+
30
+ def get_all_ingredients(self, skip: int = 0, limit: int = 100):
31
+ return self.db.query(models.Ingredient).offset(skip).limit(limit).all()
32
+
33
+ def create_ingredient(self, ingredient_data: IngredientAnalysisResult):
34
+ # Create ingredient record
35
+ db_ingredient = models.Ingredient(
36
+ name=ingredient_data.name,
37
+ alternate_names=ingredient_data.alternate_names,
38
+ safety_rating=ingredient_data.safety_rating,
39
+ description=ingredient_data.description,
40
+ health_effects=ingredient_data.health_effects
41
+ )
42
+ self.db.add(db_ingredient)
43
+ self.db.commit()
44
+ self.db.refresh(db_ingredient)
45
+
46
+ # Create source records
47
+ for source in ingredient_data.details_with_source:
48
+ db_source = models.IngredientSource(
49
+ ingredient_id=db_ingredient.id,
50
+ source_name=source.get("source", "Unknown"),
51
+ found=source.get("found", False),
52
+ summary=source.get("summary", ""),
53
+ data=source
54
+ )
55
+ self.db.add(db_source)
56
+
57
+ self.db.commit()
58
+ return db_ingredient
59
+
60
+ def update_ingredient(self, name: str, ingredient_data: IngredientAnalysisResult):
61
+ db_ingredient = self.get_ingredient_by_name(name)
62
+ if db_ingredient:
63
+ # Update ingredient fields
64
+ db_ingredient.alternate_names = ingredient_data.alternate_names
65
+ db_ingredient.safety_rating = ingredient_data.safety_rating
66
+ db_ingredient.description = ingredient_data.description
67
+ db_ingredient.health_effects = ingredient_data.health_effects
68
+
69
+ # Delete old sources
70
+ self.db.query(models.IngredientSource).filter(
71
+ models.IngredientSource.ingredient_id == db_ingredient.id
72
+ ).delete()
73
+
74
+ # Create new sources
75
+ for source in ingredient_data.details_with_source:
76
+ db_source = models.IngredientSource(
77
+ ingredient_id=db_ingredient.id,
78
+ source_name=source.get("source", "Unknown"),
79
+ found=source.get("found", False),
80
+ summary=source.get("summary", ""),
81
+ data=source
82
+ )
83
+ self.db.add(db_source)
84
+
85
+ self.db.commit()
86
+ self.db.refresh(db_ingredient)
87
+ return db_ingredient
88
+ return None
interfaces/authModels.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class UserCreate(BaseModel):
4
+ username: str
5
+ email: str
6
+ password: str
7
+
8
+ class Token(BaseModel):
9
+ access_token: str
10
+ token_type: str
11
+
12
+ class UserResponse(BaseModel):
13
+ id: int
14
+ username: str
15
+ email: str
16
+ is_active: bool
17
+
18
+ class Config:
19
+ from_attributes = True # This enables ORM mode
20
+
21
+
22
+ class TokenData(BaseModel):
23
+ username: str | None = None
interfaces/ingredientModels.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, Any, Optional, TypedDict
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ # Define a structured output model
6
+ class IngredientAnalysisResult(BaseModel):
7
+ name: str
8
+ alternate_names: List[str] = Field(default_factory=list)
9
+ is_found: bool = False
10
+ safety_rating: int = 5
11
+ description: str = "No information found."
12
+ health_effects: List[str] = Field(default_factory=lambda: ["Unknown"])
13
+ details_with_source: List[Dict[str, Any]] = [Field(default_factory=list)]
14
+
15
+ class Config:
16
+ from_attributes = True # Enable ORM mode
17
+
18
+ # Define typed state for LangGraph
19
+ class IngredientState(TypedDict):
20
+ ingredient: str
21
+ sources_data: List[Dict[str, Any]]
22
+ status: str
23
+ result: Optional[Dict[str, Any]]
24
+ local_db_checked: bool
25
+ web_search_done: bool
26
+ wikipedia_checked: bool
27
+ open_food_facts_checked: bool
28
+ usda_checked: bool
29
+ pubchem_checked: bool
30
+ analysis_done: bool
31
+
32
+ class IngredientRequest(BaseModel):
33
+ name: str
models/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- from .base import Base
2
- from .user import User
3
- from .user_preferences import UserPreferences
4
- from .ingredient import Ingredient
5
- from .scan_history import ScanHistory
6
- from .product import Product
7
-
8
- __all__ = ["Base", "User", "UserPreferences", "Ingredient", "ScanHistory", "Product"]
 
 
 
 
 
 
 
 
 
models/base.py DELETED
@@ -1,4 +0,0 @@
1
- from sqlalchemy.orm import DeclarativeBase
2
-
3
- class Base(DeclarativeBase):
4
- pass
 
 
 
 
 
models/ingredient.py DELETED
@@ -1,14 +0,0 @@
1
- from sqlalchemy import Column, Integer, String, JSON, Boolean
2
- from .base import Base
3
-
4
- class Ingredient(Base):
5
- __tablename__ = "ingredients"
6
-
7
- id = Column(Integer, primary_key=True, index=True)
8
- name = Column(String, unique=True, index=True)
9
- nutritional_info = Column(JSON)
10
- description = Column(String, nullable=True)
11
- origin = Column(String, nullable=True)
12
- allergens = Column(String, nullable=True)
13
- vegan = Column(Boolean, nullable=True)
14
- vegetarian = Column(Boolean, nullable=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/product.py DELETED
@@ -1,17 +0,0 @@
1
- from sqlalchemy import Column, Integer, String, JSON
2
- from .base import Base
3
-
4
- class Product(Base):
5
- __tablename__ = "products"
6
-
7
- id = Column(Integer, primary_key=True, index=True)
8
- product_name = Column(String, nullable=False)
9
- generic_name = Column(String, nullable=True)
10
- brands = Column(String, nullable=True)
11
- ingredients = Column(JSON, nullable=True)
12
- ingredients_text = Column(String, nullable=True)
13
- ingredients_analysis = Column(JSON, nullable=True)
14
- nutriscore = Column(JSON, nullable=True)
15
- nutrient_levels = Column(JSON, nullable=True)
16
- nutriments = Column(JSON, nullable=True)
17
- data_quality_warnings = Column(JSON, nullable=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/scan_history.py DELETED
@@ -1,16 +0,0 @@
1
- from sqlalchemy.orm import Mapped, mapped_column, relationship
2
- from sqlalchemy import ForeignKey, Integer, DateTime
3
- from datetime import datetime
4
- from .base import Base
5
- from typing import Optional
6
-
7
- class ScanHistory(Base):
8
- __tablename__ = "scan_history"
9
-
10
- id: Mapped[int] = mapped_column(primary_key=True)
11
- user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
12
- product_id: Mapped[int] = mapped_column(Integer)
13
- scan_date: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
14
-
15
- # Add relationship to user
16
- user: Mapped["User"] = relationship("User", back_populates="scan_history")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/user.py DELETED
@@ -1,28 +0,0 @@
1
- from sqlalchemy.orm import Mapped, mapped_column, relationship
2
- from sqlalchemy import String, Boolean
3
- from typing import List, TYPE_CHECKING
4
- from .base import Base
5
-
6
- if TYPE_CHECKING:
7
- from .user_preferences import UserPreferences
8
-
9
- class User(Base):
10
- __tablename__ = "users"
11
-
12
- id: Mapped[int] = mapped_column(primary_key=True)
13
- username: Mapped[str] = mapped_column(String, unique=True, index=True)
14
- email: Mapped[str] = mapped_column(String, unique=True, index=True)
15
- hashed_password: Mapped[str] = mapped_column(String)
16
- is_active: Mapped[bool] = mapped_column(Boolean, default=True)
17
-
18
- preferences: Mapped[List["UserPreferences"]] = relationship(
19
- "UserPreferences",
20
- back_populates="user",
21
- cascade="all, delete-orphan"
22
- )
23
- # Add relationship to scan history
24
- scan_history: Mapped[List["ScanHistory"]] = relationship(
25
- "ScanHistory",
26
- back_populates="user",
27
- cascade="all, delete-orphan"
28
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models/user_preferences.py DELETED
@@ -1,19 +0,0 @@
1
- from sqlalchemy.orm import Mapped, mapped_column, relationship
2
- from sqlalchemy import ForeignKey, String
3
- from typing import Optional, TYPE_CHECKING
4
- from .base import Base
5
-
6
- if TYPE_CHECKING:
7
- from .user import User
8
-
9
- class UserPreferences(Base):
10
- __tablename__ = "user_preferences"
11
-
12
- id: Mapped[int] = mapped_column(primary_key=True)
13
- user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
14
- dietary_restrictions: Mapped[Optional[str]] = mapped_column(String, nullable=True)
15
- allergens: Mapped[Optional[str]] = mapped_column(String, nullable=True)
16
- preferred_ingredients: Mapped[Optional[str]] = mapped_column(String, nullable=True)
17
- disliked_ingredients: Mapped[Optional[str]] = mapped_column(String, nullable=True)
18
-
19
- user: Mapped["User"] = relationship("User", back_populates="preferences")