Commit ·
0e87fe4
1
Parent(s): c8cde52
feat: Implement Smart Tips feature with service, router, and schema
Browse files- app/app.py +13 -1
- app/routers/smart_tips_router.py +53 -0
- app/schemas/smart_tips_schema.py +12 -0
- app/services/smart_tips_service.py +93 -0
- seed_smart_tips.py +28 -0
app/app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from fastapi import FastAPI, Request
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
| 4 |
-
from app.routers import auth_router, merchant_profile_router, merchant_router, merchant_settings_router, role_router,branch_router, location_router
|
| 5 |
import logging
|
| 6 |
from app.utils.logger import setup_logger
|
| 7 |
|
|
@@ -68,6 +68,18 @@ app.include_router(auth_router.router)
|
|
| 68 |
app.include_router(role_router.router, prefix="/api", tags=["Roles"])
|
| 69 |
app.include_router(branch_router.router,prefix="/api/branch",tags=["Branch"])
|
| 70 |
app.include_router(location_router.router, prefix="/api", tags=["Locations"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
@app.get("/")
|
| 73 |
def root():
|
|
|
|
| 1 |
from fastapi import FastAPI, Request
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
| 4 |
+
from app.routers import auth_router, merchant_profile_router, merchant_router, merchant_settings_router, role_router, branch_router, location_router, smart_tips_router
|
| 5 |
import logging
|
| 6 |
from app.utils.logger import setup_logger
|
| 7 |
|
|
|
|
| 68 |
app.include_router(role_router.router, prefix="/api", tags=["Roles"])
|
| 69 |
app.include_router(branch_router.router,prefix="/api/branch",tags=["Branch"])
|
| 70 |
app.include_router(location_router.router, prefix="/api", tags=["Locations"])
|
| 71 |
+
app.include_router(smart_tips_router.router, prefix="/api", tags=["SmartTips"])
|
| 72 |
+
|
| 73 |
+
@app.on_event("startup")
|
| 74 |
+
async def startup_event():
|
| 75 |
+
"""
|
| 76 |
+
Initialize necessary data at application startup
|
| 77 |
+
"""
|
| 78 |
+
from app.services.smart_tips_service import SmartTipsService
|
| 79 |
+
logger.info("Initializing smart tips collection")
|
| 80 |
+
service = SmartTipsService()
|
| 81 |
+
await service.ensure_collection_exists()
|
| 82 |
+
logger.info("Smart tips initialization complete")
|
| 83 |
|
| 84 |
@app.get("/")
|
| 85 |
def root():
|
app/routers/smart_tips_router.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from fastapi import APIRouter, HTTPException, Depends, Query
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
from app.schemas.smart_tips_schema import SmartTip
|
| 5 |
+
from app.services.smart_tips_service import SmartTipsService
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger("smart_tips_router")
|
| 8 |
+
router = APIRouter(tags=["SmartTips"])
|
| 9 |
+
|
| 10 |
+
# Define mandatory features
|
| 11 |
+
MANDATORY_FEATURES = ["asset", "associate"]
|
| 12 |
+
|
| 13 |
+
async def get_smart_tips_service():
|
| 14 |
+
"""
|
| 15 |
+
Dependency to get a configured SmartTipsService instance
|
| 16 |
+
"""
|
| 17 |
+
service = SmartTipsService()
|
| 18 |
+
await service.ensure_collection_exists()
|
| 19 |
+
return service
|
| 20 |
+
|
| 21 |
+
@router.get("/smart-tips", response_model=List[SmartTip])
|
| 22 |
+
async def get_smart_tips(
|
| 23 |
+
features: Optional[List[str]] = Query(None, description="Filter tips by feature(s)"),
|
| 24 |
+
include_mandatory: bool = Query(True, description="Include mandatory features (asset, associate)"),
|
| 25 |
+
smart_tips_service: SmartTipsService = Depends(get_smart_tips_service)
|
| 26 |
+
):
|
| 27 |
+
"""
|
| 28 |
+
Get a list of smart tips for different features.
|
| 29 |
+
|
| 30 |
+
- Optional query parameter to filter by features
|
| 31 |
+
- Always includes mandatory features if include_mandatory=True
|
| 32 |
+
"""
|
| 33 |
+
try:
|
| 34 |
+
logger.info("Fetching smart tips from MongoDB")
|
| 35 |
+
|
| 36 |
+
# Handle mandatory features
|
| 37 |
+
if include_mandatory:
|
| 38 |
+
if features:
|
| 39 |
+
# Include mandatory features with user-requested features
|
| 40 |
+
request_features = list(set(features + MANDATORY_FEATURES))
|
| 41 |
+
return await smart_tips_service.get_smart_tips_by_features(request_features)
|
| 42 |
+
else:
|
| 43 |
+
# Include mandatory features with all other features
|
| 44 |
+
return await smart_tips_service.get_smart_tips_with_mandatory(MANDATORY_FEATURES)
|
| 45 |
+
else:
|
| 46 |
+
# Filter by features if provided, otherwise return all
|
| 47 |
+
if features:
|
| 48 |
+
return await smart_tips_service.get_smart_tips_by_features(features)
|
| 49 |
+
else:
|
| 50 |
+
return await smart_tips_service.get_all_smart_tips()
|
| 51 |
+
except Exception as e:
|
| 52 |
+
logger.error(f"Error fetching smart tips: {str(e)}")
|
| 53 |
+
raise HTTPException(status_code=500, detail=str(e))
|
app/schemas/smart_tips_schema.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Optional
|
| 2 |
+
from pydantic import BaseModel, Field, RootModel
|
| 3 |
+
|
| 4 |
+
class SmartTip(BaseModel):
|
| 5 |
+
id: str
|
| 6 |
+
title: str
|
| 7 |
+
description: str
|
| 8 |
+
icon: Optional[str] = Field(None, description="Must match a valid Lucide icon name: Target, BookOpen, AlertCircle, Clock, FileText, Save, Tag, BarChart3, Users, Settings, Lightbulb")
|
| 9 |
+
feature: str = Field(..., description="Feature category this tip belongs to (e.g., asset, associate, catalogue)")
|
| 10 |
+
|
| 11 |
+
class SmartTipsResponse(RootModel):
|
| 12 |
+
root: List[SmartTip]
|
app/services/smart_tips_service.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import List, Optional
|
| 3 |
+
from app.nosql import db
|
| 4 |
+
from app.schemas.smart_tips_schema import SmartTip
|
| 5 |
+
|
| 6 |
+
logger = logging.getLogger("smart_tips_service")
|
| 7 |
+
|
| 8 |
+
class SmartTipsService:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.collection = db["smart_tips"]
|
| 11 |
+
|
| 12 |
+
async def ensure_collection_exists(self):
|
| 13 |
+
"""
|
| 14 |
+
Check if the collection exists and has data. If not, seed with initial data.
|
| 15 |
+
"""
|
| 16 |
+
count = await self.collection.count_documents({})
|
| 17 |
+
if count == 0:
|
| 18 |
+
await self.seed_initial_data()
|
| 19 |
+
logger.info("Smart tips collection seeded with initial data")
|
| 20 |
+
|
| 21 |
+
async def seed_initial_data(self):
|
| 22 |
+
"""
|
| 23 |
+
Seed the collection with initial smart tips data
|
| 24 |
+
"""
|
| 25 |
+
initial_tips = [
|
| 26 |
+
{
|
| 27 |
+
"id": "tip-1",
|
| 28 |
+
"title": "Complete in Order",
|
| 29 |
+
"description": "Fill out sections sequentially for the best experience.",
|
| 30 |
+
"icon": "Target",
|
| 31 |
+
"feature": "general"
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"id": "tip-2",
|
| 35 |
+
"title": "Associate Management",
|
| 36 |
+
"description": "Add all your team members for better role management.",
|
| 37 |
+
"icon": "Users",
|
| 38 |
+
"feature": "associate"
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"id": "tip-3",
|
| 42 |
+
"title": "Catalogue Best Practices",
|
| 43 |
+
"description": "Keep your product details consistent for better customer experience.",
|
| 44 |
+
"icon": "BookOpen",
|
| 45 |
+
"feature": "catalogue"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"id": "tip-4",
|
| 49 |
+
"title": "Asset Management",
|
| 50 |
+
"description": "Regularly update your asset inventory for accurate financial reporting.",
|
| 51 |
+
"icon": "FileText",
|
| 52 |
+
"feature": "asset"
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"id": "tip-5",
|
| 56 |
+
"title": "Time Management",
|
| 57 |
+
"description": "Schedule regular inventory checks to maintain accuracy.",
|
| 58 |
+
"icon": "Clock",
|
| 59 |
+
"feature": "general"
|
| 60 |
+
}
|
| 61 |
+
]
|
| 62 |
+
await self.collection.insert_many(initial_tips)
|
| 63 |
+
|
| 64 |
+
async def get_all_smart_tips(self) -> List[SmartTip]:
|
| 65 |
+
"""
|
| 66 |
+
Get all smart tips from the database
|
| 67 |
+
"""
|
| 68 |
+
cursor = self.collection.find({})
|
| 69 |
+
tips = await cursor.to_list(length=100)
|
| 70 |
+
return [SmartTip(**tip) for tip in tips]
|
| 71 |
+
|
| 72 |
+
async def get_smart_tips_by_features(self, features: List[str]) -> List[SmartTip]:
|
| 73 |
+
"""
|
| 74 |
+
Get smart tips filtered by a list of features
|
| 75 |
+
"""
|
| 76 |
+
cursor = self.collection.find({"feature": {"$in": features}})
|
| 77 |
+
tips = await cursor.to_list(length=100)
|
| 78 |
+
return [SmartTip(**tip) for tip in tips]
|
| 79 |
+
|
| 80 |
+
async def get_smart_tips_with_mandatory(self, mandatory_features: List[str]) -> List[SmartTip]:
|
| 81 |
+
"""
|
| 82 |
+
Get all smart tips ensuring mandatory features are included
|
| 83 |
+
"""
|
| 84 |
+
# First, get tips from mandatory features
|
| 85 |
+
mandatory_tips = await self.get_smart_tips_by_features(mandatory_features)
|
| 86 |
+
|
| 87 |
+
# Then get all other tips
|
| 88 |
+
cursor = self.collection.find({"feature": {"$nin": mandatory_features}})
|
| 89 |
+
other_tips = await cursor.to_list(length=100)
|
| 90 |
+
other_tips = [SmartTip(**tip) for tip in other_tips]
|
| 91 |
+
|
| 92 |
+
# Combine both sets
|
| 93 |
+
return mandatory_tips + other_tips
|
seed_smart_tips.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Script to seed the smart_tips collection in MongoDB
|
| 3 |
+
Run this script to initialize the collection if needed
|
| 4 |
+
"""
|
| 5 |
+
import asyncio
|
| 6 |
+
import logging
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# Add the parent directory to sys.path to import app modules
|
| 11 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 12 |
+
|
| 13 |
+
from app.services.smart_tips_service import SmartTipsService
|
| 14 |
+
|
| 15 |
+
logging.basicConfig(level=logging.INFO)
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
async def seed_smart_tips():
|
| 19 |
+
"""
|
| 20 |
+
Seed the smart_tips collection with initial data
|
| 21 |
+
"""
|
| 22 |
+
logger.info("Starting smart tips collection seeding process")
|
| 23 |
+
service = SmartTipsService()
|
| 24 |
+
await service.ensure_collection_exists()
|
| 25 |
+
logger.info("Smart tips collection seeding completed")
|
| 26 |
+
|
| 27 |
+
if __name__ == "__main__":
|
| 28 |
+
asyncio.run(seed_smart_tips())
|