├── src ├── __init__.py ├── main │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── product_api.py │ │ ├── retailer_api.py │ │ └── price_api.py │ ├── models │ │ ├── __init__.py │ │ ├── price.py │ │ ├── product.py │ │ ├── promotion.py │ │ ├── user.py │ │ ├── saved_list.py │ │ ├── price_alert.py │ │ └── retailer.py │ ├── factories │ │ ├── __init__.py │ │ └── repository_factory.py │ ├── repositories │ │ ├── __init__.py │ │ ├── promotion │ │ │ ├── __init__.py │ │ │ ├── database_promotion_repository.py │ │ │ ├── filesystem_promotion_repository.py │ │ │ └── redis_promotion_repository.py │ │ ├── inmemory │ │ │ ├── __init__.py │ │ │ ├── inmemory_user_repository.py │ │ │ ├── inmemory_saved_list_repository.py │ │ │ ├── inmemory_product_repository.py │ │ │ ├── inmemory_promotion_repository.py │ │ │ ├── inmemory_retailer_repository.py │ │ │ └── inmemory_price_alert_repository.py │ │ ├── user_repository.py │ │ ├── saved_list_repository.py │ │ ├── retailer_repository.py │ │ ├── repository.py │ │ ├── promotion_repository.py │ │ ├── price_alert_repository.py │ │ └── product_repository.py │ ├── services │ │ ├── __init__.py │ │ ├── price_service.py │ │ ├── product_service.py │ │ └── retailer_service.py │ ├── requirements.txt │ ├── compare_prices.py │ ├── app.py │ └── run_scraper.py ├── scraper │ ├── __init__.py │ ├── checkers_scraper.py │ ├── woolworths_scraper.py │ └── picknpay_scraper.py └── tests │ ├── __init__.py │ └── unit │ ├── __init__.py │ ├── test_basic.py │ ├── test_product.py │ └── test_user.py ├── .github └── workflows │ ├── prcheck.png │ ├── artifact-rollout.png │ ├── Screenshot 2025-05-10 at 19.03.48.png │ ├── codeql.yml │ └── ci.yml ├── docs ├── branch_protection_rules.png ├── kanban │ ├── update 20250504.jpg │ ├── index.md │ ├── kanban_board.md │ ├── kanban_explanation.md │ └── template_analysis.md ├── CICD_implementation │ ├── branch_protection_rules.png │ └── protection.md ├── branch_protection_rules │ ├── branch_protection_rules.png │ └── protection.md ├── diagrams │ ├── activity_diagrams │ │ ├── product.md │ │ ├── retailer_profile.md │ │ ├── price_alert.md │ │ ├── activity_diagrams.md │ │ └── user_account.md │ ├── state_transition_diagrams │ │ ├── price_alert.md │ │ ├── product.md │ │ ├── retailer_profile.md │ │ ├── state_transition_diagrams.md │ │ └── user_account.md │ └── Traceability Matrix.md ├── protection.md ├── agile_planning │ ├── agile_planning_document.md │ ├── sprint_planning.md │ ├── backlog.md │ └── user_stories.md ├── voting_results.md ├── test_use_case_documentation │ ├── test_use_case_documentation.md │ ├── test_case_development.md │ ├── use_case_diagrams.md │ └── use_case_specifications.md ├── Reflection ├── domain_model │ ├── class_diagram_updated.js │ ├── explanation_of_the_class_diagram.md │ ├── class_diagram.js │ └── domain_model_documentation.md └── specification │ ├── stakeholder_analysis.md │ ├── specification.md │ ├── architecture.md │ └── system_requirements_document.md ├── .idea ├── vcs.xml ├── .gitignore ├── misc.xml ├── modules.xml ├── inspectionProfiles │ └── Project_Default.xml └── Price-aggregator-comparison-app.iml ├── setup.py ├── pyproject.toml ├── license.md ├── changelog.md ├── test_scrapers.py ├── .gitignore ├── roadmap.md ├── CONTRIBUTING.md └── README.md /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scraper/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/factories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/repositories/promotion/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi>=0.104.0 2 | pydantic>=2.0.0 3 | redis>=5.0.0 4 | uvicorn>=0.24.0 -------------------------------------------------------------------------------- /.github/workflows/prcheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/.github/workflows/prcheck.png -------------------------------------------------------------------------------- /docs/branch_protection_rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/docs/branch_protection_rules.png -------------------------------------------------------------------------------- /docs/kanban/update 20250504.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/docs/kanban/update 20250504.jpg -------------------------------------------------------------------------------- /.github/workflows/artifact-rollout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/.github/workflows/artifact-rollout.png -------------------------------------------------------------------------------- /src/main/repositories/inmemory/__init__.py: -------------------------------------------------------------------------------- 1 | # src/main/repositories/inmemory/__init__.py 2 | from .inmemory_price_alert_repository import InMemoryPriceAlertRepository, InMemoryPriceRepository -------------------------------------------------------------------------------- /docs/CICD_implementation/branch_protection_rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/docs/CICD_implementation/branch_protection_rules.png -------------------------------------------------------------------------------- /.github/workflows/Screenshot 2025-05-10 at 19.03.48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/.github/workflows/Screenshot 2025-05-10 at 19.03.48.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/branch_protection_rules/branch_protection_rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denzil-daniels-204525918/Price-aggregator-comparison-app/HEAD/docs/branch_protection_rules/branch_protection_rules.png -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/main/api/product_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter(prefix="/api/products", tags=["products-api"]) 4 | 5 | @router.get("/") 6 | async def get_products(): 7 | return {"message": "Products API endpoint"} -------------------------------------------------------------------------------- /src/main/repositories/user_repository.py: -------------------------------------------------------------------------------- 1 | # src/main/repositories/user_repository.py 2 | from .repository import Repository 3 | from price_aggregator.main.models.user import User 4 | 5 | class UserRepository(Repository[User, str]): 6 | pass -------------------------------------------------------------------------------- /src/main/api/retailer_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter(prefix="/api/retailers", tags=["retailers-api"]) 4 | 5 | @router.get("/") 6 | async def get_retailers(): 7 | return {"message": "Retailers API endpoint"} -------------------------------------------------------------------------------- /src/main/repositories/saved_list_repository.py: -------------------------------------------------------------------------------- 1 | # src/repositories/saved_list_repository.py 2 | from .repository import Repository 3 | from price_aggregator.main.models.saved_list import SavedList 4 | 5 | class SavedListRepository(Repository[SavedList, str]): 6 | pass -------------------------------------------------------------------------------- /src/main/repositories/retailer_repository.py: -------------------------------------------------------------------------------- 1 | from .repository import Repository 2 | from price_aggregator.main.models.retailer import Retailer 3 | 4 | class RetailerRepository(Repository[Retailer, str]): 5 | """ 6 | Repository for Retailer entity extending base Repository class. 7 | """ 8 | pass 9 | -------------------------------------------------------------------------------- /src/tests/unit/test_basic.py: -------------------------------------------------------------------------------- 1 | def test_basic(): 2 | assert 1 + 1 == 2 3 | 4 | def test_imports(): 5 | try: 6 | from price_aggregator.main.user import User 7 | from price_aggregator.main.product import Product 8 | assert True 9 | except ImportError: 10 | assert False, "Import failed" 11 | -------------------------------------------------------------------------------- /src/main/api/price_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | router = APIRouter(prefix="/prices", tags=["prices"]) 4 | 5 | @router.get("/") 6 | async def get_prices(): 7 | return {"message": "Prices endpoint"} 8 | 9 | @router.get("/compare") 10 | async def compare_prices(): 11 | return {"message": "Price comparison endpoint"} -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/models/price.py: -------------------------------------------------------------------------------- 1 | # --- src/main/models/price.py --- 2 | from pydantic import BaseModel 3 | 4 | class Price(BaseModel): 5 | price_id: str 6 | product_id: str 7 | retailer_id: str 8 | amount: float 9 | currency: str 10 | date: str # ISO format (e.g., '2025-05-04') 11 | alert_id: str 12 | price_threshold: float 13 | -------------------------------------------------------------------------------- /src/main/models/product.py: -------------------------------------------------------------------------------- 1 | # --- src/main/models/product.py --- 2 | from pydantic import BaseModel 3 | from typing import Optional 4 | import copy 5 | 6 | class Product(BaseModel): 7 | product_id: str 8 | name: str 9 | price: float 10 | description: Optional[str] = None 11 | category: Optional[str] = None 12 | 13 | def clone(self): 14 | return copy.deepcopy(self) -------------------------------------------------------------------------------- /src/tests/unit/test_product.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from price_aggregator.main.product import Product 3 | 4 | def test_product_creation(): 5 | product = Product( 6 | product_id="prod123", 7 | name="Test Product", 8 | price=10.99, 9 | description="Test description" 10 | ) 11 | assert product.product_id == "prod123" 12 | assert product.name == "Test Product" 13 | assert product.price == 10.99 -------------------------------------------------------------------------------- /docs/diagrams/activity_diagrams/product.md: -------------------------------------------------------------------------------- 1 | # 📌 Search for a Product 2 | 3 | ```mermaid 4 | graph TD; 5 | A[Start] --> B[User Enters Search Query]; 6 | B --> C[Fetch Matching Products]; 7 | C --> D[Display Results]; 8 | D --> E[End]; 9 | ``` 10 | 11 | ### Explanation: 12 | - Ensures users can find items efficiently. 13 | - Fetches data dynamically for real-time updates. 14 | 15 | --- 16 | 17 | [Back to README.md](../../../README.md) -------------------------------------------------------------------------------- /docs/diagrams/activity_diagrams/retailer_profile.md: -------------------------------------------------------------------------------- 1 | # 📌 Retailer Price Updates 2 | 3 | ```mermaid 4 | graph TD; 5 | A[Start] --> B[Retailer Logs In]; 6 | B --> C[Navigate to Dashboard]; 7 | C --> D[Update Product Prices]; 8 | D --> E[Changes Reflected in System]; 9 | E --> F[End]; 10 | ``` 11 | 12 | ### Explanation: 13 | - Ensures accurate price updates from retailers. 14 | - Keeps system data fresh and relevant. 15 | 16 | --- 17 | 18 | [Back to README.md](../../../README.md) -------------------------------------------------------------------------------- /src/main/compare_prices.py: -------------------------------------------------------------------------------- 1 | from src.scraper import checkers_scraper, picknpay_scraper, woolworths_scraper 2 | 3 | search = "Clover Tropika Orange 2L" 4 | 5 | prices = { 6 | "Checkers": checkers_scraper.get_products(search), 7 | "Pick n Pay": picknpay_scraper.get_products(search), 8 | "Woolworths": woolworths_scraper.get_products(search) 9 | } 10 | 11 | for store, products in prices.items(): 12 | for name, price in products.items(): 13 | print(f"{store} → {name}: R{price}") 14 | -------------------------------------------------------------------------------- /src/main/models/promotion.py: -------------------------------------------------------------------------------- 1 | # --- src/main/models/promotion.py --- 2 | from pydantic import BaseModel, field_validator 3 | 4 | class Promotion(BaseModel): 5 | promotion_id: str 6 | title: str 7 | description: str 8 | discount_percentage: float 9 | duration_days: int 10 | 11 | @field_validator('discount_percentage') 12 | def validate_discount(cls, v): 13 | if not 0 <= v <= 100: 14 | raise ValueError('Discount must be between 0 and 100') 15 | return v -------------------------------------------------------------------------------- /src/tests/unit/test_user.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from price_aggregator.main.user import User 3 | 4 | def test_user_creation(): 5 | user = User(user_id="test123", name="Test User", email="test@example.com") 6 | assert user.user_id == "test123" 7 | assert user.name == "Test User" 8 | assert user.email == "test@example.com" 9 | 10 | def test_user_login(): 11 | user = User(user_id="test123", name="Test User", email="test@example.com") 12 | # Add login test logic here 13 | assert user.user_id == "test123" -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /docs/diagrams/activity_diagrams/price_alert.md: -------------------------------------------------------------------------------- 1 | # 📌 Set Price Alerts 2 | 3 | ```mermaid 4 | graph TD; 5 | A[Start] --> B[User Selects Product]; 6 | B --> C[Click 'Set Price Alert']; 7 | C --> D[User Defines Target Price]; 8 | D --> E[System Monitors Prices]; 9 | E -->|Price Drops| F[Send Notification]; 10 | F --> G[End]; 11 | ``` 12 | 13 | ### Explanation: 14 | - Helps users get notified of deals. 15 | - Automates price tracking for convenience. 16 | 17 | --- 18 | * [Back to Activity Diagrams overview](Activity%20Diagrams.md) 19 | * [Back to README.md](../../../README.md) -------------------------------------------------------------------------------- /docs/diagrams/state_transition_diagrams/price_alert.md: -------------------------------------------------------------------------------- 1 | ### 📌 Price Alert 2 | ```mermaid 3 | graph TD; 4 | Subscribed -->|Price Drops| Notified 5 | Subscribed -->|User Cancels| Unsubscribed 6 | Notified -->|User Views| Viewed 7 | ``` 8 | **Explanation:** 9 | - **Key states:** Subscribed, Notified, Viewed, Unsubscribed 10 | - **Transitions:** Triggered by changes in product price or user actions 11 | - **Mapping to Functional Requirements:** FR-003 (Personalized user alerts) 12 | 13 | --- 14 | * [Back to State Transition Diagrams](../../State%20Transition%20Diagrams.md) 15 | * [Back to README](../../../README.md) -------------------------------------------------------------------------------- /docs/kanban/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- 4 | 5 | ### 🎯 Case: Price aggregator comparison system 6 | 7 | --- 8 | ## Overview 9 | 10 | This document outlines the Kanban project management tool for the Price Aggregator Comparison System: 11 | 12 | * [Template_analysis](template_analysis.md): Template Analysis and Selection 13 | * [Kanban_board](kanban_board.md): Visuals of the projects; - Kanban board, - Linked Issues with labels, - Task assignments and statuses 14 | * [Kanban_explanation](kanban_explanation.md): Defining and exploring the Kanban tool, and it's application withing the project 15 | 16 | * [Reflection](reflection.md) 17 | -------------------------------------------------------------------------------- /src/main/repositories/repository.py: -------------------------------------------------------------------------------- 1 | # src/main/repositories/repository.py 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import Generic, TypeVar, Optional, List 5 | 6 | T = TypeVar('T') # Entity type 7 | ID = TypeVar('ID') # ID type 8 | 9 | class Repository(ABC, Generic[T, ID]): 10 | @abstractmethod 11 | def save(self, entity: T) -> None: 12 | pass 13 | 14 | @abstractmethod 15 | def find_by_id(self, id: ID) -> Optional[T]: 16 | pass 17 | 18 | @abstractmethod 19 | def find_all(self) -> List[T]: 20 | pass 21 | 22 | @abstractmethod 23 | def delete(self, id: ID) -> None: 24 | pass 25 | -------------------------------------------------------------------------------- /src/main/repositories/promotion_repository.py: -------------------------------------------------------------------------------- 1 | # src/repositories/promotion_repository.py 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import List 5 | from price_aggregator.main.models.promotion import Promotion 6 | 7 | class PromotionRepository(ABC): 8 | 9 | @abstractmethod 10 | def save(self, promotion: Promotion) -> None: 11 | pass 12 | 13 | @abstractmethod 14 | def find_by_id(self, promotion_id: str) -> Promotion: 15 | pass 16 | 17 | @abstractmethod 18 | def find_all(self) -> List[Promotion]: 19 | pass 20 | 21 | @abstractmethod 22 | def delete(self, promotion_id: str) -> None: 23 | pass 24 | -------------------------------------------------------------------------------- /docs/diagrams/state_transition_diagrams/product.md: -------------------------------------------------------------------------------- 1 | ### 📌 Product 2 | ```mermaid 3 | graph TD; 4 | Draft -->|Submitted by Retailer| PendingApproval 5 | PendingApproval -->|Admin Approves| Published 6 | PendingApproval -->|Admin Rejects| Draft 7 | Published -->|Marked Unavailable| Unavailable 8 | ``` 9 | **Explanation:** 10 | - **Key states:** Draft, PendingApproval, Published, Unavailable 11 | - **Transitions:** Retailers submit products for approval before they're shown publicly 12 | - **Mapping to Functional Requirements:** FR-002 (Content moderation and product availability) 13 | 14 | --- 15 | * [Back to State Transition Diagrams](../../State%20Transition%20Diagrams.md) 16 | * [Back to README](../../../README.md) -------------------------------------------------------------------------------- /docs/diagrams/state_transition_diagrams/retailer_profile.md: -------------------------------------------------------------------------------- 1 | Retailer Profile 2 | ```mermaid 3 | graph TD; 4 | Created -->|Admin Verifies| Active 5 | Active -->|Retailer Suspended| Suspended 6 | Suspended -->|Retailer Appeals| UnderReview 7 | UnderReview -->|Admin Approves| Active 8 | UnderReview -->|Admin Denies| Suspended 9 | ``` 10 | **Explanation:** 11 | - **Key states:** Created, Active, Suspended, UnderReview 12 | - **Transitions:** Controlled by admin moderation and appeal processes 13 | - **Mapping to Functional Requirements:** FR-004 (Retailer access and visibility) 14 | 15 | --- 16 | * [Back to State Transition Diagrams](State%20Transition%20Diagrams.md) 17 | * [Back to README](../../../README.md) 18 | -------------------------------------------------------------------------------- /src/main/models/user.py: -------------------------------------------------------------------------------- 1 | # --- src/main/models/user.py --- 2 | class User: 3 | def __init__(self, user_id: str, name: str, email: str, password: str): 4 | self.user_id = user_id 5 | self.name = name 6 | self.email = email 7 | self._password = password 8 | self._is_logged_in = False 9 | 10 | def login(self, password: str) -> bool: 11 | if password == self._password: 12 | self._is_logged_in = True 13 | print(f"{self.name} logged in.") 14 | return True 15 | print("Incorrect password.") 16 | return False 17 | 18 | def logout(self) -> bool: 19 | self._is_logged_in = False 20 | print(f"{self.name} logged out.") 21 | return True -------------------------------------------------------------------------------- /docs/protection.md: -------------------------------------------------------------------------------- 1 | # Branch Protection Rules for `main` 2 | 3 | This document explains why branch protection is enforced in this repository. 4 | 5 | ## **Why Protect `main`?** 6 | ✅ **Prevents Broken Code** – Ensures all changes are reviewed and tested before merging. 7 | ✅ **Enforces Code Quality** – Mandates PR reviews, reducing bugs and bad commits. 8 | ✅ **Blocks Direct Pushes** – No accidental/unreviewed changes bypassing CI. 9 | 10 | ## **Rules Applied** 11 | 1. **PR Review Required** – At least 1 approval before merging. 12 | 2. **CI Checks Must Pass** – Ensures tests/linting pass before merging. 13 | 3. **No Direct Pushes** – All changes must go through a PR. 14 | 15 | 🚀 **Result:** A stable, reliable `main` branch with fewer production issues! -------------------------------------------------------------------------------- /src/main/models/saved_list.py: -------------------------------------------------------------------------------- 1 | # --- src/main/models/saved_list.py --- 2 | class SavedList: 3 | def __init__(self, list_id: str, user_id: str): 4 | self.list_id = list_id 5 | self.user_id = user_id 6 | self.products = [] 7 | 8 | def add_product_to_list(self, product): 9 | self.products.append(product) 10 | print(f"Product {product.name} added to saved list {self.list_id}.") 11 | 12 | def remove_product_from_list(self, product): 13 | if product in self.products: 14 | self.products.remove(product) 15 | print(f"Product {product.name} removed from saved list {self.list_id}.") 16 | else: 17 | print(f"Product {product.name} not found in saved list {self.list_id}.") -------------------------------------------------------------------------------- /src/main/repositories/price_alert_repository.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Generic, TypeVar, Optional, List 3 | from price_aggregator.main.models.price_alert import PriceAlert 4 | 5 | 6 | T = TypeVar('T') 7 | ID = TypeVar('ID') 8 | 9 | class Repository(ABC, Generic[T, ID]): 10 | @abstractmethod 11 | def save(self, entity: T) -> None: 12 | pass 13 | 14 | @abstractmethod 15 | def find_by_id(self, id: ID) -> Optional[T]: 16 | pass 17 | 18 | @abstractmethod 19 | def find_all(self) -> List[T]: 20 | pass 21 | 22 | @abstractmethod 23 | def delete(self, id: ID) -> None: 24 | pass 25 | 26 | class PriceAlertRepository(Repository[PriceAlert, str], ABC): 27 | pass 28 | -------------------------------------------------------------------------------- /docs/branch_protection_rules/protection.md: -------------------------------------------------------------------------------- 1 | # Branch Protection Rules for `main` 2 | 3 | This document explains why branch protection is enforced in this repository. 4 | 5 | ## **Why Protect `main`?** 6 | ✅ **Prevents Broken Code** – Ensures all changes are reviewed and tested before merging. 7 | ✅ **Enforces Code Quality** – Mandates PR reviews, reducing bugs and bad commits. 8 | ✅ **Blocks Direct Pushes** – No accidental/unreviewed changes bypassing CI. 9 | 10 | ## **Rules Applied** 11 | 1. **PR Review Required** – At least 1 approval before merging. 12 | 2. **CI Checks Must Pass** – Ensures tests/linting pass before merging. 13 | 3. **No Direct Pushes** – All changes must go through a PR. 14 | 15 | 🚀 **Result:** A stable, reliable `main` branch with fewer production issues! -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main, develop ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '0 0 * * 0' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | 24 | - name: Initialize CodeQL 25 | uses: github/codeql-action/init@v3 26 | with: 27 | languages: python 28 | 29 | - name: Autobuild 30 | uses: github/codeql-action/autobuild@v3 31 | 32 | - name: Perform CodeQL Analysis 33 | uses: github/codeql-action/analyze@v3 -------------------------------------------------------------------------------- /src/main/repositories/product_repository.py: -------------------------------------------------------------------------------- 1 | # src/main/repositories/product_repository.py 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import List, Optional 5 | from price_aggregator.main.models.product import Product 6 | 7 | class ProductRepository(ABC): 8 | @abstractmethod 9 | def save(self, product: Product) -> None: 10 | pass 11 | 12 | @abstractmethod 13 | def find_by_id(self, id: str) -> Optional[Product]: 14 | pass 15 | 16 | @abstractmethod 17 | def find_all(self) -> List[Product]: 18 | pass 19 | 20 | @abstractmethod 21 | def delete(self, id: str) -> None: 22 | pass 23 | 24 | @abstractmethod 25 | def update(self, product_id: str, product: Product) -> Product: 26 | """Update a product by its ID.""" 27 | pass 28 | -------------------------------------------------------------------------------- /.idea/Price-aggregator-comparison-app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/kanban/kanban_board.md: -------------------------------------------------------------------------------- 1 | ## Kanban board 2 | 3 | ![{D32FBF1F-0BFF-4FEF-BF16-B97F26E41987}](https://github.com/user-attachments/assets/0eb61675-11bd-4bec-9110-609b8619111e) 4 | 5 | ## Linked Issues with labels 6 | 7 | ![{F888DF02-E397-42DE-8D72-381450F577CC}](https://github.com/user-attachments/assets/d04f0808-e6f1-4ef0-bb08-618150d0f751) 8 | 9 | ## Task assignments and statuses 10 | ![{B6B4D6CE-DC0C-48B5-AD12-23C718DC1EF6}](https://github.com/user-attachments/assets/76335b97-e19e-4406-beab-1651869cbbd7) 11 | --- 12 | ## Read Me 13 | I added To Do, In Progress, and Done columns to align with sprint planning boards, ensuring tasks can be tracked through different stages. Additionally, I created the custom Testing column which can be used for QA and reviews, and a Blocked column for tasks or processes that are removed or delayed in Sprint 1. 14 | 15 | --- 16 | -------------------------------------------------------------------------------- /src/main/factories/repository_factory.py: -------------------------------------------------------------------------------- 1 | from price_aggregator.main.repositories.promotion.filesystem_promotion_repository import FileSystemPromotionRepositoryStub 2 | from price_aggregator.main.repositories.promotion.database_promotion_repository import DatabasePromotionRepositoryStub 3 | from price_aggregator.main.repositories.promotion.redis_promotion_repository import RedisPromotionRepositoryStub 4 | from price_aggregator.main.repositories.promotion_repository import PromotionRepository 5 | 6 | class RepositoryFactory: 7 | @staticmethod 8 | def get_promotion_repository(storage_type: str = "filesystem") -> PromotionRepository: 9 | if storage_type == "database": 10 | return DatabasePromotionRepositoryStub() 11 | elif storage_type == "redis": 12 | return RedisPromotionRepositoryStub() 13 | else: 14 | return FileSystemPromotionRepositoryStub() 15 | -------------------------------------------------------------------------------- /src/main/models/price_alert.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Optional 3 | from datetime import datetime 4 | 5 | class Price(BaseModel): 6 | """Represents a price entry for a product""" 7 | product_id: str 8 | retailer_id: str 9 | price: float 10 | currency: str = "ZAR" 11 | timestamp: datetime = datetime.now() 12 | 13 | class PriceAlert(BaseModel): 14 | """Represents a price alert set by a user""" 15 | alert_id: str 16 | user_id: str 17 | product_id: str 18 | target_price: float 19 | is_active: bool = True 20 | created_at: datetime = datetime.now() 21 | 22 | def check_trigger(self, current_price: float) -> bool: 23 | """Check if the current price triggers the alert""" 24 | return current_price <= self.target_price 25 | 26 | def deactivate(self): 27 | """Deactivate the price alert""" 28 | self.is_active = False -------------------------------------------------------------------------------- /src/main/repositories/inmemory/inmemory_user_repository.py: -------------------------------------------------------------------------------- 1 | # src/main/repositories/inmemory/inmemory_user_repository.py 2 | 3 | from price_aggregator.main.repositories.repository import Repository 4 | from typing import List, Optional 5 | from price_aggregator.main.models.user import User # Assuming a User class exists with at least an `id` field 6 | 7 | class InMemoryUserRepository(Repository[User, str]): 8 | def __init__(self): 9 | self._storage = {} # In-memory storage using a dictionary 10 | 11 | def save(self, user: User) -> None: 12 | self._storage[user.id] = user 13 | 14 | def find_by_id(self, id: str) -> Optional[User]: 15 | return self._storage.get(id) 16 | 17 | def find_all(self) -> List[User]: 18 | return list(self._storage.values()) 19 | 20 | def delete(self, id: str) -> None: 21 | if id in self._storage: 22 | del self._storage[id] 23 | -------------------------------------------------------------------------------- /src/main/repositories/inmemory/inmemory_saved_list_repository.py: -------------------------------------------------------------------------------- 1 | # src/main/repositories/inmemory/inmemory_saved_list_repository.py 2 | 3 | from price_aggregator.main.repositories.repository import Repository 4 | from typing import List, Optional 5 | from price_aggregator.main.models.saved_list import SavedList # Assuming this exists 6 | 7 | class InMemorySavedListRepository(Repository[SavedList, str]): 8 | def __init__(self): 9 | self._storage = {} # In-memory dictionary 10 | 11 | def save(self, saved_list: SavedList) -> None: 12 | self._storage[saved_list.id] = saved_list 13 | 14 | def find_by_id(self, id: str) -> Optional[SavedList]: 15 | return self._storage.get(id) 16 | 17 | def find_all(self) -> List[SavedList]: 18 | return list(self._storage.values()) 19 | 20 | def delete(self, id: str) -> None: 21 | if id in self._storage: 22 | del self._storage[id] 23 | -------------------------------------------------------------------------------- /docs/agile_planning/agile_planning_document.md: -------------------------------------------------------------------------------- 1 | # 📌 Agile Planning Document 2 | 3 | --- 4 | ### 🎯 Case: Price aggregator comparison system 5 | 6 | --- 7 | ## Overview 8 | 9 | This document outlines the Agile planning process for the Price Aggregator Comparison System: 10 | 11 | * [Backlog](backlog): A prioritized list of user stories and tasks that define the scope of work for the system. 12 | 13 | * [Sprint Planning](Sprint%20Planning.md): Details on how the development team will plan and execute the initial sprint, breaking down user stories into actionable tasks. 14 | 15 | * [User stories](User%20stories.md): User-focused requirements that describe desired features and functionality from the perspective of different system users. 16 | 17 | These documents form the foundation for the Agile development process, ensuring clear priorities and organized execution. 18 | 19 | --- 20 | 21 | [Back to README](../../README.md) 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/voting_results.md: -------------------------------------------------------------------------------- 1 | # 🗳️ Peer Engagement Results 2 | 3 | This document summarizes the community engagement for the **Price Aggregator Comparison App** repository. 4 | 5 | ## ⭐ GitHub Stars 6 | - **Total Stars:** 17 7 | 8 | ## 🍴 GitHub Forks 9 | - **Total Forks:** 13 10 | 11 | ## 📊 Engagement Summary 12 | - Several peers starred the repository to show support and interest in the project's functionality and relevance. 13 | - Forks indicate attempts to contribute, modify, or explore the app architecture and implementation. 14 | - Feedback and engagement were used to prioritise certain features in the project roadmap. 15 | 16 | ## 📣 Notes 17 | - The votes collected reflect genuine interest in solving real-world problems with AI-driven price comparison. 18 | - Contributors will provide ongoing suggestions via Issues and Discussions, which are being incorporated into upcoming sprints. 19 | 20 | > Thank you to everyone who supported and engaged with the project! 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name="price-aggregator", 8 | version="0.1.0", 9 | author="Your Name", 10 | author_email="your.email@example.com", 11 | description="A price aggregator and comparison system for grocery stores", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | package_dir={"": "src"}, 15 | packages=find_packages(where="src"), 16 | python_requires=">=3.9", 17 | install_requires=[ 18 | "fastapi>=0.104.0", 19 | "pydantic>=2.0.0", 20 | "redis>=5.0.0", 21 | "uvicorn>=0.24.0", 22 | ], 23 | extras_require={ 24 | "dev": [ 25 | "pytest>=7.0.0", 26 | "pytest-cov>=4.0.0", 27 | "black>=23.0.0", 28 | "flake8>=6.0.0", 29 | ], 30 | }, 31 | ) -------------------------------------------------------------------------------- /src/main/repositories/inmemory/inmemory_product_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List 2 | from price_aggregator.main.models.product import Product # Add this import 3 | 4 | class InMemoryProductRepository: 5 | def __init__(self): 6 | self.storage = {} 7 | 8 | def save(self, product: Product) -> Product: 9 | self.storage[product.product_id] = product 10 | return product 11 | 12 | def find_by_id(self, product_id: str) -> Optional[Product]: 13 | return self.storage.get(product_id) 14 | 15 | def find_all(self) -> List[Product]: 16 | return list(self.storage.values()) 17 | 18 | def delete(self, product_id: str) -> bool: 19 | if product_id in self.storage: 20 | del self.storage[product_id] 21 | return True 22 | return False 23 | 24 | def update(self, product: Product) -> Product: 25 | self.storage[product.product_id] = product 26 | return product -------------------------------------------------------------------------------- /src/main/repositories/promotion/database_promotion_repository.py: -------------------------------------------------------------------------------- 1 | # Stub or actual database implementation 2 | 3 | from price_aggregator.main.models.promotion import Promotion 4 | from price_aggregator.main.repositories.promotion_repository import PromotionRepository 5 | from typing import List 6 | 7 | class DatabasePromotionRepositoryStub(PromotionRepository): 8 | def __init__(self): 9 | self._db = [] # Simulating a database with an in-memory list 10 | 11 | def save(self, promotion: Promotion) -> None: 12 | self._db.append(promotion) 13 | 14 | def find_by_id(self, promotion_id: str) -> Promotion: 15 | for promo in self._db: 16 | if promo.promotion_id == promotion_id: 17 | return promo 18 | return None 19 | 20 | def find_all(self) -> List[Promotion]: 21 | return self._db 22 | 23 | def delete(self, promotion_id: str) -> None: 24 | self._db = [promo for promo in self._db if promo.promotion_id != promotion_id] 25 | -------------------------------------------------------------------------------- /src/main/services/price_service.py: -------------------------------------------------------------------------------- 1 | # src/main/services/price_service.py 2 | 3 | from typing import List, Optional 4 | from price_aggregator.main.models.price import Price 5 | from price_aggregator.main.repositories import PriceRepositoryInterface 6 | 7 | class PriceService: 8 | def __init__(self, repository: PriceRepositoryInterface): 9 | self.repository = repository 10 | 11 | def create_price(self, price_data: dict) -> Price: 12 | price = Price(**price_data) 13 | return self.repository.add_price(price) 14 | 15 | def get_price(self, price_id: str) -> Optional[Price]: 16 | return self.repository.get_price(price_id) 17 | 18 | def update_price(self, price_id: str, update_data: dict) -> Optional[Price]: 19 | return self.repository.update_price(price_id, update_data) 20 | 21 | def delete_price(self, price_id: str) -> bool: 22 | return self.repository.delete_price(price_id) 23 | 24 | def list_prices(self) -> List[Price]: 25 | return self.repository.list_prices() 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=45", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "price-aggregator" 7 | version = "0.1.0" 8 | description = "A price aggregator and comparison system for grocery stores" 9 | authors = [ 10 | {name = "Your Name", email = "your.email@example.com"}, 11 | ] 12 | readme = {file = "README.md", content-type = "text/markdown"} 13 | requires-python = ">=3.9" 14 | dependencies = [ 15 | "fastapi>=0.104.0", 16 | "pydantic>=2.0.0", 17 | "redis>=5.0.0", 18 | "uvicorn>=0.24.0", 19 | ] 20 | 21 | [project.optional-dependencies] 22 | dev = [ 23 | "pytest>=7.0.0", 24 | "pytest-cov>=4.0.0", 25 | "black>=23.0.0", 26 | "flake8>=6.0.0", 27 | ] 28 | 29 | [tool.setuptools.packages.find] 30 | where = ["src"] 31 | 32 | [tool.pytest.ini_options] 33 | testpaths = ["src/tests"] 34 | python_files = ["test_*.py"] 35 | addopts = "--cov=src --cov-report=html --cov-report=xml" 36 | 37 | [tool.black] 38 | line-length = 88 39 | target-version = ['py39'] -------------------------------------------------------------------------------- /src/main/repositories/inmemory/inmemory_promotion_repository.py: -------------------------------------------------------------------------------- 1 | from price_aggregator.main.repositories.promotion_repository import PromotionRepository 2 | from price_aggregator.main.models.promotion import Promotion 3 | from typing import Dict, List 4 | 5 | 6 | class InMemoryPromotionRepository(PromotionRepository): 7 | def __init__(self): 8 | self.storage: Dict[str, Promotion] = {} 9 | 10 | def save(self, promotion: Promotion) -> None: 11 | print(f"Saving promotion: {promotion.promotion_id}") # Added logging 12 | self.storage[promotion.promotion_id] = promotion 13 | 14 | def find_by_id(self, promotion_id: str) -> Promotion: 15 | print(f"Finding promotion by id: {promotion_id}") # Added logging 16 | return self.storage.get(promotion_id) 17 | 18 | def find_all(self) -> List[Promotion]: 19 | return list(self.storage.values()) 20 | 21 | def delete(self, promotion_id: str) -> None: 22 | print(f"Deleting promotion: {promotion_id}") # Added logging 23 | self.storage.pop(promotion_id, None) 24 | -------------------------------------------------------------------------------- /docs/test_use_case_documentation/test_use_case_documentation.md: -------------------------------------------------------------------------------- 1 | # 📌 Test and use case documentation 2 | 3 | --- 4 | ### 🎯 Use Case: Price aggregator comparison system 5 | 6 | --- 7 | 8 | 9 | * [Use Case Specifications.md](Use%20Case%20Specifications.md) 10 | --- 11 | 12 | ## Overview 13 | 14 | This section provides comprehensive documentation for the Price Aggregator Comparison System: 15 | 16 | * [Test Case Development](Test%20Case%20Development.md): Contains detailed test cases that validate the functionality of the system. It includes functional and non-functional test scenarios. 17 | 18 | * [Use Case Diagrams](Use%20Case%20Diagrams.md): Visual representation of system interactions, showing actors and use case relationships for better understanding of the workflow. 19 | 20 | * [Use Case Specifications](Use%20Case%20Specifications.md): Describes in detail the processes for key functionalities like searching products, comparing prices, subscribing to price alerts, and more. 21 | 22 | These documents ensure proper functionality, quality, and alignment with system requirements. 23 | 24 | --- 25 | [Back to README](../../README.md) -------------------------------------------------------------------------------- /src/main/repositories/inmemory/inmemory_retailer_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional, List 2 | from price_aggregator.main.models.retailer import Retailer 3 | 4 | class InMemoryRetailerRepository: 5 | def __init__(self): 6 | self.retailers: Dict[str, Retailer] = {} 7 | 8 | def save(self, retailer: Retailer) -> Retailer: 9 | self.retailers[retailer.retailer_id] = retailer 10 | return retailer 11 | 12 | def find_by_id(self, retailer_id: str) -> Optional[Retailer]: 13 | return self.retailers.get(retailer_id) 14 | 15 | def find_all(self) -> List[Retailer]: 16 | return list(self.retailers.values()) 17 | 18 | def delete(self, retailer_id: str) -> bool: 19 | if retailer_id in self.retailers: 20 | del self.retailers[retailer_id] 21 | return True 22 | return False 23 | 24 | def update(self, retailer_id: str, retailer: Retailer) -> Retailer: 25 | if retailer_id in self.retailers: 26 | self.retailers[retailer_id] = retailer 27 | return retailer 28 | raise ValueError("Retailer not found") 29 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Denzil Daniels 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/repositories/promotion/filesystem_promotion_repository.py: -------------------------------------------------------------------------------- 1 | from price_aggregator.main.models.promotion import Promotion 2 | from price_aggregator.main.repositories.promotion_repository import PromotionRepository 3 | from typing import List 4 | 5 | class FileSystemPromotionRepositoryStub(PromotionRepository): 6 | def __init__(self): 7 | self._storage = [] # Simulating a file with an in-memory list 8 | 9 | def save(self, promotion: Promotion) -> None: 10 | self._storage.append(promotion) # Adding the promotion to the list 11 | 12 | def find_by_id(self, promotion_id: str) -> Promotion: 13 | # Searching for the promotion by ID 14 | for promo in self._storage: 15 | if promo.promotion_id == promotion_id: 16 | return promo 17 | return None # If not found, return None 18 | 19 | def find_all(self) -> List[Promotion]: 20 | return self._storage # Returning all promotions in the storage 21 | 22 | def delete(self, promotion_id: str) -> None: 23 | # Removing the promotion with the given ID 24 | self._storage = [promo for promo in self._storage if promo.promotion_id != promotion_id] 25 | -------------------------------------------------------------------------------- /src/main/repositories/inmemory/inmemory_price_alert_repository.py: -------------------------------------------------------------------------------- 1 | from price_aggregator.main.models.price_alert import PriceAlert, Price # Added Price import 2 | from typing import List, Optional 3 | 4 | class InMemoryPriceAlertRepository: 5 | def __init__(self): 6 | self.alerts = [] 7 | 8 | def save(self, alert: PriceAlert) -> None: 9 | self.alerts.append(alert) 10 | 11 | def find_by_id(self, alert_id: str) -> Optional[PriceAlert]: 12 | return next((alert for alert in self.alerts if alert.alert_id == alert_id), None) 13 | 14 | def find_all(self) -> List[PriceAlert]: 15 | return self.alerts 16 | 17 | def delete(self, alert_id: str) -> bool: 18 | alert = self.find_by_id(alert_id) 19 | if alert: 20 | self.alerts.remove(alert) 21 | return True 22 | return False 23 | 24 | class InMemoryPriceRepository: 25 | def __init__(self): 26 | self.prices = [] 27 | 28 | def save(self, price: Price) -> None: 29 | self.prices.append(price) 30 | 31 | def find_by_product_id(self, product_id: str) -> List[Price]: 32 | return [price for price in self.prices if price.product_id == product_id] -------------------------------------------------------------------------------- /docs/CICD_implementation/protection.md: -------------------------------------------------------------------------------- 1 | # Branch Protection Rules for `main` 2 | 3 | This document explains why branch protection is enforced in this repository. 4 | 5 | ## **Why Protect `main`?** 6 | ✅ **Prevents Broken Code** – Ensures all changes are reviewed and tested before merging. 7 | ✅ **Enforces Code Quality** – Mandates PR reviews, reducing bugs and bad commits. 8 | ✅ **Blocks Direct Pushes** – No accidental/unreviewed changes bypassing CI. 9 | 10 | ## **Rules Applied** 11 | 1. **PR Review Required** – At least 1 approval before merging. 12 | 2. **CI Checks Must Pass** – Ensures tests/linting pass before merging. 13 | 3. **No Direct Pushes** – All changes must go through a PR. 14 | 15 | 🚀 **Result:** A stable, reliable `main` branch with fewer production issues! 16 | 17 | # .github/branch-protection-rules.yml 18 | rules: 19 | - name: Main Branch Protection 20 | branches: [main] 21 | required_status_checks: 22 | strict: true 23 | contexts: 24 | - test 25 | - security-scan 26 | - build 27 | required_pull_request_reviews: 28 | required_approving_review_count: 1 29 | dismiss_stale_reviews: true 30 | require_code_owner_reviews: false 31 | restrictions: null 32 | enforce_admins: false 33 | allow_force_pushes: false 34 | allow_deletions: false -------------------------------------------------------------------------------- /src/main/repositories/promotion/redis_promotion_repository.py: -------------------------------------------------------------------------------- 1 | from price_aggregator.main.models.promotion import Promotion 2 | from price_aggregator.main.repositories.promotion_repository import PromotionRepository 3 | from typing import List 4 | 5 | class RedisPromotionRepositoryStub(PromotionRepository): 6 | def __init__(self): 7 | self._redis = {} # Simulating a Redis database with a dictionary 8 | 9 | def save(self, promotion: Promotion) -> None: 10 | print(f"Saving promotion to Redis: {promotion.promotion_id}") # Added logging for debugging 11 | self._redis[promotion.promotion_id] = promotion # Storing promotion by its ID 12 | 13 | def find_by_id(self, promotion_id: str) -> Promotion: 14 | print(f"Finding promotion in Redis by ID: {promotion_id}") # Added logging 15 | return self._redis.get(promotion_id, None) # Return promotion or None if not found 16 | 17 | def find_all(self) -> List[Promotion]: 18 | return list(self._redis.values()) # Return all promotions in Redis 19 | 20 | def delete(self, promotion_id: str) -> None: 21 | print(f"Deleting promotion from Redis: {promotion_id}") # Added logging 22 | if promotion_id in self._redis: 23 | del self._redis[promotion_id] # Delete the promotion if it exists 24 | -------------------------------------------------------------------------------- /src/main/services/product_service.py: -------------------------------------------------------------------------------- 1 | # --- src/main/services/product_service.py --- 2 | from typing import Optional, List 3 | from price_aggregator.main.repositories.inmemory.inmemory_product_repository import InMemoryProductRepository 4 | from price_aggregator.main.models.product import Product 5 | 6 | class ProductService: 7 | def __init__(self, repository: InMemoryProductRepository = None): 8 | self.repository = repository or InMemoryProductRepository() 9 | 10 | def create_product(self, product_data: dict) -> Product: 11 | product = Product(**product_data) 12 | return self.repository.save(product) 13 | 14 | def get_product(self, product_id: str) -> Optional[Product]: 15 | return self.repository.find_by_id(product_id) 16 | 17 | def get_all_products(self) -> List[Product]: 18 | return self.repository.find_all() 19 | 20 | def update_product(self, product_id: str, update_data: dict) -> Optional[Product]: 21 | product = self.repository.find_by_id(product_id) 22 | if not product: 23 | return None 24 | for key, value in update_data.items(): 25 | setattr(product, key, value) 26 | return self.repository.update(product) 27 | 28 | def delete_product(self, product_id: str) -> bool: 29 | return self.repository.delete(product_id) 30 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.1.0] - 2025-05-04 4 | ### Added 5 | - Initial FastAPI setup with `/` root route. 6 | - `GET /products/{product_id}` endpoint returning mock product data. 7 | - Swagger UI (`/docs`) and OpenAPI spec (`/openapi.json`) available. 8 | - Product model using `pydantic.BaseModel`. 9 | - Project folder structure organized under `src/` with support for modular development. 10 | - `openapi.json` generated for documentation purposes and saved to `docs/openapi/`. 11 | 12 | ### Changed 13 | - Renamed `main.py` to `app.py` for proper FastAPI loading with Uvicorn. 14 | - Updated `uvicorn` launch command to reference `app:app`. 15 | 16 | ### Fixed 17 | - Resolved ASGI app loading error due to incorrect module path. 18 | - Fixed OpenAPI generation by ensuring server is running during `curl` operation. 19 | 20 | --- 21 | 22 | 23 | ## [v1.0.0] - 2025-04-20 24 | ### Added 25 | - Implemented Singleton design pattern 26 | - Added Factory Method with product and creator classes 27 | - Created Builder pattern for product reports 28 | - Developed Abstract Factory for retailer and product creation 29 | - Added unit tests for Singleton, Factory, Builder, and Abstract Factory 30 | 31 | ### Fixed 32 | - Fixed import issues in test_factory_method.py and test_builder.py 33 | - Updated PYTHONPATH for pytest compatibility 34 | - Improved test coverage and assertions 35 | 36 | ### Changed 37 | - Refactored Singleton to be thread-safe 38 | - Restructured project folder for clarity and testability 39 | 40 | ### Removed 41 | - Deprecated unused class `OldRetailerFactory` 42 | 43 | -------------------------------------------------------------------------------- /test_scrapers.py: -------------------------------------------------------------------------------- 1 | # Create test_scrapers.py in the root directory (Price-aggregator-comparison-app/) 2 | cat > test_scrapers.py << 'EOF' 3 | import sys 4 | import os 5 | 6 | # Add the src directory to the path so we can import the scrapers 7 | sys.path.append(os.path.join(os.path.dirname(__file__), 'src')) 8 | 9 | from scraper.checkers_scraper import get_products as get_checkers_products 10 | from scraper.picknpay_scraper import get_products as get_picknpay_products 11 | from scraper.woolworths_scraper import get_products as get_woolworths_products 12 | 13 | # Test Checkers 14 | print("=== Checkers Products ===") 15 | checkers_products = get_checkers_products("") 16 | print(f"Checkers found {len(checkers_products)} products") 17 | for name, price in list(checkers_products.items())[:3]: # Show first 3 18 | print(f" {name}: R{price}") 19 | 20 | print("\n=== Pick n Pay Products ===") 21 | picknpay_products = get_picknpay_products("") 22 | print(f"Pick n Pay found {len(picknpay_products)} products") 23 | for name, price in list(picknpay_products.items())[:3]: # Show first 3 24 | print(f" {name}: R{price}") 25 | 26 | print("\n=== Woolworths Products ===") 27 | woolworths_products = get_woolworths_products("") 28 | print(f"Woolworths found {len(woolworths_products)} products") 29 | for name, price in list(woolworths_products.items())[:3]: # Show first 3 30 | print(f" {name}: R{price}") 31 | 32 | # Test specific searches 33 | print("\n=== Testing Search for 'Coca-Cola' ===") 34 | coca_cola_checkers = get_checkers_products("Coca-Cola") 35 | print(f"Checkers 'Coca-Cola' results: {len(coca_cola_checkers)}") 36 | for name, price in coca_cola_checkers.items(): 37 | print(f" {name}: R{price}") 38 | EOF -------------------------------------------------------------------------------- /docs/diagrams/activity_diagrams/activity_diagrams.md: -------------------------------------------------------------------------------- 1 | # 📌 Activity Diagrams 2 | 3 | ## Objective 4 | This document presents UML activity diagrams for key workflows in the system. Each workflow is designed to enhance system efficiency, user experience, and stakeholder satisfaction. 5 | 6 | --- 7 | 8 | ### 🎯 Case: Price aggregator comparison system 9 | 10 | --- 11 | 12 | ## 📋 Available Activity Diagrams 13 | 14 | * ✅ [User Registration & Authentication](user_account.md) – Account creation and secure access 15 | * ✅ [Product Search](product.md) – Finding grocery items efficiently 16 | * ✅ [Price Alert Management](price_alert.md) – Setting up price drop notifications 17 | * ✅ [Retailer Price Updates](retailer_profile.md) – Ensuring accurate pricing data 18 | 19 | ## 🎯 Key Workflows Modeled 20 | 21 | ### User-Facing Workflows 22 | - **User Registration**: Creating and verifying new accounts 23 | - **Product Discovery**: Searching and filtering products 24 | - **Price Monitoring**: Setting up and managing price alerts 25 | - **List Management**: Saving and sharing shopping lists 26 | 27 | ### Retailer-Facing Workflows 28 | - **Price Updates**: Retailer dashboard access and price modifications 29 | - **Product Management**: Adding and updating product information 30 | 31 | ## 🔄 Workflow Integration 32 | 33 | These activity diagrams ensure: 34 | - **Smooth user interactions** across all system features 35 | - **Real-time data updates** for accurate pricing information 36 | - **Secure authentication** processes for all user types 37 | - **Efficient system operations** for both users and retailers 38 | 39 | --- 40 | 41 | * [Back to Diagrams Overview](../Traceability%20Matrix.md) 42 | * [Back to README](../../README.md) -------------------------------------------------------------------------------- /docs/kanban/kanban_explanation.md: -------------------------------------------------------------------------------- 1 | ## Defining Kanban Board 2 | 3 | Kanban boards are industry standard tools for agile teams, it's a visual tool that tracks tasks across stages. 4 | It helps teams manage workflows efficiently by breaking down tasks into manageable steps and ensuring transparency. 5 | * using coloumns to represent workflow stages 6 | * limits work-in-progress to prevent overload 7 | * provides rea-time visibility into task statuses 8 | * and enhances collaboration by clarifying responsibilities. 9 | 10 | ## My Kanban Board 11 | Visualising workflow: 12 | * The board consists of To Do, In Progress, Testing, Blocked, and Done columns, where each column represents a stage in the workflow. Team members can easily track task statuses, enhancing clarity, collaboration, and efficiency in project management. 13 | Limiting Work-in-Progress (WIP): 14 | * To prevent bottlenecks, WIP limits restrict the In Progress column to a maximum of 3 tasks, ensuring team members stay focused and avoid overload. This promotes task completion before new work begins, improving efficiency. The Blocked column highlights stalled tasks, allowing issues to be addressed without disrupting the overall workflow. 15 | Supporting Agile Principles: 16 | * The board promotes continuous delivery by ensuring tasks progress seamlessly from one stage to the next. It enhances adaptability, enabling priorities to be easily adjusted during sprint reviews. The Testing column aligns with Agile's iterative feedback loops, ensuring quality control before tasks move to completion, allowing for ongoing refinement and improvement. 17 | 18 | ### 🚀 By leveraging this structure, this Kanban board enhances productivity, transparency, and Agile efficiency. 19 | -------------------------------------------------------------------------------- /src/main/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from price_aggregator.main.user import router as user_router 3 | from price_aggregator.main.product import router as product_router 4 | from price_aggregator.main.retailer import router as retailer_router 5 | 6 | # Import API routers 7 | try: 8 | from price_aggregator.main.api.price_api import router as price_router 9 | except ImportError: 10 | # Create a placeholder if the module doesn't exist 11 | from fastapi import APIRouter 12 | price_router = APIRouter(prefix="/prices", tags=["prices"]) 13 | 14 | try: 15 | from price_aggregator.main.api.product_api import router as product_api_router 16 | except ImportError: 17 | from fastapi import APIRouter 18 | product_api_router = APIRouter(prefix="/api/products", tags=["products-api"]) 19 | 20 | try: 21 | from price_aggregator.main.api.retailer_api import router as retailer_api_router 22 | except ImportError: 23 | from fastapi import APIRouter 24 | retailer_api_router = APIRouter(prefix="/api/retailers", tags=["retailers-api"]) 25 | 26 | def main(): 27 | app = FastAPI( 28 | title="Price Aggregator API", 29 | description="A price aggregator and comparison system", 30 | version="0.1.0" 31 | ) 32 | 33 | # Include routers 34 | app.include_router(user_router) 35 | app.include_router(product_router) 36 | app.include_router(retailer_router) 37 | app.include_router(price_router) 38 | app.include_router(product_api_router) 39 | app.include_router(retailer_api_router) 40 | 41 | return app 42 | 43 | # For running directly 44 | if __name__ == "__main__": 45 | import uvicorn 46 | app = main() 47 | uvicorn.run(app, host="0.0.0.0", port=8000) -------------------------------------------------------------------------------- /docs/diagrams/state_transition_diagrams/state_transition_diagrams.md: -------------------------------------------------------------------------------- 1 | # 📌 State Transition Diagrams 2 | 3 | ## Objective 4 | Model the dynamic behavior of your grocery price aggregator application using state transition diagrams (object state modeling). These diagrams refine the system's interactions and prepare the application for detailed design and implementation. 5 | 6 | --- 7 | 8 | ### 🎯 Case: Price aggregator comparison system 9 | 10 | --- 11 | 12 | ## 🔑 Critical Objects Identified: 13 | 14 | 1. [User Account](user_account.md) 15 | 2. [Product](product.md) 16 | 3. [Price Alert](price_alert.md) 17 | 4. [Retailer Profile](retailer_profile.md) 18 | 19 | ## 🏗️ System State Architecture 20 | 21 | ### Core Domain Objects 22 | - **User Management**: Account lifecycle and authentication states 23 | - **Product Catalog**: Product approval and availability states 24 | - **Price Monitoring**: Alert subscription and notification states 25 | - **Retailer Management**: Retailer verification and access states 26 | 27 | ### State Transition Principles 28 | - **Clear State Boundaries**: Each object has well-defined states 29 | - **Controlled Transitions**: State changes follow business rules 30 | - **Audit Trail**: All transitions are traceable and logged 31 | - **Error Handling**: Invalid state transitions are prevented 32 | 33 | ## 📈 Business Value 34 | 35 | These state diagrams provide: 36 | - **System Reliability**: Prevents invalid object states 37 | - **User Experience**: Clear feedback on system status 38 | - **Maintainability**: Well-defined state boundaries 39 | - **Scalability**: Structured approach to state management 40 | 41 | --- 42 | 43 | * [Back to Diagrams Overview](../Traceability%20Matrix.md) 44 | * [Back to README](../../README.md) -------------------------------------------------------------------------------- /src/main/models/retailer.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import List 3 | from price_aggregator.main.models.product import Product # Assuming you have a Product class 4 | 5 | class Retailer(BaseModel): 6 | """ 7 | Pydantic model representing a retailer with validation. 8 | Includes business logic methods for product management. 9 | """ 10 | retailer_id: str 11 | name: str 12 | contact_info: str # Changed to str to be more flexible than just email 13 | products: List[Product] = [] # Initialize empty product list 14 | 15 | def add_product(self, product: Product) -> None: 16 | """ 17 | Add a product to the retailer's inventory 18 | """ 19 | self.products.append(product) 20 | # In a real application, you might want to log this properly 21 | print(f"Product {product.name} added by {self.name}.") 22 | 23 | def remove_product(self, product: Product) -> bool: 24 | """ 25 | Remove a product from the retailer's inventory 26 | Returns True if product was found and removed, False otherwise 27 | """ 28 | if product in self.products: 29 | self.products.remove(product) 30 | print(f"Product {product.name} removed by {self.name}.") 31 | return True 32 | 33 | print(f"Product {product.name} not found in retailer's inventory.") 34 | return False 35 | 36 | class Config: 37 | # Pydantic configuration 38 | json_schema_extra = { 39 | "example": { 40 | "retailer_id": "retailer123", 41 | "name": "Awesome Store", 42 | "contact_info": "contact@awesomestore.com", 43 | "products": [] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/services/retailer_service.py: -------------------------------------------------------------------------------- 1 | # --- src/main/services/retailer_service.py --- 2 | from typing import Optional, List 3 | from price_aggregator.main.models.retailer import Retailer 4 | from price_aggregator.main.repositories.inmemory.inmemory_retailer_repository import InMemoryRetailerRepository 5 | 6 | class RetailerService: 7 | def __init__(self, repository: Optional[InMemoryRetailerRepository] = None): 8 | self.repository = repository or InMemoryRetailerRepository() 9 | 10 | def create_retailer(self, retailer_data: dict) -> Retailer: 11 | retailer = Retailer(**retailer_data) 12 | return self.repository.save(retailer) 13 | 14 | def get_retailer(self, retailer_id: str) -> Optional[Retailer]: 15 | return self.repository.find_by_id(retailer_id) 16 | 17 | def get_all_retailers(self) -> List[Retailer]: 18 | return self.repository.find_all() 19 | 20 | def delete_retailer(self, retailer_id: str) -> bool: 21 | return self.repository.delete(retailer_id) 22 | 23 | def update_contact_info(self, retailer_id: str, new_contact: str) -> Optional[Retailer]: 24 | retailer = self.repository.find_by_id(retailer_id) 25 | if not retailer: 26 | return None 27 | retailer.contact_info = new_contact 28 | return self.repository.save(retailer) 29 | 30 | def update_retailer(self, retailer_id: str, updated_data: dict) -> Optional[Retailer]: 31 | retailer = self.repository.find_by_id(retailer_id) 32 | if not retailer: 33 | return None 34 | for key, value in updated_data.items(): 35 | if hasattr(retailer, key): 36 | setattr(retailer, key, value) 37 | return self.repository.save(retailer) 38 | 39 | def list_retailers(self) -> List[Retailer]: 40 | return self.repository.find_all() -------------------------------------------------------------------------------- /docs/diagrams/Traceability Matrix.md: -------------------------------------------------------------------------------- 1 | # 📌 Traceability Matrix 2 | 3 | --- 4 | 5 | ### 🎯 Case: Price aggregator comparison system 6 | 7 | --- 8 | 9 | This project models key system behaviors using **UML State Transition Diagrams** to support planning, design, and traceability. 10 | 11 | ## 📊 Diagrams Overview 12 | 13 | | Object | Activity Diagram | State Transition Diagram | 14 | |------------------|------------------|--------------------------| 15 | | User Account | [View](activity_diagrams/user_account.md) | [View](state_transition_diagrams/user_account.md) | 16 | | Product | [View](activity_diagrams/product.md) | [View](state_transition_diagrams/product.md) | 17 | | Price Alert | [View](activity_diagrams/price_alert.md) | [View](state_transition_diagrams/price_alert.md) | 18 | | Retailer Profile | [View](activity_diagrams/retailer_profile.md) | [View](state_transition_diagrams/retailer_profile.md) | 19 | 20 | ## 📌 Functional Mapping 21 | 22 | | Diagram/Object | Mapped Functional Requirement | User Story Reference | 23 | |----------------|--------------------------------|----------------------| 24 | | User Account | FR-001: User account management | US-01: "As a user, I want to sign up and manage my profile." | 25 | | Product | FR-002: Retailers submit/manage items | US-02: "As a retailer, I want to list and update product info." | 26 | | Price Alert | FR-006: Price drop alerts | US-05: "As a user, I want to subscribe to alerts for savings." | 27 | | Retailer Profile | FR-007: Dashboard permission control | US-06: "As a retailer, I need access to update prices." | 28 | 29 | ## 🔗 Related Documentation 30 | 31 | - [Activity Diagrams Overview](activity_diagrams/activity_diagrams.md) 32 | - [State Transition Diagrams Overview](state_transition_diagrams/state_transition_diagrams.md) 33 | - [Agile Planning Document](../../agile_planning/agile_planning_document.md) 34 | 35 | --- 36 | 37 | * [Back to README](../../README.md) -------------------------------------------------------------------------------- /docs/Reflection: -------------------------------------------------------------------------------- 1 | Reflection on Repository Improvement and Open-Source Collaboration 2 | 3 | Improving my repository involved focusing on clarity, modularity, and documentation to make it easier for others to understand and contribute. 4 | I structured the codebase with clear folder hierarchies and applied design patterns like Factory and Repository to ensure components were loosely 5 | coupled and easily extensible. I added detailed README sections, including feature descriptions, setup instructions, and contribution guidelines. 6 | This transparency helped lower the entry barrier for new contributors by providing a comprehensive overview of the project’s purpose and development process. 7 | I also integrated automated testing and continuous integration to maintain code quality and catch issues early, which is essential when multiple contributors are involved. 8 | 9 | One significant challenge in onboarding contributors was managing the balance between providing enough guidance and not overwhelming new developers. 10 | While detailed documentation is vital, too much technical jargon or incomplete explanations can deter newcomers. Additionally, coordinating contributions 11 | required establishing clear code standards and review processes to maintain consistency and prevent conflicts. 12 | 13 | Through this experience, I learned that open-source collaboration thrives on transparency, empathy, and structured processes. Clear contribution guidelines and a 14 | welcoming community are crucial for encouraging participation. I also understand the importance of incremental onboarding, such as starting contributors with small, 15 | manageable issues before progressing to complex features. Effective communication channels like issue trackers, discussion forums, or chat groups facilitate knowledge 16 | sharing and foster collaboration. Lastly, embracing diverse perspectives and being open to feedback enriches the project and promotes continuous improvement. 17 | These lessons will guide me in nurturing a sustainable and vibrant open-source community around my repository. 18 | -------------------------------------------------------------------------------- /docs/diagrams/activity_diagrams/user_account.md: -------------------------------------------------------------------------------- 1 | ### 📌 User Registration 2 | 3 | ```mermaid 4 | graph TD; 5 | A[Start] --> B[Enter User Details]; 6 | B --> C[Validate Input]; 7 | C -->|Valid| D[Create Account]; 8 | C -->|Invalid| E[Show Error Message]; 9 | D --> F[Send Confirmation Email]; 10 | F --> G[User Verifies Email]; 11 | G --> H[Account Activated]; 12 | H --> I[End]; 13 | ``` 14 | 15 | ### Explanation: 16 | - Ensures users enter valid information before creating an account. 17 | - Confirmation email step adds security and prevents fake accounts. 18 | 19 | --- 20 | 21 | ### 📌 User Login & Authentication 22 | 23 | ```mermaid 24 | graph TD; 25 | A[Start] --> B[Enter Username & Password]; 26 | B --> C[Validate Credentials]; 27 | C -->|Valid| D[Grant Access]; 28 | C -->|Invalid| E[Show Error Message]; 29 | D --> F[End]; 30 | ``` 31 | 32 | ### Explanation: 33 | - Basic authentication ensures secure access. 34 | - Error handling prevents unauthorized access. 35 | 36 | 37 | --- 38 | 39 | 40 | 41 | ### 📌 Filter & Sort Prices 42 | 43 | ```mermaid 44 | graph TD; 45 | A[Start] --> B[User Applies Filters]; 46 | B --> C[Sort by Price, Retailer, Rating]; 47 | C --> D[Update Results]; 48 | D --> E[End]; 49 | ``` 50 | 51 | ### Explanation: 52 | - Enhances usability by allowing filtering options. 53 | - Sorting improves decision-making. 54 | 55 | --- 56 | 57 | ### 📌 Save a Shopping List 58 | 59 | ```mermaid 60 | graph TD; 61 | A[Start] --> B[User Selects Items]; 62 | B --> C[Click Save List]; 63 | C --> D[Store List in User Profile]; 64 | D --> E[End]; 65 | ``` 66 | 67 | ### Explanation: 68 | - Allows users to track preferred products. 69 | - Saves data for future reference. 70 | 71 | --- 72 | 73 | ### 📌 Share a Shopping List 74 | 75 | ```mermaid 76 | graph TD; 77 | A[Start] --> B[User Selects Saved List]; 78 | B --> C[Click Share]; 79 | C --> D[Generate Shareable Link]; 80 | D --> E[Send via Email/Social Media]; 81 | E --> F[End]; 82 | ``` 83 | 84 | ### Explanation: 85 | - Helps users collaborate on grocery shopping. 86 | - Shareable links make distribution seamless. 87 | 88 | --- 89 | 90 | [Back to README.md](../../../README.md) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | pip-wheel-metadata/ 20 | share/python-wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # PyInstaller 27 | *.manifest 28 | *.spec 29 | 30 | # Installer logs 31 | pip-log.txt 32 | pip-delete-this-directory.txt 33 | 34 | # Unit test / coverage reports 35 | htmlcov/ 36 | .tox/ 37 | .nox/ 38 | .coverage 39 | .coverage.* 40 | .coverage* 41 | coverage.xml 42 | *.cover 43 | *.py,cover 44 | .hypothesis/ 45 | .pytest_cache/ 46 | 47 | # Jupyter Notebook 48 | .ipynb_checkpoints 49 | 50 | # IPython 51 | profile_default/ 52 | ipython_config.py 53 | 54 | # pyenv 55 | .python-version 56 | 57 | # celery 58 | celerybeat-schedule 59 | celerybeat.pid 60 | 61 | # SageMath parsed files 62 | *.sage.py 63 | 64 | # Environments 65 | .venv 66 | venv/ 67 | env/ 68 | ENV/ 69 | env.bak/ 70 | venv.bak/ 71 | 72 | # Spyder project settings 73 | .spyderproject 74 | .spyproject 75 | 76 | # Rope project settings 77 | .ropeproject 78 | 79 | # mkdocs documentation 80 | /site 81 | 82 | # mypy 83 | .mypy_cache/ 84 | .dmypy.json 85 | dmypy.json 86 | 87 | # IDE 88 | .vscode/ 89 | .idea/ 90 | *.swp 91 | *.swo 92 | *~ 93 | 94 | # OS 95 | .DS_Store 96 | .DS_Store? 97 | ._* 98 | .Spotlight-V100 99 | .Trashes 100 | ehthumbs.db 101 | Thumbs.db 102 | 103 | # Logs 104 | *.log 105 | logs/ 106 | 107 | # Environment variables 108 | .env 109 | .env.local 110 | .env.production 111 | .env.development 112 | 113 | # Database 114 | *.db 115 | *.sqlite 116 | *.sqlite3 117 | 118 | # Temporary files 119 | *.tmp 120 | *.temp 121 | 122 | # Documentation 123 | /docs/_build/ 124 | 125 | # macOS 126 | *.icloud 127 | 128 | # Windows 129 | [Tt]humbs.db 130 | Desktop.ini 131 | 132 | # Linux 133 | *~ 134 | 135 | # FastAPI/Uvicorn 136 | uvicorn.log 137 | 138 | # Docker 139 | Dockerfile.dev 140 | docker-compose.override.yml 141 | 142 | # SSL certificates 143 | *.key 144 | *.pem 145 | *.crt 146 | 147 | # Uploads 148 | /uploads/ 149 | /static/uploads/ -------------------------------------------------------------------------------- /docs/agile_planning/sprint_planning.md: -------------------------------------------------------------------------------- 1 | # 📌 Sprint Planning 2 | 3 | --- 4 | ### 🎯 Case: Price aggregator comparison system 5 | 6 | --- 7 | 8 | ## Sprint Goal Statement 9 | 10 | The sprint goal is to implement core product search, comparison engine, and price alert features to ensure users can 11 | quickly find, compare, and track products, while laying the groundwork for further features such as location-based 12 | deals and promotions. This sprint contributes directly to the MVP by establishing the essential functionalities for 13 | product discovery and price tracking, which are key to user engagement and satisfaction. 14 | 15 | --- 16 | 17 | ## Sprint Backlog Table 18 | 19 | | Story ID | User Story | Priority | Effort (Story Points) | Tasks | 20 | |----------|------------|----------|-----------------------|-------| 21 | | **FR-01** | As a user, I want to search for products so that I can quickly find the items I need. | Must-have | 3 | - Develop search API endpoint
- Design UI for search bar
- Implement search result page UI
- Integrate with product database | 22 | | **FR-02** | As a user, I want to compare product prices across multiple retailers so that I can choose the most affordable option. | Must-have | 5 | - Implement price comparison API
- Design comparison result UI
- Integrate with search results page
- Test data accuracy and pricing updates | 23 | | **FR-06** | As a user, I want to receive notifications when a product’s price drops so that I can purchase it at the best time. | Should-have | 3 | - Set up price tracking mechanism
- Implement notification system
- Design UI for alert settings
- Test notification delivery and timing | 24 | | **FR-04** | As a user, I want the system to suggest deals based on my location so that I can find the best prices nearby. | Should-have | 4 | - Integrate geolocation service
- Develop nearby deals API
- Update product search to filter based on location
- Design UI for location-based deals | 25 | 26 | --- 27 | 28 | ## Sprint Goal Justification 29 | 30 | This sprint focuses on delivering core functionalities for product search, price comparison, and price tracking, which 31 | are the most critical features for the MVP. By completing these stories, we lay the foundation for the app's basic user 32 | flow, allowing users to search for products, compare prices across retailers, and receive notifications about price 33 | drops—features that directly address the user's primary needs for making informed purchasing decisions. This sprint will 34 | enable the core value proposition of the system, bringing us closer to a fully functional MVP. 35 | 36 | --- 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/diagrams/state_transition_diagrams/user_account.md: -------------------------------------------------------------------------------- 1 | ### 📌 User Account 2 | ```mermaid 3 | graph TD; 4 | Created -->|Email Verified| Active 5 | Active -->|User Deactivates| Deactivated 6 | Active -->|Admin Suspends| Suspended 7 | Suspended -->|User Appeals| Active 8 | Deactivated -->|User Reactivates| Active 9 | ``` 10 | **Explanation:** 11 | - **Key states:** Created, Active, Suspended, Deactivated 12 | - **Transitions:** Email verification activates account; account can be suspended or deactivated by user or admin 13 | - **Mapping to Functional Requirements:** FR-001 (Account management and access control) 14 | 15 | --- 16 | 17 | ### 📌 Promotion 18 | ```mermaid 19 | graph TD; 20 | Created -->|Submitted for Approval| Pending 21 | Pending -->|Approved| Active 22 | Pending -->|Rejected| Rejected 23 | Active -->|Expires| Expired 24 | ``` 25 | **Explanation:** 26 | - **Key states:** Created, Pending, Active, Rejected, Expired 27 | - **Transitions:** Tied to campaign lifecycle and approval system 28 | - **Mapping to Functional Requirements:** FR-005 (Promotion and deal lifecycle) 29 | 30 | --- 31 | 32 | ### 📌 Saved List 33 | ```mermaid 34 | graph TD; 35 | Empty -->|User Adds Items| Active 36 | Active -->|User Shares| Shared 37 | Active -->|User Removes All Items| Empty 38 | Shared -->|User Edits| Active 39 | ``` 40 | **Explanation:** 41 | - **Key states:** Empty, Active, Shared 42 | - **Transitions:** Users manage and share product collections 43 | - **Mapping to Functional Requirements:** FR-006 (List creation and sharing for user convenience) 44 | 45 | --- 46 | 47 | ### 📌 Subscription 48 | ```mermaid 49 | graph TD; 50 | Free -->|User Upgrades| Premium 51 | Premium -->|Subscription Expires| Expired 52 | Expired -->|User Renews| Premium 53 | Premium -->|User Cancels| Canceled 54 | ``` 55 | **Explanation:** 56 | - **Key states:** Free, Premium, Expired, Canceled 57 | - **Transitions:** Managed by user actions and system triggers 58 | - **Mapping to Functional Requirements:** FR-007 (Subscription management and renewal process) 59 | 60 | --- 61 | 62 | ### 📌 Retailer Deal Submission 63 | ```mermaid 64 | graph TD; 65 | Draft -->|Submitted| UnderReview 66 | UnderReview -->|Approved| Published 67 | UnderReview -->|Rejected| Draft 68 | Published -->|Deal Expires| Archived 69 | ``` 70 | **Explanation:** 71 | - **Key states:** Draft, UnderReview, Published, Archived 72 | - **Transitions:** Admin oversight ensures quality and current info 73 | - **Mapping to Functional Requirements:** FR-008 (Retailer submissions and updates to deals) 74 | 75 | --- 76 | * [Back to State Transition Diagrams](../../State%20Transition%20Diagrams.md) 77 | * [Back to README](../../../README.md) -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # 🛣️ Price Content Aggregator – Product Roadmap 2 | 3 | This roadmap outlines the upcoming features and improvements planned for the Price Content Aggregator App. The goal is to help consumers in South Africa make smarter, cost-effective grocery shopping decisions through transparent price comparison and AI-powered insights. 4 | 5 | --- 6 | 7 | ## ✅ Completed 8 | 9 | - [x] Core product and store models 10 | - [x] REST API with FastAPI 11 | - [x] Basic product search and price comparison 12 | - [x] User authentication and saved lists 13 | - [x] Initial testing and documentation 14 | 15 | --- 16 | 17 | ## 🔨 In Progress 18 | 19 | - [ ] Geolocation support in product search 20 | - [ ] Admin panel for managing stores and products 21 | - [ ] Initial OpenAPI/Swagger documentation updates 22 | 23 | --- 24 | 25 | ## 🧠 Planned Features 26 | 27 | ### 📍 Location-Aware Features 28 | - [ ] Store filtering based on city/suburb or user location 29 | - [ ] Radius-based product search using GPS/geolocation 30 | - [ ] Display nearest stores and available deals 31 | 32 | ### 🔁 Price History & Trends 33 | - [ ] Track historical price changes for each product 34 | - [ ] Add endpoint: `/api/products/{id}/price-history` 35 | - [ ] Display price trend graphs in frontend (optional) 36 | 37 | ### 🛍️ Promotions & Deals 38 | - [ ] Daily Deals API endpoint 39 | - [ ] Add deal expiry dates and highlighting in UI 40 | - [ ] Support for multi-store promotion comparison 41 | 42 | ### 🔔 Notifications & Alerts 43 | - [ ] Price drop alerts (via email or push) 44 | - [ ] Custom price threshold notifications 45 | - [ ] Subscription to category or store deals 46 | 47 | ### 🤖 AI & Smart Suggestions 48 | - [ ] Personalized product recommendations 49 | - [ ] Best-value basket suggestions (based on cart items) 50 | - [ ] Predictive pricing trends 51 | 52 | --- 53 | 54 | ## 📦 Technical Enhancements 55 | 56 | - [ ] Implement PostgreSQL backend 57 | - [ ] Add Redis caching for frequent queries 58 | - [ ] CI/CD integration with GitHub Actions 59 | - [ ] Dockerize the app for easy deployment 60 | - [ ] Add Sentry for error tracking and monitoring 61 | 62 | --- 63 | 64 | ## 🧪 Developer Experience 65 | 66 | - [ ] Improve setup scripts and Makefile coverage 67 | - [ ] Add more unit and integration tests 68 | - [ ] Interactive API documentation (Swagger & ReDoc) 69 | - [ ] Contribution badges and GitHub templates 70 | 71 | --- 72 | 73 | ## 💬 Have Suggestions? 74 | 75 | We welcome feature ideas and feedback from the community! 76 | Feel free to [open an issue](https://github.com/your-repo/issues/new?template=feature_request.md) or start a [GitHub Discussion](https://github.com/your-repo/discussions). 77 | 78 | --- 79 | 80 | *Let’s build smarter shopping tools—together.* 🛒✨ 81 | -------------------------------------------------------------------------------- /docs/domain_model/class_diagram_updated.js: -------------------------------------------------------------------------------- 1 | classDiagram 2 | class User { 3 | +String userId 4 | +String name 5 | +String email 6 | +String password 7 | +login() 8 | +logout() 9 | } 10 | 11 | class Product { 12 | +String productId 13 | +String name 14 | +double price 15 | +String description 16 | +addProduct() 17 | +removeProduct() 18 | } 19 | 20 | class PriceAlert { 21 | +String alertId 22 | +double priceThreshold 23 | +Boolean isActive 24 | +subscribe() 25 | +unsubscribe() 26 | } 27 | 28 | class Retailer { 29 | +String retailerId 30 | +String name 31 | +String contactInfo 32 | +addProduct() 33 | +removeProduct() 34 | } 35 | 36 | class SavedList { 37 | +String listId 38 | +String userId 39 | +addProductToList() 40 | +removeProductFromList() 41 | } 42 | 43 | class Promotion { 44 | +String promotionId 45 | +String description 46 | +double discount 47 | +applyPromotion() 48 | } 49 | 50 | class RepositoryFactory { 51 | +get_promotion_repository(storage_type: String) 52 | +get_product_repository(storage_type: String) 53 | } 54 | 55 | class PromotionRepository { 56 | +save(promotion: Promotion) 57 | +find_by_id(promotion_id: String) 58 | +find_all() 59 | +delete(promotion_id: String) 60 | } 61 | 62 | class ProductRepository { 63 | +save(product: Product) 64 | +find_by_id(product_id: String) 65 | +find_all() 66 | +delete(product_id: String) 67 | } 68 | 69 | class FileSystemPromotionRepositoryStub { 70 | +save(promotion: Promotion) 71 | +find_by_id(promotion_id: String) 72 | +find_all() 73 | +delete(promotion_id: String) 74 | } 75 | 76 | class DatabasePromotionRepositoryStub { 77 | +save(promotion: Promotion) 78 | +find_by_id(promotion_id: String) 79 | +find_all() 80 | +delete(promotion_id: String) 81 | } 82 | 83 | class RedisPromotionRepositoryStub { 84 | +save(promotion: Promotion) 85 | +find_by_id(promotion_id: String) 86 | +find_all() 87 | +delete(promotion_id: String) 88 | } 89 | 90 | User "1" -- "0..*" PriceAlert : subscribes 91 | User "1" -- "0..*" SavedList : saves 92 | Retailer "1" -- "0..*" Product : lists 93 | User "1" -- "0..*" Product : views 94 | Product "1" -- "0..*" Promotion : has 95 | SavedList "1" -- "0..*" Product : contains 96 | RepositoryFactory "1" -- "0..*" PromotionRepository : creates 97 | RepositoryFactory "1" -- "0..*" ProductRepository : creates 98 | PromotionRepository "1" -- "0..*" FileSystemPromotionRepositoryStub : implements 99 | PromotionRepository "1" -- "0..*" DatabasePromotionRepositoryStub : implements 100 | PromotionRepository "1" -- "0..*" RedisPromotionRepositoryStub : implements 101 | ProductRepository "1" -- "0..*" FileSystemProductRepositoryStub : implements 102 | ProductRepository "1" -- "0..*" DatabaseProductRepositoryStub : implements 103 | ProductRepository "1" -- "0..*" RedisProductRepositoryStub : implements 104 | -------------------------------------------------------------------------------- /docs/domain_model/explanation_of_the_class_diagram.md: -------------------------------------------------------------------------------- 1 | ## Explanation of the Class Diagram 2 | 3 | --- 4 | 5 | ### 🎯 Case: Price aggregator comparison system 6 | 7 | --- 8 | 9 | ### Classes: 10 | - **User**: Represents a user of the system. Attributes include user ID, name, email, and password, with methods for logging in and logging out. 11 | - **Product**: Represents a product listed by a retailer. Attributes include product ID, name, price, and description. Methods include adding or removing products. 12 | - **PriceAlert**: Represents a price alert set by a user for a specific product. Attributes include alert ID, price threshold, and whether the alert is active. 13 | - **Retailer**: Represents a retailer who lists products. Attributes include retailer ID, name, and contact information. Methods include adding and removing products. 14 | - **SavedList**: Represents a list where a user can save products for future reference. Attributes include list ID and user ID, with methods for adding/removing products. 15 | - **Promotion**: Represents a promotional offer for a product. Includes attributes for promotion ID, description, and discount, with methods to apply promotions. 16 | 17 | ### Relationships: 18 | - **User to PriceAlert**: A user can subscribe to multiple price alerts, but each alert is associated with one user `(1 -- 0..*)`. 19 | - **User to SavedList**: A user can save multiple products in a saved list `(1 -- 0..*)`. 20 | - **Retailer to Product**: A retailer can list multiple products, but each product is associated with only one retailer `(1 -- 0..*)`. 21 | - **User to Product**: A user can view multiple products, and products can be viewed by many users `(1 -- 0..*)`. 22 | - **Product to Promotion**: A product can have multiple promotions applied, and each promotion can apply to multiple products `(1 -- 0..*)`. 23 | - **SavedList to Product**: A saved list can contain multiple products, and each product can appear in multiple lists `(1 -- 0..*)`. 24 | 25 | ### Key Design Decisions: 26 | 27 | - **Separation of Concerns**: Each class has clear responsibilities. For example, the User class handles user-related actions, while the Product class focuses on product details. 28 | - **Aggregation vs. Association**: The relationships between Product and Promotion, and SavedList and Product, are modeled as associations, reflecting that one can exist without the other but can be related. 29 | - **Multiplicity**: The design uses multiplicity to show how entities are related to each other. For instance, one user can save many lists, and a product can have multiple promotions associated with it. 30 | - **Extensibility**: The system allows for scalability. For instance, the PriceAlert class and Promotion class could be expanded to handle additional features like expiration dates or additional attributes for discounts. 31 | 32 | 33 | This class diagram effectively models the system, keeping it modular and adaptable for future changes. 34 | 35 | --- 36 | 37 | * [View Class Diagram](class_diagram.js) 38 | * [View Domain Model Documentation](domain_model_documentation.md) 39 | -------------------------------------------------------------------------------- /docs/agile_planning/backlog.md: -------------------------------------------------------------------------------- 1 | # 📌 Backlog 2 | 3 | --- 4 | ### 🎯 Case: Price aggregator comparison system 5 | 6 | --- 7 | 8 | ## Overview 9 | 10 | The product backlog is a prioritized list of features and tasks essential for building the system, including both 11 | functional and non-functional requirements. It is organized using the MoSCoW prioritization method, focusing first on 12 | critical features (Must-have) and then on enhancements (Should-have, Could-have). Each backlog item is defined as a user 13 | story with clear acceptance criteria and effort estimates. The backlog will evolve iteratively, adapting to stakeholder 14 | feedback and project needs, ensuring that the system is developed efficiently with the most valuable features delivered first. 15 | 16 | --- 17 | 18 | ## Product Backlog Table 19 | | Story ID | User Story | Priority | Effort (Story Points) | 20 | |----------|-----------|----------|-----------------------| 21 | | **FR-01** | As a user, I want to search for products so that I can quickly find the items I need. | Must-have | 3 | 22 | | **FR-02** | As a user, I want to compare product prices across multiple retailers so that I can choose the most affordable option. | Must-have | 5 | 23 | | **FR-03** | As a user, I want to filter products by price, location, and retailer so that I can refine my search results. | Must-have | 4 | 24 | | **FR-04** | As a user, I want the system to suggest deals based on my location so that I can find the best prices nearby. | Should-have | 4 | 25 | | **FR-05** | As a user, I want to see daily promotions and discounts so that I can take advantage of the best deals. | Should-have | 3 | 26 | | **FR-06** | As a user, I want to receive notifications when a product’s price drops so that I can purchase it at the best time. | Should-have | 3 | 27 | | **FR-07** | As a retailer, I want to update my product prices in real time so that customers see accurate pricing information. | Must-have | 4 | 28 | | **FR-08** | As an advertiser, I want to publish promotions on the platform so that I can reach potential customers more effectively. | Could-have | 3 | 29 | | **FR-09** | As a user, I want to save my shopping list or share it via messaging apps so that I can keep track of my planned purchases. | Could-have | 2 | 30 | | **FR-10** | As a user, I want to leave reviews and ratings for retailers so that I can help others make informed decisions. | Won’t-have (for now) | 3 | 31 | 32 | 33 | --- 34 | ## Justification for Prioritization 35 | 36 | * Must-have stories are core functionalities that directly address stakeholder needs, ensuring usability and accuracy. 37 | * Should-have stories enhance the user experience but are not immediately critical. These features improve engagement and retention. 38 | * Could-have stories add convenience but can be deferred without affecting core functionality. 39 | * Won’t-have stories are not in the initial scope but may be considered for future iterations. 40 | 41 | This backlog ensures that essential features are developed first, while additional improvements can be implemented iteratively. 42 | 43 | --- 44 | 45 | -------------------------------------------------------------------------------- /docs/specification/stakeholder_analysis.md: -------------------------------------------------------------------------------- 1 | ## 📌 Stakeholder Analysis Overview 2 | This document identifies key stakeholders, outlining their roles, concerns, pain points, and success metrics. As the foundation for system alignment, it ensures that both business and user needs are met. 3 | 4 | ## 📌 Stakeholder Analysis 5 | 6 | --- 7 | | **Stakeholder** | **Role** | **Key Concerns** | **Pain Points** | **Success Metrics** | 8 | |-------------------------------------|-----------------|--------------------|------------------------|-------------------------| 9 | | **Shoppers** | People who use the application to compare prices to save money | - Price accuracy & real-time updates
- Easy search and navigation
- Mobile-friendly UI | - Outdated prices
- Limited store coverage
- Poor user experience | - High user engagement
- Increased cost savings
- Positive reviews | 10 | | **Data Providers (APIs / Web Scrapers)** | Supply periodic price updates | - Data reliability & freshness
- Compliance with store policies
- API uptime | - Blocked access due to scraping
- Inconsistent data sources | - High data accuracy
- Reliable API connections | 11 | | **Developers & IT Team** | Build, maintain, and update the system | - System scalability
- Data integration challenges
- Cybersecurity risks
- Backup servers and maintenance | - Managing high traffic
- Changing store APIs
- Legal issues with data collection | - System uptime & performance
- Secure data transactions | 12 | | **Advertisers & Partners** | Promotes products and offer targeted deals | - Effective ad placement
- User engagement & conversions | - Low visibility of deals
- Limited targeting options | - High ad click-through rates
- Increased promotional sales | 13 | | **Investors / Business Owners** | Fund and oversee platform growth | - Return on investment (ROI)
- Monetization strategy
- Scalability | - Slow user adoption
- Revenue generation challenges | - Revenue growth
- Increased customer base | 14 | | **Regulatory Authorities** | Ensure compliance with data privacy & consumer protection laws | - Compliance with data protection laws | - Data privacy violations
- Risk of regulatory fines | - 100% legal compliance
- No legal actions or fines | 15 | | **Customer Support** | Assist users with issues related to the platform | - Efficient ticket resolution
- Clear issue escalation
- Maintaining a positive user experience | - High ticket volume
- Slow response times
- User frustration due to unresolved issues | - High customer satisfaction ratings
- Reduced complaint rates | 16 | --- 17 | -------------------------------------------------------------------------------- /docs/specification/specification.md: -------------------------------------------------------------------------------- 1 | # Price aggregator comparison app 2 | ### Introduction 3 | 4 | The system architecture is designed to support the core objective of the Grocery Price Aggregator App: to collect, process, analyse, and present grocery price information from multiple South African retailers in an accurate and user-friendly way. The architecture embraces modularity, loose coupling, scalability, and future extensibility—ensuring that the solution can grow from a prototype into a production-ready ecosystem. 5 | 6 | The conceptual design follows standard architectural viewpoints: logical architecture, data architecture, component architecture, integration architecture, and deployment architecture. 7 | 8 | ### A pricing aggregator and comparison application that aims to consolidate information from major grocery stores, allowing users find the best deals on food and beverages in a central location. 9 | **Domain: Grocery stores**
10 | This project focuses on grocery stores, specifically analysing food prices between shops. 11 | The purpose is to provide a tool that compares grocery prices to find the most affordable options. By collecting price data from various stores, the project aims to help individuals save money on essential goods. 12 | 13 | **Problem Statement:**
14 | South Africa has seen the impact of inflation on grocery prices for everyday consumers. (Abdul-Aziz & Alagidede, 2020) Prices normally vary from store to store, with occasional discounts and promotional opportunities being missed due to information not being readily available. The lower and middle income households tend to carry the burden of making informed decisions when purchasing essential goods, without a centralized system that allows for insight and price comparison. 15 |

