Update App/routers/portfolio/routes.py
Browse files
App/routers/portfolio/routes.py
CHANGED
|
@@ -1200,6 +1200,84 @@ async def get_portfolio_performance(
|
|
| 1200 |
raise AppException(status_code=500, detail=f"An unexpected error occurred: {e}")
|
| 1201 |
|
| 1202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1203 |
@router.get(
|
| 1204 |
"/{portfolio_id}/calendar",
|
| 1205 |
response_model=ResponseModel,
|
|
|
|
| 1200 |
raise AppException(status_code=500, detail=f"An unexpected error occurred: {e}")
|
| 1201 |
|
| 1202 |
|
| 1203 |
+
@router.post(
|
| 1204 |
+
"/{portfolio_id}/recalculate-timeseries",
|
| 1205 |
+
response_model=ResponseModel,
|
| 1206 |
+
summary="Recalculate Entire Portfolio Timeseries",
|
| 1207 |
+
)
|
| 1208 |
+
async def recalculate_portfolio_timeseries(
|
| 1209 |
+
portfolio_id: int,
|
| 1210 |
+
background_tasks: BackgroundTasks,
|
| 1211 |
+
current_user=Depends(get_current_user),
|
| 1212 |
+
):
|
| 1213 |
+
"""
|
| 1214 |
+
Recalculates the entire portfolio timeseries by regenerating all historical snapshots
|
| 1215 |
+
from the first transaction date to today. This endpoint:
|
| 1216 |
+
|
| 1217 |
+
1. Validates the portfolio exists and belongs to the user
|
| 1218 |
+
2. Checks if a regeneration task is already running
|
| 1219 |
+
3. Deletes all existing snapshots for the portfolio
|
| 1220 |
+
4. Queues a background task to regenerate snapshots from the earliest transaction
|
| 1221 |
+
|
| 1222 |
+
Returns task_id for polling the regeneration status.
|
| 1223 |
+
"""
|
| 1224 |
+
try:
|
| 1225 |
+
# 1. AUTHENTICATION & VALIDATION
|
| 1226 |
+
portfolio = await Portfolio.get_or_none(
|
| 1227 |
+
id=portfolio_id, user_id=current_user.id
|
| 1228 |
+
)
|
| 1229 |
+
if not portfolio:
|
| 1230 |
+
raise AppException(status_code=404, detail="Portfolio not found")
|
| 1231 |
+
|
| 1232 |
+
# 2. CHECK FOR ACTIVE REGENERATION TASKS
|
| 1233 |
+
active_task = await ImportTask.filter(
|
| 1234 |
+
Q(details__contains={"portfolio_id": portfolio_id}),
|
| 1235 |
+
Q(task_type__in=["portfolio_regeneration", "portfolio_snapshot_history"]),
|
| 1236 |
+
status__in=["pending", "running"],
|
| 1237 |
+
).first()
|
| 1238 |
+
|
| 1239 |
+
if active_task:
|
| 1240 |
+
return ResponseModel(
|
| 1241 |
+
success=False,
|
| 1242 |
+
message="A timeseries recalculation is already in progress for this portfolio.",
|
| 1243 |
+
data={"task_id": active_task.id, "status": active_task.status},
|
| 1244 |
+
)
|
| 1245 |
+
|
| 1246 |
+
# 3. CREATE A NEW TASK AND QUEUE BACKGROUND WORK
|
| 1247 |
+
task = await ImportTask.create(
|
| 1248 |
+
task_type="portfolio_regeneration",
|
| 1249 |
+
status="pending",
|
| 1250 |
+
details={
|
| 1251 |
+
"portfolio_id": portfolio_id,
|
| 1252 |
+
"reason": "Manual full timeseries recalculation requested by user.",
|
| 1253 |
+
},
|
| 1254 |
+
)
|
| 1255 |
+
|
| 1256 |
+
# Queue the regeneration task without a start_date,
|
| 1257 |
+
# so it will find the earliest transaction and start from there
|
| 1258 |
+
background_tasks.add_task(
|
| 1259 |
+
PortfolioService.regenerate_snapshots_task, task.id, portfolio_id
|
| 1260 |
+
)
|
| 1261 |
+
|
| 1262 |
+
return ResponseModel(
|
| 1263 |
+
success=True,
|
| 1264 |
+
message="Timeseries recalculation started. This may take a few moments.",
|
| 1265 |
+
data={
|
| 1266 |
+
"task_id": task.id,
|
| 1267 |
+
"status": "pending",
|
| 1268 |
+
"portfolio_id": portfolio_id,
|
| 1269 |
+
},
|
| 1270 |
+
)
|
| 1271 |
+
|
| 1272 |
+
except AppException:
|
| 1273 |
+
raise
|
| 1274 |
+
except Exception as e:
|
| 1275 |
+
raise AppException(
|
| 1276 |
+
status_code=500, detail=f"Failed to start timeseries recalculation: {str(e)}"
|
| 1277 |
+
)
|
| 1278 |
+
|
| 1279 |
+
|
| 1280 |
+
|
| 1281 |
@router.get(
|
| 1282 |
"/{portfolio_id}/calendar",
|
| 1283 |
response_model=ResponseModel,
|