Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions backend/app/routes/memories.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
db_get_images_by_date_range,
db_get_images_by_year_month,
)
from app.utils.memory_clustering import MemoryClustering
<<<<<<< HEAD
from app.utils.memory_clustering import MemoryClustering, generate_clusters_for_weekends
=======
from app.utils.memory_clustering import MemoryClustering, find_total_location_memories
>>>>>>> main
from app.logging.setup_logging import get_logger

# Initialize router and logger
Expand Down Expand Up @@ -111,6 +115,11 @@ class LocationsResponse(BaseModel):
location_count: int
locations: List[LocationCluster]

class WeeklyMemoriesResponse(BaseModel):
success: bool
message: str
weekly_memories: List[Dict]


# API Endpoints

Expand Down Expand Up @@ -163,7 +172,7 @@ def generate_memories(
)

memories = clustering.cluster_memories(images)

tlm = find_total_location_memories(memories)
# Calculate breakdown
location_count = sum(1 for m in memories if m.get("type") == "location")
date_count = sum(1 for m in memories if m.get("type") == "date")
Expand All @@ -178,6 +187,7 @@ def generate_memories(
"image_count": len(images),
"memories": memories,
},
"total_location":tlm,
"success": True,
"message": f"{len(memories)} memories ({location_count} location, {date_count} date)",
}
Expand Down Expand Up @@ -466,3 +476,24 @@ def get_locations(
except Exception:
logger.error("Error getting locations", exc_info=True)
raise HTTPException(status_code=500, detail="Failed to get locations")


@router.get("/weekly-memories", response_model=WeeklyMemoriesResponse)
def get_weekly_memories():
try:
weekly_clusters = generate_clusters_for_weekends()
if weekly_clusters:
return WeeklyMemoriesResponse(
success= True,
message="Weekly cluster created.",
weekly_memories=weekly_clusters
)
else:
return WeeklyMemoriesResponse(
success=True,
message="Please add images.",
weekly_memories=weekly_clusters
)
except Exception as e:
logger.error("Failed to create weekly memories", exc_info=True)
raise HTTPException(status_code=500, detail=f"Failed to create weekly memories:{e}")
52 changes: 50 additions & 2 deletions backend/app/utils/memory_clustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from sklearn.cluster import DBSCAN

from app.logging.setup_logging import get_logger

from app.database.images import db_get_all_images
# Initialize logger
logger = get_logger(__name__)

Expand Down Expand Up @@ -103,6 +103,14 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl

return nearest_city

# function to count total memories which has location
# this can also be done in tsx.
def find_total_location_memories(data:list) -> int:
tlm = 0 # total location memories
for memory in data:
if memory["location_name"] != None:
tlm +=1
return tlm
Comment on lines +108 to +113
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard aggregation against missing location_name keys.

Using memory["location_name"] can raise KeyError and fail the response build if any item is missing that key. Prefer safe access here.

Suggested fix
-def find_total_location_memories(data:list) -> int:
-    tlm = 0 # total location memories
+def find_total_location_memories(data: list) -> int:
+    tlm = 0  # total location memories
     for memory in data:
-        if memory["location_name"] != None:
-            tlm +=1
-    return tlm 
+        if memory.get("location_name") is not None:
+            tlm += 1
+    return tlm
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/utils/memory_clustering.py` around lines 108 - 113, The function
find_total_location_memories currently indexes memory["location_name"] which can
raise KeyError for items missing that key; update the loop to safely access the
field (e.g., use memory.get("location_name") or check "location_name" in memory)
and only increment tlm when the safe access returns a non-None value, leaving
the rest of the function behavior unchanged; reference the function name
find_total_location_memories and the memory variable in your change.


class MemoryClustering:
"""
Expand Down Expand Up @@ -385,7 +393,7 @@ def _create_simple_memory(
title = date_obj.strftime("%B %Y")
else:
title = "Undated Photos"
location_name = ""
location_name = None
center_lat = 0
center_lon = 0

Expand Down Expand Up @@ -944,3 +952,43 @@ def _generate_memory_id(
hash_input = f"lat:{lat_rounded}|lon:{lon_rounded}"
hash_digest = hashlib.sha256(hash_input.encode()).hexdigest()[:8]
return f"mem_nodate_{hash_digest}"


from datetime import datetime
from typing import List, Dict
import uuid


def generate_clusters_for_weekends() -> List[Dict]:
images = db_get_all_images()

# sort by date
images.sort(key=lambda x: x["metadata"]["date_created"], reverse=True)

weekend_memories = {}

for img in images:
date_str: str = img["metadata"]["date_created"]
dt = datetime.fromisoformat(date_str)

# get year and week number
year, week, _ = dt.isocalendar()

week_key = f"{year}-W{week}"

if week_key not in weekend_memories:
weekend_memories[week_key] = {
"mem_id": str(uuid.uuid4()),
"images": [],
"end_date" : date_str.split("T")[0],
"start_date" : ""
}
image_info = {
"id": img["id"],
"path": img["path"],
"thumbnailPath": img["thumbnailPath"]
}
weekend_memories[week_key]["start_date"] = date_str.split("T")[0]
weekend_memories[week_key]["images"].append(image_info)

return list(weekend_memories.values())
163 changes: 163 additions & 0 deletions docs/backend/backend_python/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,27 @@
}
}
},
"/api/memories/weekly-memories": {
"get": {
"tags": [
"memories"
],
"summary": "Get Weekly Memories",
"operationId": "get_weekly_memories_api_memories_weekly_memories_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WeeklyMemoriesResponse"
}
}
}
}
}
}
},
"/shutdown": {
"post": {
"tags": [
Expand Down Expand Up @@ -3407,6 +3428,148 @@
"type"
],
"title": "ValidationError"
<<<<<<< HEAD
},
"WeeklyMemoriesResponse": {
"properties": {
"success": {
"type": "boolean",
"title": "Success"
},
"message": {
"type": "string",
"title": "Message"
},
"weekly_memories": {
"items": {
"type": "object"
},
"type": "array",
"title": "Weekly Memories"
}
},
"type": "object",
"required": [
"success",
"message",
"weekly_memories"
],
"title": "WeeklyMemoriesResponse"
},
"app__schemas__face_clusters__ErrorResponse": {
"properties": {
"success": {
"type": "boolean",
"title": "Success",
"default": false
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Message"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Error"
}
},
"type": "object",
"title": "ErrorResponse"
},
"app__schemas__folders__ErrorResponse": {
"properties": {
"success": {
"type": "boolean",
"title": "Success",
"default": false
},
"message": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Message"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Error"
}
},
"type": "object",
"title": "ErrorResponse"
},
"app__schemas__images__ErrorResponse": {
"properties": {
"success": {
"type": "boolean",
"title": "Success",
"default": false
},
"message": {
"type": "string",
"title": "Message"
},
"error": {
"type": "string",
"title": "Error"
}
},
"type": "object",
"required": [
"message",
"error"
],
"title": "ErrorResponse"
},
"app__schemas__user_preferences__ErrorResponse": {
"properties": {
"success": {
"type": "boolean",
"title": "Success"
},
"error": {
"type": "string",
"title": "Error"
},
"message": {
"type": "string",
"title": "Message"
}
},
"type": "object",
"required": [
"success",
"error",
"message"
],
"title": "ErrorResponse",
"description": "Error response model"
=======
>>>>>>> main
}
}
}
Expand Down
43 changes: 41 additions & 2 deletions frontend/src/api/api-functions/memories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface Memory {
memory_id: string;
title: string;
description: string;
location_name: string;
location_name: string | null;
date_start: string | null;
date_end: string | null;
image_count: number;
Expand All @@ -44,7 +44,7 @@ export interface Memory {
* Location cluster with sample images
*/
export interface LocationCluster {
location_name: string;
location_name: string | null;
center_lat: number;
center_lon: number;
image_count: number;
Expand Down Expand Up @@ -127,3 +127,42 @@ export const getLocations = async (options?: {
const response = await apiClient.get<APIResponse>(url);
return response.data;
};

/**
* A single image within a weekend memory cluster
*/
export interface WeeklyMemoryImage {
id: string;
path: string;
thumbnailPath: string;
}

/**
* A single weekend memory cluster returned by the backend
*/
export interface WeeklyMemory {
mem_id: string;
images: WeeklyMemoryImage[];
start_date: string;
end_date: string;
}

/**
* Full response shape from GET /api/memories/weekly-memories
*/
export interface WeeklyMemoriesResponse {
success: boolean;
message: string;
weekly_memories: WeeklyMemory[];
}

/**
* Fetch weekend memory clusters from the backend
*/
export const getWeeklyMemories = async (): Promise<WeeklyMemoriesResponse> => {
const response =
await apiClient.get<WeeklyMemoriesResponse>(
memoriesEndpoints.weeklyMemories,
);
return response.data;
};
1 change: 1 addition & 0 deletions frontend/src/api/apiEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export const memoriesEndpoints = {
timeline: '/api/memories/timeline',
onThisDay: '/api/memories/on-this-day',
locations: '/api/memories/locations',
weeklyMemories: '/api/memories/weekly-memories',
};
Loading
Loading