16 | The system aims to consolidate information in one location, so consumers are given the opportunity to make informed decisions to efficiently manage their household budgets. It will ensure users are presented with up-to-date information and don't miss out on potential savings. 17 | 18 | **Individual Scope:**
19 | My role in this project would be to develop the core functionality of the price comparison app. This includes but is not limited to: 20 | * Develop or configure a server-side application to manage data retrieval, storage, and processing 21 | * Creating a user-friendly interface that display shop specials and price comparisons 22 | * Testing and Quality Assurance to ensure functionality 23 | * User acceptance testing: Gather feedback from real users to improve usability and address any concerns 24 | 25 | #### Feasibility justification
26 | 27 | The project is feasible as there is a clear demand for a system that enables consumers to compare grocery prices and take advantage of promotions. The necessary technologies already exist, and the required data is readily accessible. Data collection can be achieved through various methods, including APIs (where supported by stores), web scraping, partnerships, or manual entry. The system can be developed using widely adopted programming frameworks, ensuring scalability and maintainability. A structured development approach will be followed, starting with a Minimum Viable Product (MVP) and iterating based on user feedback to refine and enhance the system over time. 28 | 29 | #### **References** 30 | 31 | Abdul-Aziz, I. & Alagidede, I. P., 2020. Monetary policy and food inflation in South Africa: A quantile regression analysis. Food Policy, 91 , art. no. 101816 ed. s.l.:Elsevier. 32 | 33 | **Resources** 34 | * C4 Model Guide 35 | * Mermaid Guide and Tutorial for System design Components 36 | * Chat GPT -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Price Aggregator Comparison App 2 | 3 | Thank you for considering contributing to the Price Content Aggregator! 🎉 4 | This project is built to help consumers make smarter grocery decisions by comparing prices across stores in South Africa. Your help makes it better! 5 | 6 | --- 7 | ## Contributing 8 | 1. Fork this repository. 9 | 2. Pick an issue labeled `good-first-issue`. 10 | 3. Write code and tests. 11 | 4. Submit a PR with a clear description. 12 | 13 | --- 14 | 15 | ## 📦 Project Setup 16 | 17 | ### ✅ Prerequisites 18 | 19 | Ensure you have the following installed: 20 | - Python 3.10+ 21 | - [Poetry](https://python-poetry.org/docs/#installation) (for dependency and virtualenv management) 22 | - Git 23 | - Make (optional, for automation) 24 | 25 | ### 🔧 Installation 26 | 27 | 1. Fork this repository and clone it to your local machine: 28 | ```bash 29 | git clone https://github.com/your-username/price-aggregator.git 30 | cd price-aggregator 31 | ``` 32 | 33 | 2. Set up the project environment: 34 | 35 | ```bash 36 | poetry install 37 | ``` 38 | 39 | 3. Activate the virtual environment: 40 | 41 | ```bash 42 | poetry shell 43 | ``` 44 | 45 | 4. Run initial setup and migrations (if any): 46 | 47 | ```bash 48 | make setup # Optional if Makefile is provided 49 | ``` 50 | 51 | 5. Start the development server: 52 | 53 | ```bash 54 | uvicorn app.main:app --reload 55 | ``` 56 | --- 57 | ### 🧪 Coding Standards 58 | ✅ Linting & Formatting 59 | 60 | We follow PEP8 and use: 61 | 62 | Black (for formatting) 63 | 64 | Flake8 (for linting) 65 | 66 | isort (for import sorting) 67 | 68 | Run lint checks before committing: 69 | 70 | make lint 71 | 72 | Auto-format the codebase: 73 | 74 | make format 75 | --- 76 | ### 🧪 Testing 77 | 78 | All tests are written using pytest. Please write tests for any new features or bug fixes. 79 | 80 | To run tests: 81 | 82 | make test 83 | 84 | You can also run: 85 | 86 | pytest 87 | --- 88 | ### 🧩 Picking Issues 89 | 90 | Check the Issues tab. 91 | 92 | Look for labels like: 93 | 94 | good first issue – great for new contributors 95 | 96 | help wanted – active areas of need 97 | 98 | Comment on the issue to express interest. 99 | 100 | Wait for a maintainer to assign it to you before starting work. 101 | 102 | --- 103 | 🚀 Submitting Pull Requests 104 | 105 | Fork the repo and create a new branch: 106 | 107 | git checkout -b feature/my-awesome-feature 108 | 109 | Follow the commit message style: 110 | 111 | feat: add user authentication 112 | fix: correct store name sorting 113 | 114 | Push your branch: 115 | 116 | git push origin feature/my-awesome-feature 117 | 118 | Open a Pull Request (PR) against the main branch. 119 | 120 | Your PR should include: 121 | 122 | A clear title and description 123 | 124 | Screenshots if UI-related 125 | 126 | A link to the issue it resolves (e.g. Closes #42) 127 | 128 | Confirmation that linting and tests pass 129 | 130 | A maintainer will review and provide feedback or merge. 131 | --- 132 | ### 💬 Communication 133 | 134 | For general questions or ideas, open a discussion. 135 | 136 | For bugs or feature requests, open a GitHub Issue. 137 | --- 138 | ### Thank you for contributing! ❤️ 139 | Let’s make smart shopping easier together. 140 | 141 | 142 | Let me know if you'd like to add badges, CI/CD contribution guidelines, or database configuration instructions as well. 143 | 144 | -------------------------------------------------------------------------------- /docs/kanban/template_analysis.md: -------------------------------------------------------------------------------- 1 | # 📌Template Analysis and Selection 2 | 3 | --- 4 | 5 | ### 🎯 Case: Price aggregator comparison system 6 | 7 | --- 8 | 9 | ## Goal Statement 10 | 11 | To implement an Agile-driven project management workflow using GitHub’s Board template, enabling efficient tracking of tasks, sprint planning, and automation to streamline the development of the grocery price aggregator system. 12 | 13 | This will ensure: 14 | ✅ Prioritized backlog management for structured development. 15 | ✅ Automated progress tracking to enhance workflow efficiency. 16 | ✅ Improved traceability and collaboration using GitHub Issues, Milestones, and Project Boards. 17 | 18 | --- 19 | 20 | | Template Name | Columns & Workflows | Automation Features | Agile Suitability | Best Use Case | 21 | |------------------------|----------------------------------------------------|--------------------------------------|------------------------|-----------------------------------| 22 | | **Basic Kanban** | To Do, In Progress, Done | ❌ No automation | ⭐⭐⭐ Medium | Simple task tracking | 23 | | **Automated Kanban** | To Do, In Progress, Done (auto-updates) | ✅ Auto-moves issues based on status | ⭐⭐⭐⭐ High | Continuous work tracking | 24 | | **Bug Triage** | New, High Priority, Low Priority, Closed | ❌ No automation | ⭐ Low | Bug tracking | 25 | | **Sprint Board** | Backlog, Current Sprint, In Progress, Review, Done | ✅ Auto-tracking of sprint progress | ⭐⭐⭐⭐⭐ Very High | Agile sprint planning & execution | 26 | 27 | --- 28 | 29 | ## ✅ Justification: Automated Kanban 30 | 31 | The Automated Kanban template is the best fit for this project because it streamlines task management while maintaining Agile principles, offering automation that reduces manual effort. 32 | 33 | ### ✅ Supports Sprint Tracking: 34 | * Tasks automatically move between columns as their status updates (e.g., "To Do" → "In Progress" → "Done"), reducing administrative work. 35 | * Ideal for self-managed projects, where automating repetitive tracking tasks increases efficiency. 36 | 37 | ### ✅ Backlog Management: 38 | * New issues automatically land in "To Do," ensuring a structured workflow. 39 | * Keeps the product backlog visible and prioritized, making it easier to manage future sprints. 40 | 41 | ### ✅ Enhanced Traceability & Collaboration: 42 | * GitHub Issues link directly to project tasks, enabling clear documentation of work progress. 43 | * Ideal for solo development now, but scalable as more contributors join. 44 | 45 | ### ✅ Automation Features Reduce Overhead: 46 | * Auto-close completed issues when pull requests are merged. 47 | * Assign tasks dynamically, ensuring no critical work is lost in transition. 48 | 49 | ### Why Not Other Templates? 50 | |Template | Pros |Cons | 51 | |---------------|---------------------------------------------|------------------------------------------------| 52 | |Basic Kanban | Simple, customizable columns | No automation, manual task tracking | 53 | |Bug Triage | Great for debugging workflows | Focuses only on issue tracking, not development| 54 | |Team Planning | Good for large teams with structured roles | Overhead is unnecessary for solo work | 55 | 56 | --- 57 | 58 | ## 🚀 Final Verdict: 59 | The Automated Kanban template provides an efficient, low-maintenance Agile workflow, making it the best choice for managing the grocery price aggregator’s development while allowing room for scalability. 60 | 61 | --- 62 | 63 | -------------------------------------------------------------------------------- /docs/test_use_case_documentation/test_case_development.md: -------------------------------------------------------------------------------- 1 | # 📌 Test Case Documentation 2 | 3 | --- 4 | ### 🎯 Use Case: Price aggregator comparison system 5 | 6 | --- 7 | ## Overview 8 | 9 | This document outlines the test cases and scenarios for the Price Aggregator Comparison System to ensure its functionalities and performance meet the specified requirements. The functional test cases verify key system operations like product search, price comparison, subscription to alerts, and data updates, while the non-functional test scenarios assess system performance (e.g., handling 1,000 concurrent users) and security (e.g., secure access to the admin portal). Each test case includes clear steps, expected results, and success criteria to guide testing, ensuring the system meets both functional and performance standards. 10 | 11 | --- 12 | ## Functional Test Cases 13 | 14 | | Test Case ID | Requirement ID | Description | Steps | Expected Result | Actual Result | Status (Pass/Fail) | 15 | |--------------|----------------|--------------------------------------|-----------------------------------------------------------------------|-------------------------------------|---------------|--------------------| 16 | | TC-001 | FR-001 | User searches for a product | 1. Enter product name.
2. Click "Search." | Results display within 2 seconds. | | | 17 | | TC-002 | FR-002 | User compares prices | 1. Select a product.
2. Click "Compare Prices." | Price comparison chart displayed. | | | 18 | | TC-003 | FR-003 | User subscribes to price alerts | 1. Select a product.
2. Click "Subscribe to Alerts." | Confirmation message displayed. | | | 19 | | TC-004 | FR-004 | Retailer updates pricing | 1. Retailer logs in.
2. Upload pricing data. | Pricing updated successfully. | | | 20 | | TC-005 | FR-005 | Admin manages system data | 1. Admin logs in.
2. Resolve data inconsistencies. | Data accuracy confirmed. | | | 21 | | TC-006 | FR-006 | Advertiser publishes promotions | 1. Advertiser logs in.
2. Upload promotional data. | Promotions published successfully. | | | 22 | | TC-007 | FR-007 | Data provider syncs real-time data | 1. Data provider connects via API.
2. Sync pricing data. | Data synced successfully. | | | 23 | | TC-008 | FR-008 | User applies filters to search | 1. Enter search query.
2. Apply filters (e.g., price range). | Filtered results displayed. | | | 24 | 25 | --- 26 | 27 | ## Non-Functional Test Scenarios 28 | 29 | ### Performance Test 30 | - **Scenario**: Simulate 1,000 concurrent users searching for products. 31 | - **Steps**: 32 | 1. Use a load testing tool (e.g., JMeter) to simulate 1,000 users. 33 | 2. Execute search queries simultaneously. 34 | 3. Measure response time for each query. 35 | - **Expected Result**: Response time ≤ 2 seconds for 95% of queries. 36 | - **Actual Result**: [To be filled after testing] 37 | - **Status**: [Pass/Fail] 38 | 39 | ### Security Test 40 | - **Scenario**: Verify secure access to the admin portal. 41 | - **Steps**: 42 | 1. Attempt to access the admin portal without credentials. 43 | 2. Attempt to access with incorrect credentials. 44 | 3. Attempt to access with valid credentials. 45 | - **Expected Result**: 46 | - Access denied for unauthorized users. 47 | - Access granted for authorized users. 48 | - **Actual Result**: [To be filled after testing] 49 | - **Status**: [Pass/Fail] 50 | 51 | --- 52 | 53 | [Back to Test and Use Case Document.md](Test%20and%20Use%20Case%20Document.md) -------------------------------------------------------------------------------- /docs/test_use_case_documentation/use_case_diagrams.md: -------------------------------------------------------------------------------- 1 | # 📌 Use Case Diagram 2 | 3 | --- 4 | ### 🎯 Use Case: Price aggregator comparison system 5 | 6 | --- 7 | ## Overview 8 | 9 | This document presents the Use Case Diagram for the Price Aggregator Comparison System, outlining the key system interactions and stakeholders. It visualizes the roles and relationships of actors (User, Retailer, Admin, Data Provider, Advertiser) with various system use cases, such as searching for products, comparing prices, applying filters, and subscribing to notifications. 10 | 11 | The Actors & Roles section defines the specific responsibilities of each role, while the Relationships section explains generalizations, inclusions, and extensions between use cases, demonstrating how actions like price comparison and notifications build upon other core functionalities. 12 | 13 | Finally, the Addressing Stakeholder Concerns section highlights how the system addresses the needs of users, retailers, admins, data providers, and advertisers to ensure seamless interaction, data accuracy, and effective promotions. This diagram provides a clear structure for understanding user-system interactions and ensures stakeholder requirements are effectively met. 14 | 15 | --- 16 | 17 | ```mermaid 18 | graph TD; 19 | %% Define Actors 20 | User["🧑‍💻 User"] 21 | Retailer["🏪 Retailer"] 22 | Admin["🛠️ Admin"] 23 | DataProvider["🔗 Data Provider"] 24 | Advertiser["📢 Advertiser"] 25 | 26 | %% Define Use Cases 27 | A["Search for Products"] 28 | B["Compare Prices"] 29 | C["Apply Filters"] 30 | D["View Product Details"] 31 | E["Receive Price Drop Alerts"] 32 | F["Update Pricing"] 33 | G["Manage System Data"] 34 | H["Publish Promotions"] 35 | I["Subscribe to Notifications"] 36 | J["View Promotions"] 37 | K["Manage User Accounts"] 38 | 39 | %% Relationships 40 | User --> A 41 | User --> B 42 | User --> C 43 | User --> D 44 | User --> E 45 | User --> I 46 | User --> J 47 | 48 | Retailer --> F 49 | Retailer --> H 50 | 51 | Admin --> G 52 | Admin --> K 53 | 54 | DataProvider -->|Provides Data| F 55 | Advertiser -->|Posts Promotions| H 56 | 57 | %% Use Case Relationships 58 | B -->|includes| A 59 | E -->|includes| I 60 | C -->|extends| A 61 | H -->|extends| F 62 | ``` 63 | --- 64 | ### Actors & Roles 65 | - **User**: Searches for products, compares prices, applies filters, views product details, and subscribes to price alerts. A login option is provided to personalize the experience. 66 | - **Retailer**: Updates pricing and publishes promotional items to attract customers. 67 | - **Admin**: Manages system data, user accounts, and ensures data integrity. 68 | - **Data Provider**: Supplies pricing data to the system via APIs or manual uploads. 69 | - **Advertiser**: Publishes promotional deals, which are displayed to users via the system. 70 | --- 71 | ### Relationships 72 | 73 | **Generalization** 74 | - Admin is a specialized role that extends system management functions. 75 | - Both Retailers and Advertisers interact with the system but in different ways. 76 | 77 | **Inclusion** 78 | - “Compare Prices” ⟶ (includes) ⟶ “Search for Products” 79 | - “Receive Price Drop Alerts” ⟶ (includes) ⟶ “Subscribe to Notifications” 80 | 81 | **Extension** 82 | - “Apply Filters” ⟶ (extends) ⟶ “Search for Products” 83 | - “Publish Promotions” ⟶ (extends) ⟶ “Update Pricing” 84 | --- 85 | ### Addressing Stakeholder Concerns 86 | - **Users**: The system provides an intuitive interface with efficient search, comparison tools, and personalized alerts to enhance their experience. 87 | - **Retailers**: The system supports direct data integration via APIs or manual uploads, ensuring accurate and timely updates. 88 | - **Admins**: Role-based access control (RBAC) and monitoring tools are implemented to maintain data integrity and system performance. 89 | - **Data Providers**: The system integrates APIs and web scraping mechanisms to ensure seamless data collection. 90 | - **Advertisers**: The system highlights promotional deals through notifications and banners, ensuring maximum visibility. 91 | --- 92 | 93 | [Back to Test and Use Case Document.md](Test%20and%20Use%20Case%20Document.md) -------------------------------------------------------------------------------- /docs/domain_model/class_diagram.js: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | classDiagram 3 | class User { 4 | +String userId 5 | +String name 6 | +String email 7 | +String password 8 | +login() 9 | +logout() 10 | } 11 | 12 | class Product { 13 | +String productId 14 | +String name 15 | +double price 16 | +String description 17 | +addProduct() 18 | +removeProduct() 19 | } 20 | 21 | class PriceAlert { 22 | +String alertId 23 | +double priceThreshold 24 | +Boolean isActive 25 | +subscribe() 26 | +unsubscribe() 27 | } 28 | 29 | class Retailer { 30 | +String retailerId 31 | +String name 32 | +String contactInfo 33 | +addProduct() 34 | +removeProduct() 35 | } 36 | 37 | class SavedList { 38 | +String listId 39 | +String userId 40 | +addProductToList() 41 | +removeProductFromList() 42 | } 43 | 44 | User "1" -- "0..*" PriceAlert : subscribes 45 | User "1" -- "0..*" SavedList : saves 46 | Retailer "1" -- "0..*" Product : lists 47 | User "1" -- "0..*" Product : views 48 | 49 | class Promotion { 50 | +String promotionId 51 | +String description 52 | +double discount 53 | +applyPromotion() 54 | } 55 | 56 | Product "1" -- "0..*" Promotion : has 57 | SavedList "1" -- "0..*" Product : contains 58 | ``` 59 | /* 60 | Explanation of the Class Diagram 61 | 62 | Classes: 63 | - User: Represents a user of the system. Attributes include user ID, name, email, and password, with methods for logging 64 | in and logging out. 65 | - Product: Represents a product listed by a retailer. Attributes include product ID, name, price, and description. 66 | Methods include adding or removing products. 67 | - PriceAlert: Represents a price alert set by a user for a specific product. Attributes include alert ID, price threshold, 68 | and whether the alert is active. 69 | - Retailer: Represents a retailer who lists products. Attributes include retailer ID, name, and contact information. 70 | Methods include adding and removing products. 71 | - SavedList: Represents a list where a user can save products for future reference. Attributes include list ID and user 72 | ID, with methods for adding/removing products. 73 | - Promotion: Represents a promotional offer for a product. Includes attributes for promotion ID, description, and discount, 74 | with methods to apply promotions. 75 | 76 | Relationships: 77 | - User to PriceAlert: A user can subscribe to multiple price alerts, but each alert is associated with one user (1 -- 0..*). 78 | - User to SavedList: A user can save multiple products in a saved list (1 -- 0..*). 79 | - Retailer to Product: A retailer can list multiple products, but each product is associated with only one retailer (1 -- 0..*). 80 | - User to Product: A user can view multiple products, and products can be viewed by many users (1 -- 0..*). 81 | - Product to Promotion: A product can have multiple promotions applied, and each promotion can apply to multiple products (1 -- 0..*). 82 | - SavedList to Product: A saved list can contain multiple products, and each product can appear in multiple lists (1 -- 0..*). 83 | 84 | Key Design Decisions: 85 | - Separation of Concerns: Each class has clear responsibilities. For example, the User class handles user-related actions, 86 | while the Product class focuses on product details. 87 | - Aggregation vs. Association: The relationships between Product and Promotion, and SavedList and Product, are modeled 88 | as associations, reflecting that one can exist without the other but can be related. 89 | - Multiplicity: The design uses multiplicity to show how entities are related to each other. For instance, one user can 90 | save many lists, and a product can have multiple promotions associated with it. 91 | - Extensibility: The system allows for scalability. For instance, the PriceAlert class and Promotion class could be 92 | expanded to handle additional features like expiration dates or additional attributes for discounts. 93 | 94 | This class diagram effectively models the system, keeping it modular and adaptable for future changes. 95 | */ -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | name: CI/CD Pipeline 3 | 4 | on: 5 | push: 6 | branches: [ main, develop ] 7 | pull_request: 8 | branches: [ main ] 9 | release: 10 | types: [ published ] 11 | 12 | env: 13 | PYTHON_VERSION: '3.11' 14 | VENV_NAME: '.venv' 15 | 16 | jobs: 17 | test: 18 | name: Run Tests 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout Code 23 | uses: actions/checkout@v4 24 | ======= 25 | name: Python CI/CD 26 | 27 | on: 28 | push: 29 | branches: 30 | - main 31 | 32 | jobs: 33 | build-and-release: 34 | runs-on: ubuntu-latest 35 | 36 | steps: 37 | - name: Checkout code 38 | uses: actions/checkout@v3 39 | >>>>>>> 7623d6094a56a952ccafdde3660f8390f3cc8db0 40 | 41 | - name: Set up Python 42 | uses: actions/setup-python@v4 43 | with: 44 | <<<<<<< HEAD 45 | python-version: ${{ env.PYTHON_VERSION }} 46 | cache: 'pip' 47 | 48 | - name: Create Virtual Environment 49 | run: | 50 | python -m venv ${{ env.VENV_NAME }} 51 | echo "${{ env.VENV_NAME }}/bin" >> $GITHUB_PATH 52 | 53 | - name: Install Dependencies 54 | run: | 55 | pip install --upgrade pip 56 | pip install -r requirements.txt 57 | pip install pytest pytest-cov build 58 | 59 | - name: Run Tests with Coverage 60 | env: 61 | PYTHONPATH: '${{ github.workspace }}/src' 62 | run: | 63 | pytest --cov=src --cov-report=xml --cov-report=html 64 | 65 | - name: Upload Coverage Reports 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: coverage-report 69 | path: | 70 | htmlcov/ 71 | coverage.xml 72 | retention-days: 30 73 | 74 | security-scan: 75 | name: Security Scan 76 | runs-on: ubuntu-latest 77 | needs: test 78 | 79 | steps: 80 | - name: Checkout Code 81 | uses: actions/checkout@v4 82 | 83 | - name: Set up Python 84 | uses: actions/setup-python@v4 85 | with: 86 | python-version: ${{ env.PYTHON_VERSION }} 87 | 88 | - name: Install Security Scanner 89 | run: | 90 | pip install bandit safety 91 | 92 | - name: Run Bandit Security Scan 93 | run: | 94 | bandit -r src/ -f html -o bandit-report.html || true 95 | 96 | - name: Run Safety Check 97 | run: | 98 | safety check --json --output safety-report.json || true 99 | 100 | - name: Upload Security Reports 101 | uses: actions/upload-artifact@v4 102 | with: 103 | name: security-reports 104 | path: | 105 | bandit-report.html 106 | safety-report.json 107 | retention-days: 30 108 | 109 | build: 110 | name: Build Package 111 | runs-on: ubuntu-latest 112 | needs: [test, security-scan] 113 | 114 | steps: 115 | - name: Checkout Code 116 | uses: actions/checkout@v4 117 | 118 | - name: Set up Python 119 | uses: actions/setup-python@v4 120 | with: 121 | python-version: ${{ env.PYTHON_VERSION }} 122 | 123 | - name: Install Build Dependencies 124 | run: | 125 | pip install build twine 126 | 127 | - name: Build Package 128 | run: | 129 | python -m build 130 | 131 | - name: Verify Package 132 | run: | 133 | pip install twine 134 | twine check dist/* 135 | 136 | - name: Upload Build Artifacts 137 | uses: actions/upload-artifact@v4 138 | with: 139 | name: python-package 140 | path: | 141 | dist/ 142 | *.egg-info/ 143 | retention-days: 30 144 | 145 | release: 146 | name: Create Release 147 | runs-on: ubuntu-latest 148 | needs: build 149 | if: github.event_name == 'release' && github.event.action == 'published' 150 | 151 | steps: 152 | - name: Checkout Code 153 | uses: actions/checkout@v4 154 | 155 | - name: Download Build Artifacts 156 | uses: actions/download-artifact@v4 157 | with: 158 | name: python-package 159 | path: dist/ 160 | 161 | - name: Publish to PyPI 162 | env: 163 | TWINE_USERNAME: __token__ 164 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 165 | run: | 166 | pip install twine 167 | twine upload dist/* 168 | ======= 169 | python-version: '3.10' 170 | 171 | - name: Install build module 172 | run: | 173 | python -m pip install --upgrade pip 174 | pip install build 175 | 176 | - name: Build wheel package 177 | run: python -m build 178 | 179 | - name: Upload release artifact 180 | uses: actions/upload-artifact@v3.1.3 181 | with: 182 | name: python-wheel 183 | path: dist/*.whl 184 | >>>>>>> 7623d6094a56a952ccafdde3660f8390f3cc8db0 185 | -------------------------------------------------------------------------------- /docs/domain_model/domain_model_documentation.md: -------------------------------------------------------------------------------- 1 | # 🧩 Domain Model 2 | 3 | --- 4 | 5 | ### 🎯 Case: Price aggregator comparison system 6 | 7 | --- 8 | 9 | ## Objective 10 | 11 | This document presents a detailed domain model for the Grocery Price Aggregator app. It defines the core entities, their attributes and responsibilities, relationships, and business rules aligned with previous assignments (requirements, user stories, use cases, state/activity diagrams). 12 | 13 | --- 14 | 15 | ## 🏗️ Domain Architecture 16 | 17 | ### Core Entities 18 | - **User Management**: User, SavedList, PriceAlert 19 | - **Product Catalog**: Product, Promotion 20 | - **Retailer Management**: Retailer, AdvertisementCampaign 21 | - **Pricing System**: PriceEntry 22 | 23 | ### Domain Relationships 24 | ```mermaid 25 | graph TD 26 | User -->|creates| SavedList 27 | User -->|sets| PriceAlert 28 | Retailer -->|lists| Product 29 | Product -->|has| PriceEntry 30 | Retailer -->|manages| AdvertisementCampaign 31 | ``` 32 | 33 | ### 📋 Entity Specifications 34 | #### 1. User 35 | 36 | Attributes: userId, name, email, password, status 37 | Responsibilities: Authentication, profile management, alert management 38 | Business Rules: 39 | 40 | - Maximum 10 active price alerts per user 41 | 42 | - Email verification required for activation 43 | 44 | - Profile updates require re-authentication 45 | 46 | #### 2. Product 47 | 48 | Attributes: productId, name, price, description, category, status 49 | Responsibilities: Price comparison, retailer association 50 | Business Rules: 51 | 52 | - Products require retailer association 53 | 54 | - Price history maintained for 90 days 55 | 56 | - Products can be in multiple categories 57 | 58 | #### 3. PriceAlert 59 | 60 | Attributes: alertId, userId, productId, targetPrice, isActive 61 | Responsibilities: Price monitoring, user notification 62 | Business Rules: 63 | 64 | - Alerts trigger when price ≤ targetPrice 65 | 66 | - Users receive maximum 3 notifications per alert 67 | 68 | - Alerts expire after 30 days of inactivity 69 | 70 | #### 4. Retailer 71 | 72 | Attributes: retailerId, name, location, status, verificationStatus 73 | Responsibilities: Product management, price updates, campaign management 74 | Business Rules: 75 | 76 | - Retailers must be verified to list products 77 | 78 | - Price updates require authentication 79 | 80 | - Suspended retailers cannot modify listings 81 | 82 | #### 5. **PriceEntry** 83 | 84 | | Attribute | Type | Description | 85 | |-----------|------|-------------| 86 | | entryID | String | Unique ID for the price record | 87 | | price | Float | Price of the product | 88 | | productID | String | FK - Related product | 89 | | retailerID | String | FK - Retailer who listed it | 90 | | dateListed | Date | Timestamp of price entry | 91 | 92 | **Responsibilities (Methods):** 93 | - `updatePrice()` 94 | - `trackChanges()` 95 | 96 | #### 6. SavedList 97 | Attribute Type Description 98 | listID String Unique list ID 99 | userID String FK - List owner 100 | name String List name (e.g., "Weekly Groceries") 101 | 102 | Responsibilities (Methods): 103 | 104 | - addProduct() 105 | 106 | - removeProduct() 107 | 108 | - shareList() 109 | 110 | #### 7. AdvertisementCampaign 111 | Attribute Type Description 112 | campaignID String Unique campaign ID 113 | retailerID String FK - Associated retailer 114 | title String Campaign title 115 | status String Active, Rejected, Completed 116 | endDate Date Expiry date of campaign 117 | 118 | Responsibilities (Methods): 119 | 120 | - startCampaign() 121 | 122 | - endCampaign() 123 | 124 | - cancelCampaign() 125 | 126 | ### 🔗 Relationships Overview 127 | 128 | - A User can create many SavedLists 129 | 130 | - A Retailer can post multiple Products 131 | 132 | - A Product has many PriceEntries (from different retailers) 133 | 134 | - A User can set PriceAlerts for Products 135 | 136 | - A Retailer manages AdvertisementCampaigns 137 | 138 | ### 🔒 Business Rules Summary 139 | 140 | | Rule Category | Specific Rules | 141 | |---------------|----------------| 142 | | User Management | Email verification, alert limits, profile validation | 143 | | Product Management | Retailer association, price history, categorization | 144 | | Pricing System | Real-time updates, price validation, alert triggers | 145 | | Retailer Operations | Verification requirements, suspension handling | 146 | ### 📏 Additional Business Rules 147 | 148 | - A User can have a maximum of 10 active PriceAlerts 149 | 150 | - A Retailer must be active to publish Products or Campaigns 151 | 152 | - A PriceEntry must be linked to both a valid Product and Retailer 153 | 154 | - SavedLists are private by default but can be shared via URL 155 | 156 | - AdvertisementCampaigns must be reviewed before activation 157 | 158 | ### 🎯 Design Principles 159 | 160 | - Separation of Concerns: Clear entity responsibilities 161 | 162 | - Data Integrity: Validation at entity level 163 | 164 | - Scalability: Modular domain structure 165 | 166 | - Maintainability: Well-defined business rules 167 | 168 | ---- 169 | --- 170 | 171 | * [View Class Diagram](class_diagram.js) 172 | * [View Updated Class Diagram](class_diagram_updated.js) 173 | * [View Diagram Explanation](explanation_of_the_class_diagram.md) 174 | 175 | * [Back to README](../../README.md) 176 | -------------------------------------------------------------------------------- /src/main/run_scraper.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 5 | 6 | from scraper.checkers_scraper import get_products as get_checkers_products 7 | from scraper.picknpay_scraper import get_products as get_picknpay_products 8 | from scraper.woolworths_scraper import get_products as get_woolworths_products 9 | from scraper.checkers_scraper import get_products_by_category as get_checkers_by_category 10 | from scraper.picknpay_scraper import get_products_by_category as get_picknpay_by_category 11 | from scraper.woolworths_scraper import get_products_by_category as get_woolworths_by_category 12 | from scraper.checkers_scraper import get_categories as get_checkers_categories 13 | 14 | def merge_products(*stores): 15 | """Merge products from multiple stores into a single dictionary""" 16 | merged = {} 17 | for store_name, products in stores: 18 | for product_name, price in products.items(): 19 | if product_name not in merged: 20 | merged[product_name] = {} 21 | merged[product_name][store_name] = price 22 | return merged 23 | 24 | def display_comparison(merged_products): 25 | """Display price comparison and highlight cheapest store""" 26 | print("\n=== Price Comparison Across Stores ===\n") 27 | for product_name, store_prices in merged_products.items(): 28 | cheapest_store = min(store_prices, key=store_prices.get) 29 | for store, price in store_prices.items(): 30 | highlight = "✅" if store == cheapest_store else "" 31 | print(f"{store} → {product_name}: R{price:.2f} {highlight}") 32 | print("-" * 50) 33 | 34 | def list_categories(): 35 | """List all available categories""" 36 | categories = get_checkers_categories() # Assuming all stores have same categories 37 | print("\n=== Available Categories ===") 38 | for i, category in enumerate(categories, 1): 39 | print(f"{i}. {category}") 40 | print(f"\nUsage: python src/main/run_scraper.py --category 'Bakery'") 41 | return categories 42 | 43 | def search_by_category(category_name): 44 | """Search for products by category across all stores""" 45 | checkers_products = get_checkers_by_category(category_name) 46 | picknpay_products = get_picknpay_by_category(category_name) 47 | woolworths_products = get_woolworths_by_category(category_name) 48 | 49 | merged = merge_products( 50 | ("Checkers", checkers_products), 51 | ("Pick n Pay", picknpay_products), 52 | ("Woolworths", woolworths_products) 53 | ) 54 | 55 | return merged 56 | 57 | def main(): 58 | # Handle command line arguments 59 | if len(sys.argv) == 1: 60 | # No arguments - show all products 61 | print("📦 Showing all products:") 62 | checkers_products = get_checkers_products("") 63 | picknpay_products = get_picknpay_products("") 64 | woolworths_products = get_woolworths_products("") 65 | 66 | merged = merge_products( 67 | ("Checkers", checkers_products), 68 | ("Pick n Pay", picknpay_products), 69 | ("Woolworths", woolworths_products) 70 | ) 71 | 72 | elif len(sys.argv) == 2: 73 | # One argument - could be search term or category flag 74 | arg = sys.argv[1] 75 | 76 | if arg == "--categories" or arg == "-c": 77 | list_categories() 78 | return 79 | else: 80 | # Regular product search 81 | search_query = arg 82 | print(f"🔍 Searching for '{search_query}'...") 83 | 84 | checkers_products = get_checkers_products("") 85 | picknpay_products = get_picknpay_products("") 86 | woolworths_products = get_woolworths_products("") 87 | 88 | merged = merge_products( 89 | ("Checkers", checkers_products), 90 | ("Pick n Pay", picknpay_products), 91 | ("Woolworths", woolworths_products) 92 | ) 93 | 94 | # Filter by search term 95 | search_query_lower = search_query.lower() 96 | filtered_products = {} 97 | for product_name, prices in merged.items(): 98 | if search_query_lower in product_name.lower(): 99 | filtered_products[product_name] = prices 100 | 101 | merged = filtered_products 102 | 103 | if not merged: 104 | print(f"❌ No products found matching '{search_query}'") 105 | return 106 | 107 | elif len(sys.argv) == 3: 108 | # Two arguments - check if it's a category search 109 | if sys.argv[1] == "--category" or sys.argv[1] == "-cat": 110 | category_name = sys.argv[2] 111 | print(f"🛒 Showing products in category: '{category_name}'") 112 | merged = search_by_category(category_name) 113 | 114 | if not merged: 115 | print(f"❌ No products found in category '{category_name}'") 116 | print("\n💡 Available categories:") 117 | list_categories() 118 | return 119 | else: 120 | print("❌ Invalid arguments") 121 | print("\nUsage:") 122 | print(" python src/main/run_scraper.py # Show all products") 123 | print(" python src/main/run_scraper.py 'bread' # Search for product") 124 | print(" python src/main/run_scraper.py --categories # List all categories") 125 | print(" python src/main/run_scraper.py --category 'Bakery' # Show products by category") 126 | return 127 | else: 128 | print("❌ Too many arguments") 129 | return 130 | 131 | if not merged: 132 | print("❌ No products found") 133 | return 134 | 135 | print(f"\n📊 Found {len(merged)} product(s)") 136 | display_comparison(merged) 137 | 138 | if __name__ == "__main__": 139 | main() -------------------------------------------------------------------------------- /docs/specification/architecture.md: -------------------------------------------------------------------------------- 1 | # C4 Architectural Diagrams 2 | 3 | * **Context (Level 1)** – High-level system overview and interactions with users/external systems. 4 | * **Container (Level 2)** – Breakdown of the system into main components. 5 | * **Component (Level 3)** – Internal structure of each container. 6 | * **Code (Level 4)** – Low-level details. 7 | 8 | ### **Level 1** 9 | ```mermaid 10 | graph TD; 11 | User["🛒 Shopper\n(End User)"] -->|Uses| System["Grocery Price Comparison Platform"] 12 | System -->|Fetches Prices From| StoreAPIs["🏪 Store APIs"] 13 | System -->|Scrapes Data From| WebScraper["🌐 Web Scraper"] 14 | System -->|Displays Prices & Promotions| User 15 | ``` 16 | 17 | ### **Level 2** 18 | ```mermaid 19 | graph TD; 20 | subgraph User Interface 21 | MobileApp["📱 Mobile App"] 22 | end 23 | 24 | subgraph Backend 25 | API["🖥️ API Server"] 26 | Aggregator["🔄 Price Aggregator"] 27 | Notification["📢 Notification Service"] 28 | Auth["🔑 Authentication Service"] 29 | end 30 | 31 | subgraph Data Storage 32 | Database["🗄️ Database"] 33 | end 34 | 35 | subgraph Data Collection 36 | Scraper["🌐 Web Scraper"] 37 | StoreAPI["🏪 Store API Connector"] 38 | end 39 | 40 | User["🛒 Shopper"] -->|Uses| MobileApp 41 | 42 | MobileApp -->|Requests Data| API 43 | 44 | API -->|Fetches & Stores| Database 45 | API -->|Processes| Aggregator 46 | API -->|Sends Alerts| Notification 47 | API -->|Handles Login| Auth 48 | 49 | Aggregator -->|Gathers Prices| Scraper 50 | Aggregator -->|Calls| StoreAPI 51 | Scraper -->|Scrapes Websites| Database 52 | StoreAPI -->|Fetches Store Prices| Database 53 | ``` 54 | ### **Level 3** 55 | ```mermaid 56 | graph TD; 57 | subgraph User Interface 58 | MobileApp["📱 Mobile App"] 59 | end 60 | 61 | subgraph Backend 62 | API["🖥️ API Server"] 63 | Aggregator["🔄 Price Aggregator"] 64 | Notification["📢 Notification Service"] 65 | Auth["🔑 Authentication Service"] 66 | end 67 | 68 | subgraph Data Storage 69 | Database["🗄️ Database"] 70 | Logs["📜 Logging System"] 71 | end 72 | 73 | subgraph Data Collection 74 | Scraper["🌐 Web Scraper"] 75 | StoreAPI["🏪 Store API Connector"] 76 | ManualEntry["✍️ Manual Data Entry"] 77 | end 78 | 79 | 80 | subgraph Mobile App Components 81 | UI_Mobile["📱 UI Layer"] 82 | Controller_Mobile["🔄 Controller"] 83 | DataHandler_Mobile["💾 Data Handler"] 84 | end 85 | 86 | subgraph API Components 87 | API_Gateway["🌍 API Gateway"] 88 | RequestHandler["🔄 Request Handler"] 89 | BusinessLogic["📊 Business Logic"] 90 | Cache["⚡ Cache Layer"] 91 | end 92 | 93 | subgraph Aggregator Components 94 | PriceFetcher["🔍 Price Fetcher"] 95 | PriceAnalyzer["📊 Price Analyzer"] 96 | end 97 | 98 | subgraph Notification Components 99 | EmailService["📧 Email Service"] 100 | SMSService["📩 SMS Service"] 101 | PushService["📲 Push Notifications"] 102 | end 103 | 104 | subgraph Authentication Components 105 | AuthDB["🔑 User Database"] 106 | TokenService["🔐 Token Service"] 107 | OAuth["🔗 OAuth Provider"] 108 | end 109 | 110 | subgraph Scraper Components 111 | HTMLParser["📄 HTML Parser"] 112 | DataExtractor["📊 Data Extractor"] 113 | end 114 | 115 | subgraph Store API Components 116 | APIConnector["🔗 API Connector"] 117 | DataParser["📊 Data Parser"] 118 | end 119 | 120 | subgraph Manual Entry Components 121 | InputValidator["✔️ Input Validator"] 122 | DataUploader["📤 Data Uploader"] 123 | end 124 | 125 | 126 | User["🛒 Shopper"] -->|Uses| UI 127 | User -->|Uses| UI_Mobile 128 | 129 | UI -->|Calls| Controller 130 | Controller -->|Fetches Data| DataHandler 131 | UI_Mobile -->|Calls| Controller_Mobile 132 | Controller_Mobile -->|Fetches Data| DataHandler_Mobile 133 | 134 | Controller -->|Requests| API_Gateway 135 | Controller_Mobile -->|Requests| API_Gateway 136 | API_Gateway -->|Routes Requests| RequestHandler 137 | RequestHandler -->|Executes| BusinessLogic 138 | BusinessLogic -->|Queries| Database 139 | BusinessLogic -->|Caches Data| Cache 140 | 141 | BusinessLogic -->|Requests Prices| PriceFetcher 142 | PriceFetcher -->|Analyzes Data| PriceAnalyzer 143 | 144 | BusinessLogic -->|Notifies Users| EmailService 145 | BusinessLogic -->|Notifies Users| SMSService 146 | BusinessLogic -->|Notifies Users| PushService 147 | 148 | BusinessLogic -->|Authenticates| TokenService 149 | TokenService -->|Verifies Users| AuthDB 150 | TokenService -->|OAuth Login| OAuth 151 | 152 | PriceFetcher -->|Scrapes| HTMLParser 153 | HTMLParser -->|Extracts Data| DataExtractor 154 | 155 | PriceFetcher -->|Calls API| APIConnector 156 | APIConnector -->|Parses Data| DataParser 157 | 158 | ManualEntry -->|Validates Input| InputValidator 159 | InputValidator -->|Uploads Data| DataUploader 160 | DataUploader -->|Stores in Database| Database 161 | 162 | BusinessLogic -->|Logs Events| Logs 163 | 164 | ``` 165 | 166 | ### **Level 4** 167 | ```mermaid 168 | graph TD; 169 | subgraph User Interface 170 | MobileApp["📱 Mobile App"] 171 | end 172 | 173 | subgraph Backend 174 | API["🖥️ API Server"] 175 | Aggregator["🔄 Price Aggregator"] 176 | Notification["📢 Notification Service"] 177 | Auth["🔑 Authentication Service"] 178 | end 179 | 180 | subgraph Data Storage 181 | Database["🗄️ Database"] 182 | Logs["📜 Logging System"] 183 | end 184 | 185 | subgraph Data Collection 186 | Scraper["🌐 Web Scraper"] 187 | StoreAPI["🏪 Store API Connector"] 188 | ManualEntry["✍️ Manual Data Entry"] 189 | end 190 | 191 | 192 | User["🛒 Shopper"] -->|Uses| MobileApp 193 | 194 | WebApp -->|Requests Data| API 195 | MobileApp -->|Requests Data| API 196 | 197 | API -->|Fetches Prices| Aggregator 198 | Aggregator -->|Uses| Scraper 199 | Aggregator -->|Calls| StoreAPI 200 | Aggregator -->|Processes Data| Database 201 | API -->|Sends Notifications| Notification 202 | API -->|Authenticates Users| Auth 203 | Scraper -->|Extracts Data| Database 204 | StoreAPI -->|Fetches Store Data| Database 205 | ManualEntry -->|Uploads Data| Database 206 | ``` 207 | -------------------------------------------------------------------------------- /src/scraper/checkers_scraper.py: -------------------------------------------------------------------------------- 1 | MOCK_PRODUCTS = [ 2 | {"id": "C001", "name": "Albany Brown Bread 700g", "price": 15.99, "store": "Checkers", "category": "Bakery"}, 3 | {"id": "C002", "name": "Albany White Bread 700g", "price": 16.49, "store": "Checkers", "category": "Bakery"}, 4 | {"id": "C003", "name": "Clover Full Cream Milk 2L", "price": 34.99, "store": "Checkers", "category": "Dairy"}, 5 | {"id": "C004", "name": "Clover Tropika Orange 2L", "price": 25.99, "store": "Checkers", "category": "Beverages"}, 6 | {"id": "C005", "name": "Coca-Cola 2L", "price": 19.99, "store": "Checkers", "category": "Beverages"}, 7 | {"id": "C006", "name": "Fanta Orange 2L", "price": 18.99, "store": "Checkers", "category": "Beverages"}, 8 | {"id": "C007", "name": "Sprite 2L", "price": 18.49, "store": "Checkers", "category": "Beverages"}, 9 | {"id": "C008", "name": "Sunlight Dishwashing Liquid 750ml", "price": 21.99, "store": "Checkers", "category": "Cleaning"}, 10 | {"id": "C009", "name": "Handy Andy Cream 750ml", "price": 28.49, "store": "Checkers", "category": "Cleaning"}, 11 | {"id": "C010", "name": "Domestos Thick Bleach 750ml", "price": 29.99, "store": "Checkers", "category": "Cleaning"}, 12 | {"id": "C011", "name": "Joko Tea Bags 100s", "price": 32.99, "store": "Checkers", "category": "Pantry"}, 13 | {"id": "C012", "name": "Five Roses Tea Bags 100s", "price": 38.49, "store": "Checkers", "category": "Pantry"}, 14 | {"id": "C013", "name": "Nescafe Classic Coffee 200g", "price": 89.99, "store": "Checkers", "category": "Pantry"}, 15 | {"id": "C014", "name": "Ricoffy 750g", "price": 92.99, "store": "Checkers", "category": "Pantry"}, 16 | {"id": "C015", "name": "White Star Maize Meal 10kg", "price": 119.99, "store": "Checkers", "category": "Pantry"}, 17 | {"id": "C016", "name": "Snowflake Cake Flour 2.5kg", "price": 44.99, "store": "Checkers", "category": "Pantry"}, 18 | {"id": "C017", "name": "Selati White Sugar 2.5kg", "price": 52.99, "store": "Checkers", "category": "Pantry"}, 19 | {"id": "C018", "name": "Tastic Rice 2kg", "price": 64.99, "store": "Checkers", "category": "Pantry"}, 20 | {"id": "C019", "name": "Spekko Rice 2kg", "price": 62.49, "store": "Checkers", "category": "Pantry"}, 21 | {"id": "C020", "name": "Jungle Oats 1kg", "price": 38.99, "store": "Checkers", "category": "Pantry"}, 22 | {"id": "C021", "name": "Kellogg's Corn Flakes 1kg", "price": 69.49, "store": "Checkers", "category": "Pantry"}, 23 | {"id": "C022", "name": "Bokomo Weet-Bix 450g", "price": 42.99, "store": "Checkers", "category": "Pantry"}, 24 | {"id": "C023", "name": "Huletts Brown Sugar 2.5kg", "price": 49.99, "store": "Checkers", "category": "Pantry"}, 25 | {"id": "C024", "name": "Crosse & Blackwell Tangy Mayonnaise 750g", "price": 38.49, "store": "Checkers", "category": "Pantry"}, 26 | {"id": "C025", "name": "Koo Baked Beans 410g", "price": 14.99, "store": "Checkers", "category": "Pantry"}, 27 | {"id": "C026", "name": "All Gold Tomato Sauce 700ml", "price": 36.99, "store": "Checkers", "category": "Pantry"}, 28 | {"id": "C027", "name": "Rhodes Chopped Tomatoes 410g", "price": 13.49, "store": "Checkers", "category": "Pantry"}, 29 | {"id": "C028", "name": "Clover Butter 500g", "price": 94.99, "store": "Checkers", "category": "Dairy"}, 30 | {"id": "C029", "name": "Flora Margarine 1kg", "price": 84.99, "store": "Checkers", "category": "Dairy"}, 31 | {"id": "C030", "name": "Ariel Auto Washing Powder 2kg", "price": 89.99, "store": "Checkers", "category": "Cleaning"}, 32 | {"id": "C031", "name": "OMO Auto Washing Liquid 2L", "price": 92.49, "store": "Checkers", "category": "Cleaning"}, 33 | {"id": "C032", "name": "Sunlight Washing Powder 2kg", "price": 78.99, "store": "Checkers", "category": "Cleaning"}, 34 | {"id": "C033", "name": "Shield Roll-On 50ml", "price": 24.99, "store": "Checkers", "category": "Personal Care"}, 35 | {"id": "C034", "name": "Colgate Toothpaste 100ml", "price": 19.99, "store": "Checkers", "category": "Personal Care"}, 36 | {"id": "C035", "name": "Aquafresh Toothpaste 100ml", "price": 18.49, "store": "Checkers", "category": "Personal Care"}, 37 | {"id": "C036", "name": "Dettol Antiseptic 750ml", "price": 84.49, "store": "Checkers", "category": "Personal Care"}, 38 | {"id": "C037", "name": "Panado Tablets 24s", "price": 27.99, "store": "Checkers", "category": "Health"}, 39 | {"id": "C038", "name": "Grand-Pa Headache Powders 38s", "price": 58.49, "store": "Checkers", "category": "Health"}, 40 | {"id": "C039", "name": "Huggies Nappies Size 4 36s", "price": 179.99, "store": "Checkers", "category": "Baby Care"}, 41 | {"id": "C040", "name": "Pampers Nappies Size 4 36s", "price": 184.99, "store": "Checkers", "category": "Baby Care"}, 42 | {"id": "C041", "name": "Jacobs Kronung Coffee 200g", "price": 99.99, "store": "Checkers", "category": "Pantry"}, 43 | {"id": "C042", "name": "Oros Orange Squash 2L", "price": 47.99, "store": "Checkers", "category": "Beverages"}, 44 | {"id": "C043", "name": "Futurelife Smart Food 500g", "price": 57.99, "store": "Checkers", "category": "Pantry"}, 45 | {"id": "C044", "name": "Nestle Milo 500g", "price": 72.99, "store": "Checkers", "category": "Pantry"}, 46 | {"id": "C045", "name": "Bakers Eet-Sum-Mor Biscuits 200g", "price": 32.99, "store": "Checkers", "category": "Snacks"}, 47 | {"id": "C046", "name": "Bakers Tennis Biscuits 200g", "price": 33.49, "store": "Checkers", "category": "Snacks"}, 48 | {"id": "C047", "name": "Koo Peach Halves 410g", "price": 21.49, "store": "Checkers", "category": "Pantry"}, 49 | {"id": "C048", "name": "Rhodes Pineapple Rings 410g", "price": 23.49, "store": "Checkers", "category": "Pantry"}, 50 | {"id": "C049", "name": "Crosse & Blackwell Salad Cream 790g", "price": 44.99, "store": "Checkers", "category": "Pantry"}, 51 | {"id": "C050", "name": "Iwisa Maize Meal 10kg", "price": 114.99, "store": "Checkers", "category": "Pantry"}, 52 | ] 53 | 54 | def get_products(search_term: str): 55 | results = {} 56 | for product in MOCK_PRODUCTS: 57 | if search_term.lower() in product["name"].lower(): 58 | results[product["name"]] = product["price"] 59 | return results 60 | 61 | def get_products_by_category(category: str): 62 | """Get products by category""" 63 | results = {} 64 | for product in MOCK_PRODUCTS: 65 | if category.lower() in product["category"].lower(): 66 | results[product["name"]] = product["price"] 67 | return results 68 | 69 | def get_categories(): 70 | """Get all available categories""" 71 | categories = set() 72 | for product in MOCK_PRODUCTS: 73 | categories.add(product["category"]) 74 | return sorted(list(categories)) -------------------------------------------------------------------------------- /src/scraper/woolworths_scraper.py: -------------------------------------------------------------------------------- 1 | MOCK_PRODUCTS = [ 2 | {"id": "301", "name": "Clover Tropika Orange 2L", "price": 35.99, "store": "Woolworths", "category": "Beverages"}, 3 | {"id": "302", "name": "Clover Tropika Mango 2L", "price": 36.49, "store": "Woolworths", "category": "Beverages"}, 4 | {"id": "303", "name": "Pringles Original 150g", "price": 24.99, "store": "Woolworths", "category": "Snacks"}, 5 | {"id": "304", "name": "Pringles Sour Cream 150g", "price": 24.49, "store": "Woolworths", "category": "Snacks"}, 6 | {"id": "305", "name": "Nestle Nespray 1L", "price": 44.49, "store": "Woolworths", "category": "Dairy"}, 7 | {"id": "306", "name": "Albany Brown Bread 700g", "price": 16.99, "store": "Woolworths", "category": "Bakery"}, 8 | {"id": "307", "name": "Fanta Orange 2L", "price": 19.99, "store": "Woolworths", "category": "Beverages"}, 9 | {"id": "308", "name": "Coca-Cola 2L", "price": 20.99, "store": "Woolworths", "category": "Beverages"}, 10 | {"id": "309", "name": "Sunlight Dishwashing Liquid 750ml", "price": 23.49, "store": "Woolworths", "category": "Cleaning"}, 11 | {"id": "310", "name": "Huggies Nappies Size 4 36s", "price": 182.99, "store": "Woolworths", "category": "Baby Care"}, 12 | {"id": "311", "name": "Jungle Oats 1kg", "price": 39.99, "store": "Woolworths", "category": "Pantry"}, 13 | {"id": "312", "name": "Kellogg's Corn Flakes 1kg", "price": 71.99, "store": "Woolworths", "category": "Pantry"}, 14 | {"id": "313", "name": "Five Roses Tea Bags 102s", "price": 56.99, "store": "Woolworths", "category": "Pantry"}, 15 | {"id": "314", "name": "Jacobs Kronung Coffee 200g", "price": 92.99, "store": "Woolworths", "category": "Pantry"}, 16 | {"id": "315", "name": "Woolworths Full Cream Milk 2L", "price": 34.49, "store": "Woolworths", "category": "Dairy"}, 17 | {"id": "316", "name": "Woolworths Low Fat Yoghurt 1kg", "price": 42.99, "store": "Woolworths", "category": "Dairy"}, 18 | {"id": "317", "name": "Clover Butter Salted 500g", "price": 68.49, "store": "Woolworths", "category": "Dairy"}, 19 | {"id": "318", "name": "Flora Light Margarine 1kg", "price": 72.99, "store": "Woolworths", "category": "Dairy"}, 20 | {"id": "319", "name": "Crosse & Blackwell Tangy Mayonnaise 750g", "price": 39.99, "store": "Woolworths", "category": "Pantry"}, 21 | {"id": "320", "name": "All Gold Tomato Sauce 700ml", "price": 33.49, "store": "Woolworths", "category": "Pantry"}, 22 | {"id": "321", "name": "Tastic Rice 2kg", "price": 49.99, "store": "Woolworths", "category": "Pantry"}, 23 | {"id": "322", "name": "Spekko Parboiled Rice 2kg", "price": 46.99, "store": "Woolworths", "category": "Pantry"}, 24 | {"id": "323", "name": "White Star Maize Meal 10kg", "price": 104.99, "store": "Woolworths", "category": "Pantry"}, 25 | {"id": "324", "name": "Koo Baked Beans 410g", "price": 15.49, "store": "Woolworths", "category": "Pantry"}, 26 | {"id": "325", "name": "Lucky Star Pilchards in Tomato Sauce 400g", "price": 25.99, "store": "Woolworths", "category": "Pantry"}, 27 | {"id": "326", "name": "Knorr Chicken Stock Cubes 24s", "price": 35.49, "store": "Woolworths", "category": "Pantry"}, 28 | {"id": "327", "name": "Rhodes Pineapple Pieces 410g", "price": 24.49, "store": "Woolworths", "category": "Pantry"}, 29 | {"id": "328", "name": "Woolworths Peanut Butter Smooth 400g", "price": 43.49, "store": "Woolworths", "category": "Pantry"}, 30 | {"id": "329", "name": "Woolworths Strawberry Jam 450g", "price": 39.99, "store": "Woolworths", "category": "Pantry"}, 31 | {"id": "330", "name": "Bokomo Weet-Bix 900g", "price": 54.99, "store": "Woolworths", "category": "Pantry"}, 32 | {"id": "331", "name": "Woolworths Free Range Eggs 18s", "price": 69.99, "store": "Woolworths", "category": "Dairy"}, 33 | {"id": "332", "name": "Woolworths Chicken Drumsticks 1kg", "price": 109.99, "store": "Woolworths", "category": "Meat"}, 34 | {"id": "333", "name": "Woolworths Beef Mince 500g", "price": 86.49, "store": "Woolworths", "category": "Meat"}, 35 | {"id": "334", "name": "Woolworths Baby Potatoes 1kg", "price": 42.49, "store": "Woolworths", "category": "Produce"}, 36 | {"id": "335", "name": "Woolworths Broccoli Florets 500g", "price": 45.99, "store": "Woolworths", "category": "Produce"}, 37 | {"id": "336", "name": "Woolworths Fresh Apples 1.5kg", "price": 39.99, "store": "Woolworths", "category": "Produce"}, 38 | {"id": "337", "name": "Woolworths Bananas 1kg", "price": 28.49, "store": "Woolworths", "category": "Produce"}, 39 | {"id": "338", "name": "Woolworths Still Water 5L", "price": 29.99, "store": "Woolworths", "category": "Beverages"}, 40 | {"id": "339", "name": "Woolworths Mixed Nuts 250g", "price": 69.99, "store": "Woolworths", "category": "Snacks"}, 41 | {"id": "340", "name": "Woolworths Trail Mix 250g", "price": 72.99, "store": "Woolworths", "category": "Snacks"}, 42 | {"id": "341", "name": "Dettol Antibacterial Soap 100g", "price": 13.49, "store": "Woolworths", "category": "Personal Care"}, 43 | {"id": "342", "name": "Shield Roll-On for Women 50ml", "price": 23.49, "store": "Woolworths", "category": "Personal Care"}, 44 | {"id": "343", "name": "Colgate Toothpaste 100ml", "price": 20.49, "store": "Woolworths", "category": "Personal Care"}, 45 | {"id": "344", "name": "Always Ultra Pads 8s", "price": 25.99, "store": "Woolworths", "category": "Personal Care"}, 46 | {"id": "345", "name": "Pampers Premium Care Size 5 44s", "price": 239.99, "store": "Woolworths", "category": "Baby Care"}, 47 | {"id": "346", "name": "Baby Soft Toilet Paper 9s", "price": 86.49, "store": "Woolworths", "category": "Personal Care"}, 48 | {"id": "347", "name": "Dettol Surface Disinfectant 500ml", "price": 65.99, "store": "Woolworths", "category": "Cleaning"}, 49 | {"id": "348", "name": "Handy Andy Cream Lemon 750ml", "price": 30.49, "store": "Woolworths", "category": "Cleaning"}, 50 | {"id": "349", "name": "Domestos Thick Bleach 750ml", "price": 30.99, "store": "Woolworths", "category": "Cleaning"}, 51 | {"id": "350", "name": "Sunlight Laundry Bar 500g", "price": 15.49, "store": "Woolworths", "category": "Cleaning"}, 52 | ] 53 | 54 | def get_products(search_term: str): 55 | results = {} 56 | for product in MOCK_PRODUCTS: 57 | if search_term.lower() in product["name"].lower(): 58 | results[product["name"]] = product["price"] 59 | return results 60 | 61 | def get_products_by_category(category: str): 62 | """Get products by category""" 63 | results = {} 64 | for product in MOCK_PRODUCTS: 65 | if category.lower() in product["category"].lower(): 66 | results[product["name"]] = product["price"] 67 | return results 68 | 69 | def get_categories(): 70 | """Get all available categories""" 71 | categories = set() 72 | for product in MOCK_PRODUCTS: 73 | categories.add(product["category"]) 74 | return sorted(list(categories)) -------------------------------------------------------------------------------- /src/scraper/picknpay_scraper.py: -------------------------------------------------------------------------------- 1 | MOCK_PRODUCTS = [ 2 | {"id": "201", "name": "Clover Tropika Orange 2L", "price": 34.50, "store": "Pick n Pay", "category": "Beverages"}, 3 | {"id": "202", "name": "Clover Tropika Strawberry 2L", "price": 34.99, "store": "Pick n Pay", "category": "Beverages"}, 4 | {"id": "203", "name": "Doritos Cheese 150g", "price": 21.99, "store": "Pick n Pay", "category": "Snacks"}, 5 | {"id": "204", "name": "Doritos BBQ 150g", "price": 21.49, "store": "Pick n Pay", "category": "Snacks"}, 6 | {"id": "205", "name": "Nestle Nespray 1L", "price": 43.49, "store": "Pick n Pay", "category": "Dairy"}, 7 | {"id": "206", "name": "Albany Brown Bread 700g", "price": 16.49, "store": "Pick n Pay", "category": "Bakery"}, 8 | {"id": "207", "name": "Fanta Orange 2L", "price": 19.49, "store": "Pick n Pay", "category": "Beverages"}, 9 | {"id": "208", "name": "Coca-Cola 2L", "price": 20.49, "store": "Pick n Pay", "category": "Beverages"}, 10 | {"id": "209", "name": "Sunlight Dishwashing Liquid 750ml", "price": 22.49, "store": "Pick n Pay", "category": "Cleaning"}, 11 | {"id": "210", "name": "Huggies Nappies Size 4 36s", "price": 180.99, "store": "Pick n Pay", "category": "Baby Care"}, 12 | {"id": "211", "name": "Kellogg's Corn Flakes 1kg", "price": 69.99, "store": "Pick n Pay", "category": "Pantry"}, 13 | {"id": "212", "name": "Jungle Oats 1kg", "price": 37.99, "store": "Pick n Pay", "category": "Pantry"}, 14 | {"id": "213", "name": "Nescafe Classic 200g", "price": 89.99, "store": "Pick n Pay", "category": "Pantry"}, 15 | {"id": "214", "name": "Five Roses Tea Bags 102s", "price": 54.99, "store": "Pick n Pay", "category": "Pantry"}, 16 | {"id": "215", "name": "Tastic Rice 2kg", "price": 47.99, "store": "Pick n Pay", "category": "Pantry"}, 17 | {"id": "216", "name": "Spekko Parboiled Rice 2kg", "price": 45.49, "store": "Pick n Pay", "category": "Pantry"}, 18 | {"id": "217", "name": "White Star Super Maize Meal 10kg", "price": 99.99, "store": "Pick n Pay", "category": "Pantry"}, 19 | {"id": "218", "name": "Ariel Auto Washing Powder 2kg", "price": 74.99, "store": "Pick n Pay", "category": "Cleaning"}, 20 | {"id": "219", "name": "Omo Auto Liquid 2L", "price": 89.99, "store": "Pick n Pay", "category": "Cleaning"}, 21 | {"id": "220", "name": "Colgate Toothpaste Triple Action 100ml", "price": 19.99, "store": "Pick n Pay", "category": "Personal Care"}, 22 | {"id": "221", "name": "Sunfoil Sunflower Oil 2L", "price": 89.49, "store": "Pick n Pay", "category": "Pantry"}, 23 | {"id": "222", "name": "Excella Cooking Oil 2L", "price": 86.99, "store": "Pick n Pay", "category": "Pantry"}, 24 | {"id": "223", "name": "All Gold Tomato Sauce 700ml", "price": 32.49, "store": "Pick n Pay", "category": "Pantry"}, 25 | {"id": "224", "name": "Knorr Chicken Stock Cubes 24s", "price": 34.99, "store": "Pick n Pay", "category": "Pantry"}, 26 | {"id": "225", "name": "Rhodes Baked Beans 410g", "price": 14.99, "store": "Pick n Pay", "category": "Pantry"}, 27 | {"id": "226", "name": "Lucky Star Pilchards in Tomato Sauce 400g", "price": 24.99, "store": "Pick n Pay", "category": "Pantry"}, 28 | {"id": "227", "name": "Koo Sweetcorn Cream Style 410g", "price": 16.99, "store": "Pick n Pay", "category": "Pantry"}, 29 | {"id": "228", "name": "Crosse & Blackwell Tangy Mayonnaise 750g", "price": 38.99, "store": "Pick n Pay", "category": "Pantry"}, 30 | {"id": "229", "name": "Rama Original 500g", "price": 36.49, "store": "Pick n Pay", "category": "Dairy"}, 31 | {"id": "230", "name": "Flora Margarine Light 1kg", "price": 69.99, "store": "Pick n Pay", "category": "Dairy"}, 32 | {"id": "231", "name": "Clover Fresh Milk 2L", "price": 34.49, "store": "Pick n Pay", "category": "Dairy"}, 33 | {"id": "232", "name": "Woolworths Free Range Eggs 18s", "price": 68.99, "store": "Pick n Pay", "category": "Dairy"}, 34 | {"id": "233", "name": "Woolworths Chicken Breasts 1kg", "price": 129.99, "store": "Pick n Pay", "category": "Meat"}, 35 | {"id": "234", "name": "Woolworths Lean Beef Mince 500g", "price": 84.99, "store": "Pick n Pay", "category": "Meat"}, 36 | {"id": "235", "name": "Woolworths Baby Potatoes 1kg", "price": 39.99, "store": "Pick n Pay", "category": "Produce"}, 37 | {"id": "236", "name": "Woolworths Broccoli Florets 500g", "price": 44.99, "store": "Pick n Pay", "category": "Produce"}, 38 | {"id": "237", "name": "Woolworths Greek Yoghurt 1kg", "price": 69.99, "store": "Pick n Pay", "category": "Dairy"}, 39 | {"id": "238", "name": "Woolworths Freshly Baked Croissants 4 Pack", "price": 49.99, "store": "Pick n Pay", "category": "Bakery"}, 40 | {"id": "239", "name": "Woolworths Still Water 5L", "price": 29.99, "store": "Pick n Pay", "category": "Beverages"}, 41 | {"id": "240", "name": "Woolworths Smooth Peanut Butter 400g", "price": 42.99, "store": "Pick n Pay", "category": "Pantry"}, 42 | {"id": "241", "name": "Dettol Antibacterial Soap 100g", "price": 12.99, "store": "Pick n Pay", "category": "Personal Care"}, 43 | {"id": "242", "name": "Handy Andy Cream Lemon 750ml", "price": 29.99, "store": "Pick n Pay", "category": "Cleaning"}, 44 | {"id": "243", "name": "Shield Roll-On for Men 50ml", "price": 22.49, "store": "Pick n Pay", "category": "Personal Care"}, 45 | {"id": "244", "name": "Always Ultra Pads 8s", "price": 24.99, "store": "Pick n Pay", "category": "Personal Care"}, 46 | {"id": "245", "name": "Pampers Baby-Dry Size 5 44s", "price": 229.99, "store": "Pick n Pay", "category": "Baby Care"}, 47 | {"id": "246", "name": "Baby Soft Toilet Paper 9s", "price": 84.99, "store": "Pick n Pay", "category": "Personal Care"}, 48 | {"id": "247", "name": "Lifebuoy Handwash 200ml", "price": 29.49, "store": "Pick n Pay", "category": "Personal Care"}, 49 | {"id": "248", "name": "Dettol Surface Disinfectant 500ml", "price": 64.99, "store": "Pick n Pay", "category": "Cleaning"}, 50 | {"id": "249", "name": "Domestos Thick Bleach 750ml", "price": 29.99, "store": "Pick n Pay", "category": "Cleaning"}, 51 | {"id": "250", "name": "Sunlight Laundry Bar 500g", "price": 14.99, "store": "Pick n Pay", "category": "Cleaning"}, 52 | ] 53 | 54 | def get_products(search_term: str): 55 | results = {} 56 | for product in MOCK_PRODUCTS: 57 | if search_term.lower() in product["name"].lower(): 58 | results[product["name"]] = product["price"] 59 | return results 60 | 61 | def get_products_by_category(category: str): 62 | """Get products by category""" 63 | results = {} 64 | for product in MOCK_PRODUCTS: 65 | if category.lower() in product["category"].lower(): 66 | results[product["name"]] = product["price"] 67 | return results 68 | 69 | def get_categories(): 70 | """Get all available categories""" 71 | categories = set() 72 | for product in MOCK_PRODUCTS: 73 | categories.add(product["category"]) 74 | return sorted(list(categories)) -------------------------------------------------------------------------------- /docs/specification/system_requirements_document.md: -------------------------------------------------------------------------------- 1 | ## 📌 System Requirements Document (SRD) Overview 2 | This document categorizes functional and non-functional requirements, aligning them with key quality attributes to ensure a well-structured system design. As the foundation for the system, it ensures that both business objectives and user expectations are met. 3 | * **Functional Requirements:** Defines the core features and functionality of the system. Focus on capabilities that directly address stakeholder concerns. Includes acceptance criteria for critical requirements.
4 | * **Non-Functional Requirements:** Address the overall system usability, deployability, maintenance, scalability, security and performance.

