ABAO77 commited on
Commit
e390496
·
verified ·
1 Parent(s): ef0145e

Upload 164 files

Browse files
src/apis/controllers/__pycache__/destination_controller.cpython-311.pyc CHANGED
Binary files a/src/apis/controllers/__pycache__/destination_controller.cpython-311.pyc and b/src/apis/controllers/__pycache__/destination_controller.cpython-311.pyc differ
 
src/apis/controllers/__pycache__/post_controller.cpython-311.pyc CHANGED
Binary files a/src/apis/controllers/__pycache__/post_controller.cpython-311.pyc and b/src/apis/controllers/__pycache__/post_controller.cpython-311.pyc differ
 
src/apis/controllers/post_controller.py CHANGED
@@ -7,6 +7,8 @@ from src.utils.helper import call_external_api, serialize_datetime
7
  from datetime import datetime
8
  from bson import ObjectId
9
  import math
 
 
10
 
11
 
12
  async def create_a_post_controller(
@@ -60,6 +62,148 @@ async def get_a_post_controller(post_id: str) -> Dict:
60
  return {"status": "error", "message": str(e)}
61
 
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  async def list_all_posts_controller(user_id: str, page: int = 1):
64
  try:
65
  PAGE_SIZE = 5
 
7
  from datetime import datetime
8
  from bson import ObjectId
9
  import math
10
+ from src.utils.helper import deserialize_objectid
11
+ from typing import List
12
 
13
 
14
  async def create_a_post_controller(
 
62
  return {"status": "error", "message": str(e)}
63
 
64
 
65
+ async def list_all_posts_controller_priority(
66
+ user_id: str,
67
+ page: int = 1,
68
+ top_5_destinations: List[str] = [],
69
+ view_post_ids: List[str] = [],
70
+ ):
71
+ try:
72
+ PAGE_SIZE = 10
73
+ TOP_LIMIT = int(PAGE_SIZE * 0.7)
74
+ OTHER_LIMIT = PAGE_SIZE - TOP_LIMIT
75
+ skip_top = (page - 1) * TOP_LIMIT
76
+ skip_other = (page - 1) * OTHER_LIMIT
77
+
78
+ excluded_ids = [deserialize_objectid(pid) for pid in view_post_ids]
79
+
80
+ # Tạo filter cho 2 nhóm
81
+ top_filter = {
82
+ "destination_id": {"$in": top_5_destinations},
83
+ "_id": {"$nin": excluded_ids},
84
+ }
85
+ other_filter = {
86
+ "destination_id": {"$nin": top_5_destinations},
87
+ "_id": {"$nin": excluded_ids},
88
+ }
89
+
90
+ # Đếm tổng số bài viết mỗi nhóm để tính total_items/pages
91
+ total_top = await PostCRUD.count(top_filter)
92
+ total_other = await PostCRUD.count(other_filter)
93
+ total_items = total_top + total_other
94
+ total_pages = math.ceil(total_items / PAGE_SIZE)
95
+
96
+ # Lấy bài viết đã tính điểm
97
+ top_posts = await PostCRUD.find_many_with_score(
98
+ filter=top_filter,
99
+ top_destinations=top_5_destinations,
100
+ limit=TOP_LIMIT,
101
+ skip=skip_top,
102
+ )
103
+
104
+ other_posts = await PostCRUD.find_many_with_score(
105
+ filter=other_filter,
106
+ top_destinations=[],
107
+ limit=OTHER_LIMIT,
108
+ skip=skip_other,
109
+ )
110
+
111
+ # Xen kẽ
112
+ combined_posts = []
113
+ i = j = 0
114
+ while len(combined_posts) < PAGE_SIZE and (i < len(top_posts) or j < len(other_posts)):
115
+ if i < len(top_posts):
116
+ combined_posts.append(top_posts[i])
117
+ i += 1
118
+ if j < len(other_posts) and len(combined_posts) < PAGE_SIZE:
119
+ combined_posts.append(other_posts[j])
120
+ j += 1
121
+
122
+ # Enrich thông tin
123
+ user_ids = list({post.get("user_id") for post in combined_posts})
124
+ destination_ids = list({post.get("destination_id") for post in combined_posts})
125
+ post_ids = [post["_id"] for post in combined_posts]
126
+
127
+ user_infos, destination_infos, reactions = await gather(
128
+ *[
129
+ gather(*[UserCRUD.find_by_id(uid) for uid in user_ids]),
130
+ gather(*[DestinationCRUD.find_by_id(did) for did in destination_ids]),
131
+ gather(
132
+ *[
133
+ ReactionCRUD.read_one(
134
+ {"user_id": user_id, "post_id": serialize_datetime(pid)}
135
+ )
136
+ for pid in post_ids
137
+ ]
138
+ ),
139
+ ]
140
+ )
141
+
142
+ user_info_map = {
143
+ info["_id"]: {
144
+ "user_id": info["_id"],
145
+ "name": info["name"],
146
+ "picture": info.get("picture"),
147
+ }
148
+ for info in user_infos
149
+ if info
150
+ }
151
+
152
+ destination_info_map = {
153
+ info["_id"]: info["name"] for info in destination_infos if info
154
+ }
155
+
156
+ user_reaction_map = {}
157
+ for reaction in reactions:
158
+ if reaction:
159
+ post_id = reaction.get("post_id")
160
+ user_reaction_map[post_id] = {
161
+ "id": serialize_datetime(reaction["_id"]),
162
+ "post_id": post_id,
163
+ "user_id": reaction.get("user_id"),
164
+ "reaction_type": reaction.get("type"),
165
+ }
166
+
167
+ serialized_posts = []
168
+ for post in combined_posts:
169
+ pid = serialize_datetime(post["_id"])
170
+ uid = post.get("user_id")
171
+ dest_id = post.get("destination_id")
172
+ serialized_posts.append(
173
+ {
174
+ "id": pid,
175
+ "content": post.get("content"),
176
+ "destination_id": dest_id,
177
+ "destination_name": destination_info_map.get(dest_id),
178
+ "comment_count": post.get("comment_count", 0),
179
+ "reaction_count": post.get("reaction_count", 0),
180
+ "current_user_reaction": user_reaction_map.get(pid),
181
+ "picture": post.get("picture", []),
182
+ "created_at": serialize_datetime(post.get("created_at")),
183
+ "updated_at": serialize_datetime(post.get("updated_at")),
184
+ "priority_score": post.get("PriorityScore", 0),
185
+ "destination_score": post.get("DestinationScore", 0),
186
+ "engagement_score": post.get("EngagementScore", 0),
187
+ "freshness_score": post.get("FreshnessScore", 0),
188
+ "user_info": user_info_map.get(uid),
189
+ }
190
+ )
191
+
192
+ return {
193
+ "status": "success",
194
+ "message": {
195
+ "data": serialized_posts,
196
+ "page": page,
197
+ "total_pages": total_pages,
198
+ "total_items": total_items,
199
+ "page_size": PAGE_SIZE,
200
+ },
201
+ }
202
+
203
+ except Exception as e:
204
+ logger.error(f"Error listing personalized posts: {str(e)}")
205
+ return {"status": "error", "message": str(e)}
206
+
207
  async def list_all_posts_controller(user_id: str, page: int = 1):
208
  try:
209
  PAGE_SIZE = 5
src/apis/routes/__pycache__/post_router.cpython-311.pyc CHANGED
Binary files a/src/apis/routes/__pycache__/post_router.cpython-311.pyc and b/src/apis/routes/__pycache__/post_router.cpython-311.pyc differ
 
src/apis/routes/__pycache__/travel_dest_route.cpython-311.pyc CHANGED
Binary files a/src/apis/routes/__pycache__/travel_dest_route.cpython-311.pyc and b/src/apis/routes/__pycache__/travel_dest_route.cpython-311.pyc differ
 
src/apis/routes/post_router.py CHANGED
@@ -1,5 +1,5 @@
1
  from fastapi import APIRouter, status, Depends, BackgroundTasks, Query
2
- from typing import Annotated, Optional
3
  from fastapi.responses import JSONResponse
4
  from pydantic import Field
5
  from src.apis.models.user_models import User
@@ -12,6 +12,7 @@ from src.apis.controllers.post_controller import (
12
  get_a_post_controller,
13
  update_a_post_controller,
14
  delete_a_post_controller,
 
15
  )
16
  from src.utils.redis import set_key_redis, get_key_redis
17
  from src.utils.logger import logger
@@ -97,6 +98,23 @@ async def list_all_posts_destination(
97
  else:
98
  return JSONResponse(content=result, status_code=200)
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  class BodyUpdatePost(BaseDocument):
102
  content: str = Field(..., description="Post's content", min_length=1)
 
1
  from fastapi import APIRouter, status, Depends, BackgroundTasks, Query
2
+ from typing import Annotated, Optional, List
3
  from fastapi.responses import JSONResponse
4
  from pydantic import Field
5
  from src.apis.models.user_models import User
 
12
  get_a_post_controller,
13
  update_a_post_controller,
14
  delete_a_post_controller,
15
+ list_all_posts_controller_priority,
16
  )
17
  from src.utils.redis import set_key_redis, get_key_redis
18
  from src.utils.logger import logger
 
98
  else:
99
  return JSONResponse(content=result, status_code=200)
100
 
101
+ @router.get("/list_post_priority", status_code=status.HTTP_200_OK)
102
+ async def list_all_posts_priority(
103
+ user_id: Optional[str] = Query(None),
104
+ page: int = Query(default=1, ge=1),
105
+ top_5_destinations: List[str] = Query(default=[]),
106
+ view_post_ids: List[str] = Query(default=[]),
107
+ ):
108
+ # result = await get_key_redis("all_posts")
109
+ result = None
110
+ result = eval(result) if result else None
111
+ if not result:
112
+ result = await list_all_posts_controller_priority(user_id, page, top_5_destinations, view_post_ids)
113
+ # background_tasks.add_task(set_key_redis, "all_posts", str(result),20)
114
+ if result["status"] == "error":
115
+ return JSONResponse(content=result, status_code=404)
116
+ else:
117
+ return JSONResponse(content=result, status_code=200)
118
 
119
  class BodyUpdatePost(BaseDocument):
120
  content: str = Field(..., description="Post's content", min_length=1)
src/apis/routes/travel_dest_route.py CHANGED
@@ -155,7 +155,9 @@ async def get_tourist_names():
155
 
156
  @router.get("/suggest")
157
  async def destination_suggestion(
158
- question: str,
 
 
159
  user_id: str = Query(
160
  default=None, description="User ID for personalized recommendations"
161
  ),
 
155
 
156
  @router.get("/suggest")
157
  async def destination_suggestion(
158
+ question: str = Query(
159
+ default="", min_length=1, max_length=50, description="User's question"
160
+ ),
161
  user_id: str = Query(
162
  default=None, description="User ID for personalized recommendations"
163
  ),
src/langgraph/langchain/__pycache__/llm.cpython-311.pyc CHANGED
Binary files a/src/langgraph/langchain/__pycache__/llm.cpython-311.pyc and b/src/langgraph/langchain/__pycache__/llm.cpython-311.pyc differ
 
src/langgraph/langchain/__pycache__/prompt.cpython-311.pyc CHANGED
Binary files a/src/langgraph/langchain/__pycache__/prompt.cpython-311.pyc and b/src/langgraph/langchain/__pycache__/prompt.cpython-311.pyc differ
 
src/langgraph/multi_agent/planner/__pycache__/planner_flow.cpython-311.pyc CHANGED
Binary files a/src/langgraph/multi_agent/planner/__pycache__/planner_flow.cpython-311.pyc and b/src/langgraph/multi_agent/planner/__pycache__/planner_flow.cpython-311.pyc differ
 
src/langgraph/tools/__pycache__/destination_tools.cpython-311.pyc CHANGED
Binary files a/src/langgraph/tools/__pycache__/destination_tools.cpython-311.pyc and b/src/langgraph/tools/__pycache__/destination_tools.cpython-311.pyc differ
 
src/utils/__pycache__/helper.cpython-311.pyc CHANGED
Binary files a/src/utils/__pycache__/helper.cpython-311.pyc and b/src/utils/__pycache__/helper.cpython-311.pyc differ
 
src/utils/__pycache__/mongo.cpython-311.pyc CHANGED
Binary files a/src/utils/__pycache__/mongo.cpython-311.pyc and b/src/utils/__pycache__/mongo.cpython-311.pyc differ
 
src/utils/helper.py CHANGED
@@ -12,6 +12,15 @@ from fastapi import HTTPException
12
  from bson import ObjectId
13
 
14
 
 
 
 
 
 
 
 
 
 
15
  def handle_validator_raise(func):
16
  """
17
  Custom decorator to handle exceptions raised by the validator
@@ -58,9 +67,7 @@ def format_weather_data(weather_data):
58
  location = f"Latitude: {lat}, Longitude: {lon}"
59
  icon_url = f"http://openweathermap.org/img/wn/{current_weather['weather'][0]['icon']}@2x.png"
60
  formatted_weather = f"In {location}, the current weather is as follows:\n"
61
- formatted_weather += (
62
- f" <img src='{icon_url}' width='100' height='100'/>\n"
63
- )
64
  formatted_weather += (
65
  f" Detailed status: {current_weather['weather'][0]['description']}\n"
66
  )
 
12
  from bson import ObjectId
13
 
14
 
15
+
16
+ def deserialize_objectid(id_str: str) -> ObjectId:
17
+ """Chuyển string thành ObjectId (nếu hợp lệ), nếu không thì raise lỗi."""
18
+ try:
19
+ return ObjectId(id_str)
20
+ except Exception as e:
21
+ raise ValueError(f"Invalid ObjectId string: {id_str}") from e
22
+
23
+
24
  def handle_validator_raise(func):
25
  """
26
  Custom decorator to handle exceptions raised by the validator
 
67
  location = f"Latitude: {lat}, Longitude: {lon}"
68
  icon_url = f"http://openweathermap.org/img/wn/{current_weather['weather'][0]['icon']}@2x.png"
69
  formatted_weather = f"In {location}, the current weather is as follows:\n"
70
+ formatted_weather += f" <img src='{icon_url}' width='100' height='100'/>\n"
 
 
71
  formatted_weather += (
72
  f" Detailed status: {current_weather['weather'][0]['description']}\n"
73
  )
src/utils/mongo.py CHANGED
@@ -196,6 +196,114 @@ class MongoCRUD:
196
  return 0
197
 
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  def chat_messages_history(
200
  session_id: str, number_of_messages: int = MongoCfg.MAX_HISTORY_SIZE, db="mongo"
201
  ):
@@ -221,7 +329,6 @@ def chat_messages_history(
221
  BookHotelCRUD = MongoCRUD(database[MongoCfg.BOOK_HOTEL], BookHotel)
222
  ScheduleCRUD = MongoCRUD(database[MongoCfg.ACTIVITY], Schedule)
223
  UserCRUD = MongoCRUD(database[MongoCfg.USER], User)
224
- PostCRUD = MongoCRUD(database[MongoCfg.POST], Post)
225
  ReactionCRUD = MongoCRUD(database[MongoCfg.REACTION], Reaction)
226
  CommentCRUD = MongoCRUD(database[MongoCfg.COMMENT], Comment)
227
  DestinationCRUD = MongoCRUD(database[MongoCfg.DESTINATION], Destination)
 
196
  return 0
197
 
198
 
199
+ from motor.motor_asyncio import AsyncIOMotorCollection
200
+ from bson.son import SON
201
+ from datetime import datetime, timedelta
202
+
203
+ from typing import List, Dict
204
+
205
+
206
+ class PostMongoCRUD(MongoCRUD):
207
+ async def find_many_with_score(
208
+ self,
209
+ filter: Dict,
210
+ top_destinations: List[str],
211
+ limit: int = 10,
212
+ skip: int = 0,
213
+ ) -> List[Dict]:
214
+ now = datetime.now(timezone.utc)
215
+
216
+ destination_score_branches = [
217
+ {"case": {"$eq": ["$destination_id", did]}, "then": (5 - i) * 2}
218
+ for i, did in enumerate(top_destinations)
219
+ ]
220
+
221
+ pipeline = [
222
+ {"$match": filter},
223
+ {
224
+ "$addFields": {
225
+ "BaseScore": 1,
226
+ "DestinationScore": (
227
+ {
228
+ "$switch": {
229
+ "branches": destination_score_branches,
230
+ "default": 0,
231
+ }
232
+ }
233
+ if top_destinations
234
+ else 0
235
+ ),
236
+ "EngagementScore": {
237
+ "$min": [
238
+ {
239
+ "$divide": [
240
+ {
241
+ "$add": [
242
+ "$reaction_count",
243
+ {"$multiply": ["$comment_count", 2]},
244
+ ]
245
+ },
246
+ 100,
247
+ ]
248
+ },
249
+ 5,
250
+ ]
251
+ },
252
+ "FreshnessScore": {
253
+ "$switch": {
254
+ "branches": [
255
+ {
256
+ "case": {
257
+ "$gte": ["$created_at", now - timedelta(days=1)]
258
+ },
259
+ "then": 5,
260
+ },
261
+ {
262
+ "case": {
263
+ "$gte": ["$created_at", now - timedelta(days=3)]
264
+ },
265
+ "then": 3,
266
+ },
267
+ {
268
+ "case": {
269
+ "$gte": ["$created_at", now - timedelta(days=7)]
270
+ },
271
+ "then": 2,
272
+ },
273
+ ],
274
+ "default": 1,
275
+ }
276
+ },
277
+ }
278
+ },
279
+ {
280
+ "$addFields": {
281
+ "PriorityScore": {
282
+ "$add": [
283
+ "$BaseScore",
284
+ "$DestinationScore",
285
+ "$EngagementScore",
286
+ "$FreshnessScore",
287
+ ]
288
+ }
289
+ }
290
+ },
291
+ {"$sort": SON([("PriorityScore", -1), ("created_at", -1)])},
292
+ {"$skip": skip},
293
+ {"$limit": limit},
294
+ ]
295
+
296
+ cursor = self.collection.aggregate(pipeline)
297
+ results = []
298
+ async for doc in cursor:
299
+ doc["_id"] = str(doc["_id"])
300
+ results.append(doc)
301
+ return results
302
+
303
+
304
+ PostCRUD = PostMongoCRUD(database[MongoCfg.POST], Post)
305
+
306
+
307
  def chat_messages_history(
308
  session_id: str, number_of_messages: int = MongoCfg.MAX_HISTORY_SIZE, db="mongo"
309
  ):
 
329
  BookHotelCRUD = MongoCRUD(database[MongoCfg.BOOK_HOTEL], BookHotel)
330
  ScheduleCRUD = MongoCRUD(database[MongoCfg.ACTIVITY], Schedule)
331
  UserCRUD = MongoCRUD(database[MongoCfg.USER], User)
 
332
  ReactionCRUD = MongoCRUD(database[MongoCfg.REACTION], Reaction)
333
  CommentCRUD = MongoCRUD(database[MongoCfg.COMMENT], Comment)
334
  DestinationCRUD = MongoCRUD(database[MongoCfg.DESTINATION], Destination)