imporve features
This commit is contained in:
123
services/external/app/api/calendar_operations.py
vendored
123
services/external/app/api/calendar_operations.py
vendored
@@ -213,17 +213,17 @@ async def check_is_school_holiday(
|
||||
response_model=TenantLocationContextResponse
|
||||
)
|
||||
async def get_tenant_location_context(
|
||||
tenant_id: UUID = Depends(get_current_user_dep),
|
||||
tenant_id: str = Path(..., description="Tenant ID"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get location context for a tenant including school calendar assignment (cached)"""
|
||||
try:
|
||||
tenant_id_str = str(tenant_id)
|
||||
|
||||
# Check cache first
|
||||
cached = await cache.get_cached_tenant_context(tenant_id_str)
|
||||
cached = await cache.get_cached_tenant_context(tenant_id)
|
||||
if cached:
|
||||
logger.debug("Returning cached tenant context", tenant_id=tenant_id_str)
|
||||
logger.debug("Returning cached tenant context", tenant_id=tenant_id)
|
||||
return TenantLocationContextResponse(**cached)
|
||||
|
||||
# Cache miss - fetch from database
|
||||
@@ -261,11 +261,16 @@ async def get_tenant_location_context(
|
||||
)
|
||||
async def create_or_update_tenant_location_context(
|
||||
request: TenantLocationContextCreateRequest,
|
||||
tenant_id: UUID = Depends(get_current_user_dep),
|
||||
tenant_id: str = Path(..., description="Tenant ID"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Create or update tenant location context"""
|
||||
try:
|
||||
|
||||
# Convert to UUID for use with repository
|
||||
tenant_uuid = UUID(tenant_id)
|
||||
|
||||
repo = CalendarRepository(db)
|
||||
|
||||
# Validate calendar_id if provided
|
||||
@@ -279,7 +284,7 @@ async def create_or_update_tenant_location_context(
|
||||
|
||||
# Create or update context
|
||||
context_obj = await repo.create_or_update_tenant_location_context(
|
||||
tenant_id=tenant_id,
|
||||
tenant_id=tenant_uuid,
|
||||
city_id=request.city_id,
|
||||
school_calendar_id=request.school_calendar_id,
|
||||
neighborhood=request.neighborhood,
|
||||
@@ -288,13 +293,13 @@ async def create_or_update_tenant_location_context(
|
||||
)
|
||||
|
||||
# Invalidate cache since context was updated
|
||||
await cache.invalidate_tenant_context(str(tenant_id))
|
||||
await cache.invalidate_tenant_context(tenant_id)
|
||||
|
||||
# Get full context with calendar details
|
||||
context = await repo.get_tenant_with_calendar(tenant_id)
|
||||
context = await repo.get_tenant_with_calendar(tenant_uuid)
|
||||
|
||||
# Cache the new context
|
||||
await cache.set_cached_tenant_context(str(tenant_id), context)
|
||||
await cache.set_cached_tenant_context(tenant_id, context)
|
||||
|
||||
return TenantLocationContextResponse(**context)
|
||||
|
||||
@@ -317,13 +322,18 @@ async def create_or_update_tenant_location_context(
|
||||
status_code=204
|
||||
)
|
||||
async def delete_tenant_location_context(
|
||||
tenant_id: UUID = Depends(get_current_user_dep),
|
||||
tenant_id: str = Path(..., description="Tenant ID"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete tenant location context"""
|
||||
try:
|
||||
|
||||
# Convert to UUID for use with repository
|
||||
tenant_uuid = UUID(tenant_id)
|
||||
|
||||
repo = CalendarRepository(db)
|
||||
deleted = await repo.delete_tenant_location_context(tenant_id)
|
||||
deleted = await repo.delete_tenant_location_context(tenant_uuid)
|
||||
|
||||
if not deleted:
|
||||
raise HTTPException(
|
||||
@@ -347,6 +357,97 @@ async def delete_tenant_location_context(
|
||||
)
|
||||
|
||||
|
||||
# ===== Calendar Suggestion Endpoint =====
|
||||
|
||||
@router.post(
|
||||
route_builder.build_base_route("location-context/suggest-calendar")
|
||||
)
|
||||
async def suggest_calendar_for_tenant(
|
||||
tenant_id: str = Path(..., description="Tenant ID"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Suggest an appropriate school calendar for a tenant based on location and POI data.
|
||||
|
||||
This endpoint analyzes:
|
||||
- Tenant's city location
|
||||
- Detected schools nearby (from POI detection)
|
||||
- Available calendars for the city
|
||||
- Bakery-specific heuristics (primary schools = stronger morning rush)
|
||||
|
||||
Returns a suggestion with confidence score and reasoning.
|
||||
Does NOT automatically assign - requires admin approval.
|
||||
"""
|
||||
try:
|
||||
from app.utils.calendar_suggester import CalendarSuggester
|
||||
from app.repositories.poi_context_repository import POIContextRepository
|
||||
|
||||
tenant_uuid = UUID(tenant_id)
|
||||
|
||||
# Get tenant's location context
|
||||
calendar_repo = CalendarRepository(db)
|
||||
location_context = await calendar_repo.get_tenant_location_context(tenant_uuid)
|
||||
|
||||
if not location_context:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Location context not found. Create location context first."
|
||||
)
|
||||
|
||||
city_id = location_context.city_id
|
||||
|
||||
# Get available calendars for city
|
||||
calendars_result = await calendar_repo.get_calendars_by_city(city_id, enabled_only=True)
|
||||
calendars = calendars_result.get("calendars", []) if calendars_result else []
|
||||
|
||||
# Get POI context if available
|
||||
poi_repo = POIContextRepository(db)
|
||||
poi_context = await poi_repo.get_by_tenant_id(tenant_uuid)
|
||||
poi_data = poi_context.to_dict() if poi_context else None
|
||||
|
||||
# Generate suggestion
|
||||
suggester = CalendarSuggester()
|
||||
suggestion = suggester.suggest_calendar_for_tenant(
|
||||
city_id=city_id,
|
||||
available_calendars=calendars,
|
||||
poi_context=poi_data,
|
||||
tenant_data=None # Could include tenant info if needed
|
||||
)
|
||||
|
||||
# Format for admin display
|
||||
admin_message = suggester.format_suggestion_for_admin(suggestion)
|
||||
|
||||
logger.info(
|
||||
"Calendar suggestion generated",
|
||||
tenant_id=tenant_id,
|
||||
city_id=city_id,
|
||||
suggested_calendar=suggestion.get("suggested_calendar_id"),
|
||||
confidence=suggestion.get("confidence")
|
||||
)
|
||||
|
||||
return {
|
||||
**suggestion,
|
||||
"admin_message": admin_message,
|
||||
"tenant_id": tenant_id,
|
||||
"current_calendar_id": str(location_context.school_calendar_id) if location_context.school_calendar_id else None
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error generating calendar suggestion",
|
||||
tenant_id=tenant_id,
|
||||
error=str(e),
|
||||
exc_info=True
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error generating calendar suggestion: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
# ===== Helper Endpoints =====
|
||||
|
||||
@router.get(
|
||||
|
||||
82
services/external/app/api/poi_context.py
vendored
82
services/external/app/api/poi_context.py
vendored
@@ -21,10 +21,10 @@ from app.core.redis_client import get_redis_client
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
router = APIRouter(prefix="/poi-context", tags=["POI Context"])
|
||||
router = APIRouter(prefix="/tenants", tags=["POI Context"])
|
||||
|
||||
|
||||
@router.post("/{tenant_id}/detect")
|
||||
@router.post("/{tenant_id}/poi-context/detect")
|
||||
async def detect_pois_for_tenant(
|
||||
tenant_id: str,
|
||||
latitude: float = Query(..., description="Bakery latitude"),
|
||||
@@ -209,13 +209,79 @@ async def detect_pois_for_tenant(
|
||||
relevant_categories=len(feature_selection.get("relevant_categories", []))
|
||||
)
|
||||
|
||||
# Phase 3: Auto-trigger calendar suggestion after POI detection
|
||||
# This helps admins by providing intelligent calendar recommendations
|
||||
calendar_suggestion = None
|
||||
try:
|
||||
from app.utils.calendar_suggester import CalendarSuggester
|
||||
from app.repositories.calendar_repository import CalendarRepository
|
||||
|
||||
# Get tenant's location context
|
||||
calendar_repo = CalendarRepository(db)
|
||||
location_context = await calendar_repo.get_tenant_location_context(tenant_uuid)
|
||||
|
||||
if location_context and location_context.school_calendar_id is None:
|
||||
# Only suggest if no calendar assigned yet
|
||||
city_id = location_context.city_id
|
||||
|
||||
# Get available calendars for city
|
||||
calendars_result = await calendar_repo.get_calendars_by_city(city_id, enabled_only=True)
|
||||
calendars = calendars_result.get("calendars", []) if calendars_result else []
|
||||
|
||||
if calendars:
|
||||
# Generate suggestion using POI data
|
||||
suggester = CalendarSuggester()
|
||||
calendar_suggestion = suggester.suggest_calendar_for_tenant(
|
||||
city_id=city_id,
|
||||
available_calendars=calendars,
|
||||
poi_context=poi_context.to_dict(),
|
||||
tenant_data=None
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Calendar suggestion auto-generated after POI detection",
|
||||
tenant_id=tenant_id,
|
||||
suggested_calendar=calendar_suggestion.get("calendar_name"),
|
||||
confidence=calendar_suggestion.get("confidence_percentage"),
|
||||
should_auto_assign=calendar_suggestion.get("should_auto_assign")
|
||||
)
|
||||
|
||||
# TODO: Send notification to admin about available suggestion
|
||||
# This will be implemented when notification service is integrated
|
||||
else:
|
||||
logger.info(
|
||||
"No calendars available for city, skipping suggestion",
|
||||
tenant_id=tenant_id,
|
||||
city_id=city_id
|
||||
)
|
||||
elif location_context and location_context.school_calendar_id:
|
||||
logger.info(
|
||||
"Calendar already assigned, skipping suggestion",
|
||||
tenant_id=tenant_id,
|
||||
calendar_id=str(location_context.school_calendar_id)
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"No location context found, skipping calendar suggestion",
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Non-blocking: POI detection should succeed even if suggestion fails
|
||||
logger.warning(
|
||||
"Failed to auto-generate calendar suggestion (non-blocking)",
|
||||
tenant_id=tenant_id,
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"source": "detection",
|
||||
"poi_context": poi_context.to_dict(),
|
||||
"feature_selection": feature_selection,
|
||||
"competitor_analysis": competitor_analysis,
|
||||
"competitive_insights": competitive_insights
|
||||
"competitive_insights": competitive_insights,
|
||||
"calendar_suggestion": calendar_suggestion # Include suggestion in response
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
@@ -231,7 +297,7 @@ async def detect_pois_for_tenant(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{tenant_id}")
|
||||
@router.get("/{tenant_id}/poi-context")
|
||||
async def get_poi_context(
|
||||
tenant_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
@@ -265,7 +331,7 @@ async def get_poi_context(
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{tenant_id}/refresh")
|
||||
@router.post("/{tenant_id}/poi-context/refresh")
|
||||
async def refresh_poi_context(
|
||||
tenant_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
@@ -299,7 +365,7 @@ async def refresh_poi_context(
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{tenant_id}")
|
||||
@router.delete("/{tenant_id}/poi-context")
|
||||
async def delete_poi_context(
|
||||
tenant_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
@@ -327,7 +393,7 @@ async def delete_poi_context(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{tenant_id}/feature-importance")
|
||||
@router.get("/{tenant_id}/poi-context/feature-importance")
|
||||
async def get_feature_importance(
|
||||
tenant_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
@@ -364,7 +430,7 @@ async def get_feature_importance(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{tenant_id}/competitor-analysis")
|
||||
@router.get("/{tenant_id}/poi-context/competitor-analysis")
|
||||
async def get_competitor_analysis(
|
||||
tenant_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
|
||||
Reference in New Issue
Block a user