5 | 6 | **Also view:** #### Stakeholder Analysis: [Stakeholder Analysis.md](stakeholder_analysis) 7 | 8 | ## 📌 Functional Requirements 9 | 10 | --- 11 | | | **Requirement** | **Acceptance Criteria** | 12 | |---------------------|------------------------------|-------------------------| 13 | | **Usability** | **Search Functionality** | ✅ Users can search for specific grocery items by name or category.
✅ Search results return relevant products within **2 seconds**. | 14 | | | **Comparison Engine** | ✅ Users can see a side-by-side price comparison across multiple retailers.
✅ The cheapest option is highlighted for easy selection. | 15 | | | **Filter Options** | ✅ Users can filter results by **price, location, retailer, brand, or promotions**.
✅ Filters apply instantly without page reloads. | 16 | | | **Geolocation Integration** | ✅ Users can view deals available **near their location** or within a **custom radius**.
✅ Location-based filtering updates automatically when the user moves. | 17 | | | **Notifications** | ✅ Users receive **push/email notifications** when prices drop on saved items.
✅ Users can enable or disable notifications in settings. | 18 | | | **Save or Share Feature** | ✅ Users can **save their shopping lists** within the app.
✅ Users can **share price comparisons** via WhatsApp, SMS, or social media. | 19 | |   | | | 20 | | **Deployability** | **Platform Compatibility** | ✅ The system should be deployable on **Windows, Linux**, and major cloud services (AWS, Azure).
✅ The system supports deployment on modern web servers. | 21 | |   | | | 22 | | **Maintainability** | **Documentation** | ✅ The documentation shall include an **API guide** for future integrations.
✅ Clear setup instructions for developers and system administrators. | 23 | | | **Modular Codebase** | ✅ Codebase should be **modular** for easy future upgrades.
✅ Each module must have unit tests and clear comments. | 24 | |   | | | 25 | | **Scalability** | **System Scalability** | ✅ The system can handle **1,000 concurrent users** during peak hours.
✅ The database is capable of scaling to handle high traffic. | 26 | | | **Store Coverage Expansion** | ✅ New stores can be integrated within **14 business days** without major system changes.
✅ The system scales efficiently as new stores are added. | 27 | |   | | | 28 | | **Security** | **Data Security** | ✅ All user data shall be encrypted using **AES-256** encryption both **in transit and at rest**.
✅ User passwords are stored securely using **bcrypt** hashing. | 29 | | | **User Authentication** | ✅ Users will authenticate via email/password or OAuth (Google, Facebook).
✅ The system shall implement **multi-factor authentication (MFA)**. | 30 | |   | | | 31 | | **Performance** | **Search Performance** | ✅ Search results shall load within **2 seconds** for any query.
✅ The system shall handle search requests efficiently under high load. | 32 | | | **Real-Time Price Updates** | ✅ The system fetches price data via APIs or web scraping at regular intervals.
✅ Data accuracy is maintained above **95%** with minimal latency. | 33 | --- 34 | 35 | ## 📌 Non-Functional Requirements 36 | 37 | --- 38 | | | **Requirement** | **Acceptance Criteria** | 39 | |------------------------------|------------------------------|-----------------------------------| 40 | | **Usability** | **User Interface Design** | ✅ The system shall have an intuitive, user-friendly interface with a how to secttion to educate users on how to use the app
✅ The interface shall comply with **WCAG 2.1 accessibility standards** to ensure accessibility for users with disabilities. | 41 | | | **Search Functionality** | ✅ The search feature shall be easy to use and return relevant results quickly.
✅ Search filters shall be simple to apply and clear in their purpose.| 42 | |   | | | 43 | | **Deployability** | **Cross-Platform Compatibility** | ✅ The system shall be deployable on **Windows, Linux**, and modern cloud platforms (e.g., AWS, Google Cloud).
✅ Deployment on **Docker containers** should be supported for flexibility. | 44 | | | **Continuous Integration/Deployment** | ✅ The system shall have a CI/CD pipeline set up to deploy updates smoothly to production with minimal downtime. | 45 | |   | | | 46 | | **Maintainability** | **Code Documentation** | ✅ The codebase shall be well-documented, with clear comments and explanations.
✅ An **API guide** will be included for ease of future integrations. | 47 | | | **Error Logging & Monitoring** | ✅ The system shall have logging for system errors and user actions to aid future troubleshooting and maintenance.
✅ There will be automated alerts for system failures. | 48 | |   | | | 49 | | **Scalability** | **Horizontal Scalability** | ✅ The system shall support **horizontal scaling** to accommodate an increasing number of users and stores.
✅ Load balancing shall be implemented to ensure even distribution of traffic. | 50 | | | **Database Scalability** | ✅ The system shall be able to scale the database to accommodate millions of records without significant performance degradation.
✅ Partitioning and replication strategies should be in place to improve database performance. | 51 | |   | | | 52 | | **Security** | **Data Encryption** | ✅ All sensitive data (e.g., user passwords, personal information) shall be encrypted using **AES-256** or stronger encryption.
✅ Communication with third-party services shall also be encrypted using **TLS**. | 53 | | | **Access Control** | ✅ Users shall only have access to data they are authorized to view.
✅ Role-based access control (RBAC) shall be implemented. | 54 | | | **Session Management** | ✅ Sessions shall expire after **30 minutes of inactivity**.
✅ Users shall be logged out after multiple failed login attempts, requiring additional authentication steps (e.g., CAPTCHA). | 55 | |   | | | 56 | | **Performance** | **Response Time** | ✅ The system shall respond to user requests (e.g., searches, comparisons) within **2 seconds**.
✅ High traffic periods shall not result in degraded user experience. | 57 | | | **Real-Time Updates** | ✅ The system shall provide **real-time updates** of prices without requiring page refresh.
✅ Real-time data should be retrieved and displayed with a latency of less than **3 seconds**. | 58 | | | **Throughput** | ✅ The system shall handle at least **1,000 concurrent users** during peak periods.
✅ Each user request shall be handled within **2 seconds**. | 59 | --- 60 | -------------------------------------------------------------------------------- /docs/agile_planning/user_stories.md: -------------------------------------------------------------------------------- 1 | # 📌 User Stories 2 | 3 | --- 4 | ### 🎯 Case: Price aggregator comparison system 5 | 6 | --- 7 | ## Overview 8 | 9 | User stories define key features from the end user's perspective, focusing on actions and outcomes. They follow the 10 | INVEST criteria for clarity and feasibility, and are prioritized using the MoSCoW method. These stories ensure the system 11 | delivers valuable features first, such as product search and price comparison, guiding the development to meet user needs 12 | and business objectives efficiently. 13 | 14 | --- 15 | ## Functional Requirements 16 | | **Story ID** | **Feature** | **User Story** | **Acceptance Criteria** | **Priority** | 17 | |--------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------| 18 | | US-001 | **Search Functionality** | As a user, I want to search for products so that I can quickly find the items I need. | ✅ The system shall allow users to enter search terms and return relevant results within 2 seconds.
✅ Search results shall include product name, price, retailer, and availability. | High | 19 | | US-002 | **Comparison Engine** | As a user, I want to compare product prices across multiple retailers so that I can choose the most affordable option. | ✅ The system shall display a side-by-side price comparison for the same product from different retailers.
✅ Users shall be able to sort results by price, rating, and retailer. | High | 20 | | US-003 | **Filter Options** | As a user, I want to filter products by price, location, and retailer so that I can refine my search results. | ✅ The system shall provide filter options for price range, retailer, and location.
✅ The applied filters shall immediately update the search results. | Medium | 21 | | US-004 | **Geolocation Integration** | As a user, I want the system to suggest deals based on my location so that I can find the best prices nearby. | ✅ The system shall request location permission from users.
✅ Users shall be able to see deals available within a user-defined radius (e.g., 5km, 10km). | Medium | 22 | | US-005 | **Daily Specials & Promotions** | As a user, I want to see daily promotions and discounts so that I can take advantage of the best deals. | ✅ The system shall display a dedicated section for daily promotions and store-specific deals.
✅ Users shall be able to filter promotions by category or retailer. | Medium | 23 | | US-006 | **Price Drop Alerts** | As a user, I want to receive notifications when a product’s price drops so that I can purchase it at the best time. | ✅ Users shall be able to subscribe to price drop alerts for specific products.
✅ Notifications shall be sent via email or app alerts when a price drop occurs. | High | 24 | | US-007 | **Retailer Pricing Updates** | As a retailer, I want to update my product prices in real time so that customers see accurate pricing information. | ✅ Retailers shall have access to a dashboard where they can update pricing instantly.
✅ Price changes shall reflect in the system within 1 minute. | High | 25 | | US-008 | **Advertisement Management** | As an advertiser, I want to publish promotions on the platform so that I can reach potential customers more effectively. | ✅ Advertisers shall be able to create and schedule promotional campaigns.
✅ Users shall see targeted promotions based on browsing history and preferences. | Medium | 26 | 27 | --- 28 | 1. **Search Functionality:** As a user, I want to search for products so that I can quickly find the items I need. 29 | 2. **Comparison Engine:** As a user, I want to compare product prices across multiple retailers so that I can choose the most affordable option. 30 | 3. **Filter Options:** As a user, I want to filter products by price, location, and retailer so that I can refine my search results. 31 | 4. **Geolocation Integration:** As a user, I want the system to suggest deals based on my location so that I can find the best prices nearby. 32 | 5. **Daily Specials & Promotions:** As a user, I want to see daily promotions and discounts so that I can take advantage of the best deals. 33 | 6. **Price Drop Alerts:** As a user, I want to receive notifications when a product’s price drops so that I can purchase it at the best time. 34 | 7. **Retailer Pricing Updates:** As a retailer, I want to update my product prices in real time so that customers see accurate pricing information. 35 | 8. **Advertisement Management:** As an advertiser, I want to publish promotions on the platform so that I can reach potential customers more effectively. 36 | --- 37 | ## Non-functional Requirements 38 | | **Story ID** | **Requirement** | **Acceptance Criteria** | **Priority** | 39 | |-------------|-----------------------------|-------------------------|------------| 40 | | NFR-001 | **Usability** – User Interface Design | ✅ The system shall have an intuitive, user-friendly interface with a "How To" section for user guidance.
✅ The interface shall comply with WCAG 2.1 accessibility standards for users with disabilities. | High | 41 | | NFR-002 | **Deployability** – Cross-Platform Compatibility | ✅ The system shall be deployable on Windows, Linux, and cloud platforms (AWS, Google Cloud).
✅ Docker support shall be included for flexible deployment. | High | 42 | | NFR-003 | **Maintainability** – Code Documentation | ✅ The codebase shall be well-documented, including inline comments and API documentation.
✅ A developer guide shall be provided to assist future enhancements. | Medium | 43 | | NFR-004 | **Maintainability** – Error Logging & Monitoring | ✅ The system shall include logging for errors and user actions.
✅ Automated alerts shall notify admins of system failures or downtime. | High | 44 | | NFR-005 | **Scalability** – Horizontal Scaling | ✅ The system shall support horizontal scaling to handle increasing users and retailers.
✅ Load balancing shall ensure even traffic distribution. | High | 45 | | NFR-006 | **Scalability** – Database Performance | ✅ The system shall efficiently store and retrieve large volumes of data without performance degradation.
✅ Partitioning and replication strategies shall be implemented for scalability. | Medium | 46 | | NFR-007 | **Security** – Data Encryption | ✅ All sensitive data (e.g., passwords, personal details) shall be encrypted using AES-256.
✅ All communications with third-party services shall be encrypted using TLS. | High | 47 | | NFR-008 | **Security** – Access Control | ✅ Role-Based Access Control (RBAC) shall be implemented to restrict data access.
✅ Users shall only access information they are authorized for. | High | 48 | | NFR-009 | **Security** – Session Management | ✅ Sessions shall automatically expire after 30 minutes of inactivity.
✅ Users shall be logged out after multiple failed login attempts, requiring CAPTCHA for re-login. | Medium | 49 | | NFR-010 | **Performance** – Response Time | ✅ Search results shall be returned within 2 seconds.
✅ The system shall handle 1,000 concurrent users during peak hours. | High | 50 | | NFR-011 | **Performance** – Real-Time Updates | ✅ The system shall provide real-time price updates without requiring a page refresh.
✅ Updates shall be displayed within 3 seconds of data change. | Medium | 51 | | NFR-012 | **Performance** – Throughput | ✅ The system shall process at least 1,000 API requests per second without failure.
✅ The database shall be optimized to handle high read/write operations efficiently. | High | 52 | 53 | --- 54 | 1. Usability 55 | * As a user, I want an intuitive and accessible interface so that I can easily navigate and use the system without confusion. 56 | * As a user with disabilities, I want the system to comply with WCAG 2.1 accessibility standards so that I can interact with it without barriers. 57 | 58 | 2. Deployability 59 | * As a developer, I want the system to be deployable on Windows, Linux, and cloud platforms so that it can run on various environments without modification. 60 | * As a DevOps engineer, I want Docker support so that deployment remains flexible and consistent across different environments. 61 | 62 | 3. Maintainability 63 | * As a developer, I want well-documented code so that future maintenance and feature enhancements are easier to implement. 64 | * As a team lead, I want logging and monitoring in place so that system issues can be detected and resolved quickly. 65 | 66 | 4. Scalability 67 | * As a system architect, I want the system to scale horizontally so that it can handle an increasing number of users and retailers without performance issues. 68 | * As a database administrator, I want efficient data storage and retrieval so that performance is not affected by large datasets. 69 | 70 | 5. Security 71 | * As a user, I want my personal data to be securely encrypted so that my privacy is protected from unauthorized access. 72 | * As a security officer, I want role-based access control (RBAC) implemented so that users can only access authorized information. 73 | * As an admin, I want session management policies in place so that inactive users are logged out after a period of inactivity. 74 | 75 | 6. Performance 76 | * As a user, I want the system to return search results within 2 seconds so that I don’t experience delays when looking for products. 77 | * As a user, I want real-time price updates so that I see the latest pricing without having to refresh the page. 78 | * As a system administrator, I want the system to process at least 1,000 API requests per second so that performance remains stable under heavy usage. 79 | --- -------------------------------------------------------------------------------- /docs/test_use_case_documentation/use_case_specifications.md: -------------------------------------------------------------------------------- 1 | # 📌 Use Case Specifications 2 | 3 | --- 4 | ### 🎯 Use Case: Price aggregator comparison system 5 | 6 | --- 7 | 8 | ## Overview 9 | 10 | This section outlines the **Use Case Specifications** for the **Price Aggregator Comparison System**, detailing the core functionalities and interactions between users, retailers, admins, data providers, and advertisers. 11 | 12 | 1. **Search for Products**: Users can search for grocery items, view matching results, and apply filters to refine their search. If no results are found or data sources are unavailable, the system provides appropriate feedback. 13 | 2. **Compare Prices**: Users can compare prices of a selected product across multiple retailers, apply filters (e.g., price, location), and choose the best option. The system handles scenarios where retailer data is missing or out of stock. 14 | 3. **Subscribe to Price Drop Alerts**: Users can subscribe to price drop alerts for their selected products, ensuring they are notified when prices change. Alternative flows account for situations like unlogged users or missing notification preferences. 15 | 4. **Update Pricing (Retailer)**: Retailers can upload or sync pricing data to keep the system up to date. The system handles errors like invalid data formats or connection failures to ensure data integrity. 16 | 5. **Manage System Data (Admin)**: Admins can manage system data, resolve inconsistencies, and monitor performance. They can also handle data corruption or unauthorized access attempts. 17 | 6. **Publish Promotions (Advertiser)**: Advertisers can upload promotional offers that are displayed to users. The system ensures that data is validated and any conflicts or API failures are appropriately managed. 18 | 7. **Provide Real-Time Data (Data Provider)**: Data providers sync real-time product pricing and availability. The system validates incoming data, handles conflicts, and confirms successful updates to maintain data accuracy. 19 | 20 | These use cases collectively outline the critical functionalities of the price comparison platform and ensure that all stakeholders can interact effectively with the system while addressing various edge cases and potential issues. 21 | 22 | --- 23 | 24 | ### Use Case 1: Search for Products 25 | 26 | --- 27 | ### Description 28 | - **Purpose**: To allow users to search for grocery items and view available options. 29 | - **Scope**: Covers the process of searching for products, retrieving results, and displaying them to the user. 30 | 31 | ### Preconditions 32 | - User has internet access. 33 | - System is connected to data sources (e.g., APIs, databases). 34 | - Product data is available from retailers. 35 | 36 | ### Postconditions 37 | - User views a list of products matching their search query. 38 | - System logs the search query for analytics and recommendations. 39 | 40 | ### Basic Flow 41 | 1. User enters a search query (e.g., "Milk 2L") in the search bar. 42 | 2. System validates the query and retrieves product data from connected data sources. 43 | 3. System filters and sorts the results based on relevance or user preferences. 44 | 4. System displays the list of matching products to the user. 45 | 5. User selects a product to view more details. 46 | 47 | ### Alternative Flows 48 | - **No Results Found**: 49 | - If no products match the search query, the system displays a message: "No results found. Try a different search." 50 | - The system suggests popular or related products. 51 | - **Data Source Unavailable**: 52 | - If a data source fails to respond, the system uses cached data (if available) and notifies the user: "Some data may be outdated." 53 | - The system logs the error for admin review. 54 | - **Invalid Search Query**: 55 | - If the user enters an invalid or empty query, the system displays an error message: "Please enter a valid search term." 56 | 57 | --- 58 | 59 | ### Use Case 2: Compare Prices 60 | 61 | --- 62 | ### Description 63 | - **Purpose**: To enable users to compare prices of a product across multiple retailers. 64 | - **Scope**: Covers the process of retrieving prices, displaying comparisons, and allowing users to filter results. 65 | 66 | ### Preconditions 67 | - User has searched for a product. 68 | - Retailers have provided pricing data. 69 | - System is connected to data sources. 70 | 71 | ### Postconditions 72 | - User views a comparison chart of prices across retailers. 73 | - System logs the comparison for future recommendations. 74 | 75 | ### Basic Flow 76 | 1. User selects a product from the search results. 77 | 2. System retrieves pricing data for the product from multiple retailers. 78 | 3. System displays a comparison chart showing prices, retailer names, and availability. 79 | 4. User applies filters (e.g., sort by price, filter by location). 80 | 5. User selects a retailer to view more details or make a purchase. 81 | 82 | ### Alternative Flows 83 | - **No Retailer Data Available**: 84 | - If no retailer data is available, the system displays a message: "No pricing data available for this product." 85 | - **Retailer Out of Stock**: 86 | - If a retailer is out of stock, the system displays: "Out of Stock" next to the retailer’s name. 87 | - **API Failure**: 88 | - If the system cannot retrieve live data, it uses cached data and notifies the user: "Data may not be up to date." 89 | 90 | --- 91 | 92 | ### Use Case 3: Subscribe to Price Drop Alerts 93 | 94 | --- 95 | 96 | 97 | ### Description 98 | - **Purpose**: To enable users to receive notifications when the price of a product drops. 99 | - **Scope**: Covers the process of subscribing to alerts, monitoring price changes, and sending notifications. 100 | 101 | ### Preconditions 102 | - User is logged into their account. 103 | - Product is available in the system. 104 | - System has permission to send notifications (e.g., email or SMS). 105 | 106 | ### Postconditions 107 | - User is subscribed to price drop alerts for the selected product. 108 | - System monitors the product’s price and schedules notifications. 109 | 110 | ### Basic Flow 111 | 1. User selects a product and clicks the "Subscribe to Price Alerts" button. 112 | 2. System prompts the user to confirm their notification preferences (e.g., email or SMS). 113 | 3. User confirms their preferences. 114 | 4. System adds the product to the user’s alert list and begins monitoring its price. 115 | 5. System sends a confirmation message: "You are now subscribed to price alerts for [Product Name]." 116 | 117 | ### Alternative Flows 118 | - **User Not Logged In**: 119 | - If the user is not logged in, the system prompts them to log in or create an account. 120 | - After logging in, the system resumes the subscription process. 121 | - **Notification Preferences Not Set**: 122 | - If the user has not set notification preferences, the system prompts them to configure preferences before subscribing. 123 | - **Product No Longer Available**: 124 | - If the product is removed from the system, the system notifies the user: "This product is no longer available. Your subscription has been canceled." 125 | 126 | --- 127 | 128 | ### Use Case 4: Update Pricing (Retailer) 129 | 130 | --- 131 | 132 | ### Description 133 | - **Purpose**: To allow retailers to update product pricing and availability in the system. 134 | - **Scope**: Covers the process of uploading or syncing pricing data with the system. 135 | 136 | ### Preconditions 137 | - Retailer has access to the system (e.g., via API or admin portal). 138 | - Pricing data is ready for upload or syncing. 139 | 140 | ### Postconditions 141 | - Product pricing and availability are updated in the system. 142 | - System reflects the latest data for user searches and comparisons. 143 | 144 | ### Basic Flow 145 | 1. Retailer logs into the system or connects via API. 146 | 2. Retailer uploads or syncs pricing data for their products. 147 | 3. System validates the data and updates the database. 148 | 4. System confirms the update: "Pricing data updated successfully." 149 | 150 | ### Alternative Flows 151 | - **Invalid Data Format**: 152 | - If the data format is invalid, the system rejects the upload and notifies the retailer: "Invalid data format. Please check your file." 153 | - **Data Conflict**: 154 | - If there is a conflict with existing data (e.g., duplicate entries), the system prompts the retailer to resolve the conflict. 155 | - **API Connection Failure**: 156 | - If the API connection fails, the system notifies the retailer: "Connection failed. Please try again later." 157 | 158 | --- 159 | 160 | ### Use Case 5: Manage System Data (Admin) 161 | 162 | --- 163 | ### Description 164 | - **Purpose**: To allow admins to manage system data, ensure accuracy, and resolve issues. 165 | - **Scope**: Covers data management, error resolution, and system monitoring. 166 | 167 | ### Preconditions 168 | - Admin has access to the admin portal. 169 | - System is operational and connected to data sources. 170 | 171 | ### Postconditions 172 | - System data is accurate and up to date. 173 | - Errors are resolved, and system performance is optimized. 174 | 175 | ### Basic Flow 176 | 1. Admin logs into the admin portal. 177 | 2. Admin reviews system data (e.g., product listings, pricing, user logs). 178 | 3. Admin resolves any data inconsistencies or errors. 179 | 4. Admin monitors system performance and optimizes as needed. 180 | 5. System confirms successful updates or changes. 181 | 182 | ### Alternative Flows 183 | - **Data Corruption**: 184 | - If data corruption is detected, the system notifies the admin and provides recovery options. 185 | - **Unauthorized Access**: 186 | - If an unauthorized user attempts to access the admin portal, the system blocks access and logs the attempt. 187 | - **System Outage**: 188 | - If the system experiences an outage, the admin is notified and provided with troubleshooting steps. 189 | 190 | --- 191 | 192 | ### Use Case 6: Publish Promotions (Advertiser) 193 | 194 | --- 195 | 196 | ### Description 197 | - **Purpose**: To allow advertisers to publish promotional offers and targeted deals. 198 | - **Scope**: Covers the process of creating, uploading, and displaying promotions. 199 | 200 | ### Preconditions 201 | - Advertiser has access to the system (e.g., via API or advertiser portal). 202 | - Promotional data is ready for publication. 203 | 204 | ### Postconditions 205 | - Promotions are published and displayed to users. 206 | - System logs promotional performance for analytics. 207 | 208 | ### Basic Flow 209 | 1. Advertiser logs into the system or connects via API. 210 | 2. Advertiser uploads promotional data (e.g., discounts, deals). 211 | 3. System validates the data and publishes the promotions. 212 | 4. System confirms the publication: "Promotions published successfully." 213 | 214 | ### Alternative Flows 215 | - **Invalid Promotion Data**: 216 | - If the promotional data is invalid, the system rejects the upload and notifies the advertiser: "Invalid data. Please check your file." 217 | - **Promotion Conflict**: 218 | - If there is a conflict with existing promotions, the system prompts the advertiser to resolve the conflict. 219 | - **API Connection Failure**: 220 | - If the API connection fails, the system notifies the advertiser: "Connection failed. Please try again later." 221 | 222 | --- 223 | 224 | ### Use Case 7: Provide Real-Time Data (Data Provider) 225 | 226 | --- 227 | 228 | ### Description 229 | - **Purpose**: To allow data providers to supply real-time product pricing and availability data. 230 | - **Scope**: Covers the process of syncing data with the system. 231 | 232 | ### Preconditions 233 | - Data provider has access to the system (e.g., via API). 234 | - Real-time data is ready for syncing. 235 | 236 | ### Postconditions 237 | - System reflects the latest product pricing and availability data. 238 | - Data provider’s updates are successfully integrated. 239 | 240 | ### Basic Flow 241 | 1. Data provider connects to the system via API. 242 | 2. Data provider syncs real-time pricing and availability data. 243 | 3. System validates the data and updates the database. 244 | 4. System confirms the sync: "Data synced successfully." 245 | 246 | ### Alternative Flows 247 | - **Invalid Data Format**: 248 | - If the data format is invalid, the system rejects the sync and notifies the data provider: "Invalid data format. Please check your file." 249 | - **Data Conflict**: 250 | - If there is a conflict with existing data, the system prompts the data provider to resolve the conflict. 251 | - **API Connection Failure**: 252 | - If the API connection fails, the system notifies the data provider: "Connection failed. Please try again later." 253 | --- 254 | 255 | [Back to Test and Use Case Document.md](Test%20and%20Use%20Case%20Document.md) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Price aggregator comparison app [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 2 | 3 | 4 | ### A pricing aggregator and comparison application that aims to consolidate information from major grocery stores, allowing users find the best deals on food and beverages in a central location. 5 | 6 | --- 7 | ## Key Features 8 | 9 | * **Search Functionality:** Users can search for specific items. 10 | * **Comparison Engine:** Displays the price differences across multiple retailers. 11 | * **Filter Options:** Allow users to sort by price, location, or retailer. 12 | * **Geolocation Integration:** Suggest deals available nearby or within a specific radius. 13 | * **Daily Specials:** Highlight discounted items or promotions from stores. 14 | * **Notifications:** Send alerts for new deals or when prices drop for a user’s favorite items. 15 | * **Save or share:** Save list to notes or share via text applications. 16 | 17 | --- 18 | 19 | ## **_Quick Links_** 20 | 21 | ### # 🧪 How to Run Tests Locally 22 | ➡️ [Click here](#-how-to-run-tests-locally) 23 | 24 | ### 🔄 CI/CD Pipeline Workflow 25 | ➡️ [Click here](#-ci/cd-pipeline-workflow) 26 | 27 | --- 28 | 29 | ### Language Choice & Key Design Decisions 30 | 31 | ➡️ [Click here](#-language-choice-python) 32 | 33 | ### Factory Pattern 34 | ➡️ [Click here](#Storage-Abstraction-Mechanism) 35 | 36 | ### Repository Interface Design 37 | 38 | ➡️ [Click here](#-Justification-for-Repository-Design) 39 | 40 | --- 41 | ## Getting Started 42 | 43 | Follow the steps below to run the project locally: 44 | 45 | ### 📦 Prerequisites 46 | - Python 3.10+ 47 | - pip (Python package installer) 48 | 49 | ### ⚙️ Setup Instructions 50 | 51 | 1. **Clone the Repository** 52 | ```bash 53 | git clone https://github.com/your-username/price-aggregator-app.git 54 | cd price-aggregator-app 55 | ``` 56 | 2. Create and Activate Virtual Environment 57 | ```bash 58 | python -m venv venv 59 | source venv/bin/activate # On Windows: venv\Scripts\activate 60 | ``` 61 | 3. Install Dependencies 62 | ```bash 63 | pip install -r requirements.txt 64 | ``` 65 | 4. Run the Application 66 | ```bash 67 | uvicorn src.main:app --reload 68 | ``` 69 | 5. Access the API 70 | ```bash 71 | Visit http://127.0.0.1:8000/docs for the Swagger UI documentation. 72 | ``` 73 | ### Features for Contribution 74 | 75 | | Feature | Description | Status | 76 | |---------------------------------|-------------------------------------------------------------------|--------------| 77 | | **Product CRUD** | Add, update, delete, and retrieve product information. | ✅ Implemented | 78 | | **Price Comparison Engine** | Compare product prices across stores. | ✅ Implemented | 79 | | **User Authentication** | Register/login/logout functionality. | 🚧 In Progress | 80 | | **Wishlist/Saved List** | Users can save products to track later. | ✅ Implemented | 81 | | **Notifications/Alerts** | Price drop alerts and daily deal notifications. | ✅ Implemented | 82 | | **Retailer Profile Management** | Maintain and edit store and retailer info. | ✅ Implemented | 83 | | **Promotion & Discount Management** | Track and display active discounts from various retailers. | ✅ Implemented | 84 | | **Review and Rating System** | Users can leave reviews/ratings on products and stores. | 🛠 Planned | 85 | | **Store Geolocation Integration** | Find nearby stores with the best prices. | ✅ Implemented | 86 | | **Multi-source Data Ingestion** | Integrate more store APIs for better price data coverage. | 🛠 Planned | 87 | | **Mobile Responsiveness** | Make UI mobile-friendly (for web apps). | 🛠 Planned | 88 | 89 | --- 90 | 91 | Folder structure 92 | 93 | Price-aggregator-comparison-app/ 94 | .github/ 95 | .venv/ 96 | docs/ 97 | │ 98 | ├── agile_planning/ 99 | │ ├── agile_planning_document.md 100 | │ ├── backlog.md 101 | │ ├── sprint_planning.md 102 | │ └── user_stories.md 103 | │ 104 | ├── CICD_implementation/ 105 | │ ├── branch_protection_rules.png 106 | │ └── protection.md 107 | │ 108 | ├── diagrams/ 109 | │ ├── activity_diagrams/ 110 | │ │ ├── activity_diagrams.md 111 | │ │ ├── price_alert.md 112 | │ │ ├── product.md 113 | │ │ ├── retailer_profile.md 114 | │ │ └── user_account.md 115 | │ ├── state_transition_diagrams/ 116 | │ │ ├── price_alert.md 117 | │ │ ├── product.md 118 | │ │ ├── retailer_profile.md 119 | │ │ ├── state_transition_diagrams.md 120 | │ │ └── user_account.md 121 | │ └── Traceability Matrix.md 122 | ├── domain_model/ 123 | │ ├── class_diagram.js 124 | │ ├── class_diagram_updated.js 125 | │ ├── domain_model_documentation.md 126 | │ └── explanation_of_the_class_diagram.md 127 | │ 128 | ├── kanban/ 129 | │ ├── index.md 130 | │ ├── kanban_board.md 131 | │ ├── kanban_explanation.md 132 | │ ├── template_analysis.md 133 | │ └── update 20250504.jpg 134 | │ 135 | ├── specification/ 136 | │ ├── architecture.md 137 | │ ├── specification.md 138 | │ ├── stakeholder_analysis.md 139 | │ └── system_requirements_document.md 140 | │ 141 | └── test_use_case_documentation/ 142 | ├── test_case_development.md 143 | ├── test_use_case_documentation.md 144 | ├── use_case_diagrams.md 145 | └── use_case_specifications.md 146 | src/ 147 | │ 148 | ├── main/ 149 | │ ├── __init__.py 150 | │ ├── run_scraper.py # Entry point for scraper 151 | │ ├── app.py # Main orchestrator (optional) 152 | │ ├── compare_prices.py # Optional comparison logic 153 | │ ├── requirements.txt 154 | │ │ 155 | │ ├── api/ 156 | │ │ ├── __init__.py 157 | │ │ ├── price_api.py 158 | │ │ ├── product_api.py 159 | │ │ └── retailer_api.py 160 | │ │ 161 | │ ├── models/ 162 | │ │ ├── __init__.py 163 | │ │ ├── price.py 164 | │ │ ├── price_alert.py 165 | │ │ ├── product.py 166 | │ │ ├── promotion.py 167 | │ │ ├── retailer.py 168 | │ │ ├── saved_list.py 169 | │ │ └── user.py 170 | │ │ 171 | │ ├── repositories/ 172 | │ │ ├── __init__.py 173 | │ │ ├── repository.py 174 | │ │ ├── price_alert_repository.py 175 | │ │ ├── product_repository.py 176 | │ │ ├── promotion_repository.py 177 | │ │ ├── retailer_repository.py 178 | │ │ ├── saved_list_repository.py 179 | │ │ ├── user_repository.py 180 | │ │ └── inmemory/ 181 | │ │ ├── __init__.py 182 | │ │ ├── inmemory_price_alert_repository.py 183 | │ │ ├── inmemory_product_repository.py 184 | │ │ ├── inmemory_promotion_repository.py 185 | │ │ ├── inmemory_retailer_repository.py 186 | │ │ ├── inmemory_saved_list_repository.py 187 | │ │ └── inmemory_user_repository.py 188 | │ │ 189 | │ ├── services/ 190 | │ │ ├── __init__.py 191 | │ │ ├── price_service.py 192 | │ │ ├── product_service.py 193 | │ │ └── retailer_service.py 194 | │ │ 195 | │ └── factories/ 196 | │ ├── __init__.py 197 | │ └── repository_factory.py 198 | │ 199 | ├── scraper/ 200 | │ ├── __init__.py 201 | │ ├── checkers_scraper.py 202 | │ ├── picknpay_scraper.py 203 | │ └── woolworths_scraper.py 204 | │ 205 | ├── tests/ 206 | │ ├── __init__.py 207 | │ ├── unit/ 208 | │ │ ├── __init__.py 209 | │ │ ├── test_basic.py 210 | │ │ ├── test_product.py 211 | │ │ └── test_user.py 212 | │ └── integration/ 213 | │ ├── __init__.py 214 | │ └── test_integration.py 215 | │ 216 | .gitignore 217 | README.md 218 | changelog.md 219 | pyproject.toml 220 | setup.py 221 | 222 | --- 223 | 224 | ## Initial sprint Documentation 225 | [Agile planning document](docs/agile_planning/agile_planning_document.md) 226 | 227 | ## Test and use case Documentation 228 | 229 | * [Test and Use Case Document](docs/Test%20and%20Use%20Case%20Documentation/Test%20and%20Use%20Case%20Document.md) 230 | 231 | ## Additional Documentation 232 | 233 | * Specifications: [specification.md](docs/specification/specification.md) 234 | * Architecture: [architecture.md](docs/specification/architecture.md) 235 | * System Requirements Document (SRD):[system_requirements_document.md](docs/specification/system_requirements_document.md) 236 | * Stakeholder Analysis: [stakeholder_analysis.md](docs/specification/stakeholder_analysis.md) 237 | 238 | --- 239 | 240 | ## 🔄 UML State Transition Diagrams 241 | 242 | This project models key system behaviors using **UML State Transition Diagrams** to support planning, design, and traceability. 243 | 244 | ### 📊 Diagrams Overview 245 | | Object | Activity Diagram & Explanation | State Transition Diagram & Explanation | 246 | |------------------|--------------------------------------------------------------------|-------------------------------------------------------------------------| 247 | | User Account | [View](docs/diagrams/Activity%20Diagrams/user_account.md) | [View](./docs/explanations/user_account.md) | 248 | | User Login & Authentication | [View](Diagrams/Activity%20Diagrams/user_account.md) | [View](./docs/explanations/user_account.md) | 249 | | Product | [View](docs/diagrams/Activity%20Diagrams/product.md) | [View](docs/diagrams/State%20Transition%20Diagrams/product.md) | 250 | | Price Alert | [View](docs/diagrams/Activity%20Diagrams/price_alert.md) | [View](docs/diagrams/State%20Transition%20Diagrams/price_alert.md) | 251 | | Retailer Profile | [View](docs/diagrams/Activity%20Diagrams/retailer_profile.md) | [View](docs/diagrams/State%20Transition%20Diagrams/retailer_profile.md) | 252 | | Saved List | [View](Diagrams/Activity%20Diagrams/user_account.md) | [View](./docs/explanations/user_account.md) | 253 | | Subscription | [View](Diagrams/Activity%20Diagrams/user_account.md) | [View](./docs/explanations/user_account.md) | 254 | | Retailer Deal Submission | [View](Diagrams/Activity%20Diagrams/user_account.md) | [View](./docs/explanations/user_account.md) | 255 | 256 | --- 257 | Overview 258 | 259 | * [Activity Diagrams.md](docs/diagrams/Activity%20Diagrams/Activity%20Diagrams.md) 260 | * [State Transition Diagrams.md](docs/diagrams/State%20Transition%20Diagrams/State%20Transition%20Diagrams.md) 261 | --- 262 | 263 | ### 📌 Functional Mapping 264 | 265 | Each diagram supports functional requirements and sprint work outlined in: 266 | 267 | * [SRD.md](docs/specification/system_requirements_document.md) 268 | * [Agile Planning Document.md](Agile%20Planning/Agile%20Planning%20Document.md) 269 | 270 | --- 271 | 272 | | Diagram/Object | Mapped Functional Requirement (FR) | User Story / Sprint Task Reference | 273 | |----------------------------|--------------------------------------------------|------------------------------------------------------------------------------------| 274 | | User Account | FR-001: User account management | US-01: "As a user, I want to sign up and manage my profile." | 275 | | Product | FR-002: Retailers submit/manage items | US-02: "As a retailer, I want to list and update product info." | 276 | | Price Alert | FR-006: Price drop alerts | US-05: "As a user, I want to subscribe to alerts for savings." | 277 | | Retailer Dashboard Access | FR-007: Dashboard permission control | US-06: "As a retailer, I need access to update prices." | 278 | | Advertisement Campaign | FR-008: Ad campaign lifecycle | US-07: "As an advertiser, I want to schedule promotions." | 279 | | Promotion Content | FR-009: Filtered promotions view | US-08: "As a user, I want to filter deals by retailer." | 280 | | Saved List | FR-010: Saved items | US-09: "As a user, I want to save my favorite products." | 281 | 282 | --- 283 | 284 | ## Class Implementation 285 | 286 | --- 287 | 288 | ### 🐍 Language Choice: Python 289 | 290 | * Readability & Simplicity 291 | * Strong Support for OOP 292 | * Testing Support 293 | * Cross-Platform 294 | 295 | I chose Python for this project because I have some prior knowledge of the language, and it supports object-oriented programming. Python made it easier to define the relationships between classes, which aligned well with the class diagram I had previously created. It also allowed me to clearly structure the methods and responsibilities within each class, and accurately implement the attributes and logic outlined in my domain model. 296 | 297 | --- 298 | 299 | ### 🧠 Key Design Decisions 300 | 301 | Modular Design with Single Responsibility Principle (SRP) 302 | Each class was designed with a clear responsibility: 303 | 304 | * User: manages login and logout - [user.py](src/main/models/user.py) 305 | * Product: holds product details - [product.py](src/main/models/product.py) 306 | * SavedList: allows users to save preferred items - [saved_list.py](src/main/models/saved_list.py) 307 | * Retailer: stores information about stores and available products - [retailer.py](src/main/models/retailer.py) 308 | * Promotion: handles discount logic - [promotion.py](src/main/models/promotion.py) 309 | * PriceAlert: notifies users when price thresholds are met - [price_alert.py](src/main/models/price_alert.py) 310 | 311 | --- 312 | 313 | [**Test cases**](src/price_aggregator/tests) 314 | 315 | --- 316 | 317 | ### ✅ Running Tests 318 | 319 | To run the test suite locally: 320 | 321 | 1. Ensure you have `pytest` installed: 322 | ```bash 323 | pip install pytest 324 | 325 | 2. Navigate to the root directory and run: 326 | ```bash 327 | pytest src/tests 328 | 329 | --- 330 | 331 | # Repository Interface Design 332 | 333 | ### ✅ Justification for Repository Design 334 | 335 | * **Generic Repository Interface:**
336 | By using a generic repository, we avoid writing duplicate methods for each type of object we want 337 | to store. Instead, we can use this one interface for all entities. 338 | 339 | * **Entity-Specific Repositories:**
340 | The ProductRepository is specific to the Product entity. It provides the implementation of the CRUD operations using an in-memory storage (_storage dictionary). 341 | Other repositories (like RetailerRepository) will follow the same pattern but focus on their respective entities. 342 | 343 | * **In-Memory Implementation:**
344 | In-memory storage is fast and easy to implement. We don’t need an external database for now, and it allows for quick testing of CRUD functionality. 345 | It’s also ideal for unit testing, since it doesn’t rely on any external dependencies. 346 | 347 | * **Abstraction with Factory Pattern:**
348 | The Factory Pattern allows easy switching between different repository implementations without modifying other parts of the codebase. 349 | We can swap out the in-memory store with other storage options (like a database or file system) without changing the business logic. 350 | 351 | * **Future-Proofing for Other Backends:**
352 | We are future-proofing by designing the system to easily switch storage backends. Adding a new storage type (e.g., database) will be straightforward. 353 | This ensures the application can scale when switching to a more permanent solution like a database. 354 | 355 | ---- 356 | ### Storage-Abstraction Mechanism 357 | 358 | ✅ Why the Factory Pattern 359 | 360 | ### 🔁 1. Support multiple storage backends 361 | 362 | * App may start with in-memory storage, but later Migrate to a PostgreSQL or MongoDB backend. 363 | * Add support for external APIs or cloud databases. 364 | * A RepositoryFactory allows you to easily switch. 365 | 366 | ### 🧱 2. Clear separation of concerns 367 | 368 | * The factory pattern keeps your services clean. They ask the factory for a repository — without knowing or caring how it’s implemented. 369 | 370 | ### ⚙️ 3. Easy extensibility 371 | * You can plug in new backends (e.g., FirestorePromotionRepository, RedisCacheRepository) without changing your service logic. 372 | 373 | ### 🧪 4. Simpler for testing 374 | 375 | * You can inject mock or test versions of repositories easily via the factory. 376 | 377 | ---- 378 | 379 | # 🧪 How to Run Tests Locally 380 | 381 | To run all unit tests locally using `pytest`, follow these steps: 382 | 383 | ```bash 384 | # Step 1: Create and activate a virtual environment 385 | python -m venv venv 386 | source venv/bin/activate # Use venv\Scripts\activate on Windows 387 | 388 | # Step 2: Install dependencies 389 | pip install -r requirements.txt 390 | 391 | # Step 3: Run tests 392 | pytest 393 | ``` 394 | 395 | # 🔄 CI/CD Pipeline Workflow 396 | 397 | This project uses GitHub Actions for Continuous Integration and Deployment. 398 | 399 | ## CI/CD Steps 400 | 401 | ### ✅ Every Push / PR: 402 | 403 | - Triggers `pytest` to run all tests. 404 | - Linting and formatting checks (e.g., `black`, `flake8`) are optional but recommended. 405 | 406 | ### 🚫 On Pull Requests: 407 | 408 | - PRs are blocked from merging if any test fails. 409 | - GitHub will show a red ❌ and prevent merge until all checks pass ✅. 410 | 411 | ### 📦 On Merge to `main`: 412 | 413 | - Artifacts (e.g., reports or build assets) are generated. 414 | - Optionally, deployment steps (e.g., Docker image build or pushing to a test server) can be triggered. 415 | 416 | --- 417 | --------------------------------------------------------------------------------