statistic.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. from datetime import datetime
  2. from decimal import Decimal
  3. import pytz
  4. from flask import jsonify
  5. from flask_login import current_user # type: ignore
  6. from flask_restful import Resource, reqparse # type: ignore
  7. from controllers.console import api
  8. from controllers.console.app.wraps import get_app_model
  9. from controllers.console.wraps import account_initialization_required, setup_required
  10. from extensions.ext_database import db
  11. from libs.helper import DatetimeString
  12. from libs.login import login_required
  13. from models.model import AppMode
  14. class DailyMessageStatistic(Resource):
  15. @setup_required
  16. @login_required
  17. @account_initialization_required
  18. @get_app_model
  19. def get(self, app_model):
  20. account = current_user
  21. parser = reqparse.RequestParser()
  22. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  23. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  24. args = parser.parse_args()
  25. sql_query = """SELECT
  26. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  27. COUNT(*) AS message_count
  28. FROM
  29. messages
  30. WHERE
  31. app_id = :app_id"""
  32. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  33. timezone = pytz.timezone(account.timezone)
  34. utc_timezone = pytz.utc
  35. if args["start"]:
  36. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  37. start_datetime = start_datetime.replace(second=0)
  38. start_datetime_timezone = timezone.localize(start_datetime)
  39. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  40. sql_query += " AND created_at >= :start"
  41. arg_dict["start"] = start_datetime_utc
  42. if args["end"]:
  43. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  44. end_datetime = end_datetime.replace(second=0)
  45. end_datetime_timezone = timezone.localize(end_datetime)
  46. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  47. sql_query += " AND created_at < :end"
  48. arg_dict["end"] = end_datetime_utc
  49. sql_query += " GROUP BY date ORDER BY date"
  50. response_data = []
  51. with db.engine.begin() as conn:
  52. rs = conn.execute(db.text(sql_query), arg_dict)
  53. for i in rs:
  54. response_data.append({"date": str(i.date), "message_count": i.message_count})
  55. return jsonify({"data": response_data})
  56. class DailyConversationStatistic(Resource):
  57. @setup_required
  58. @login_required
  59. @account_initialization_required
  60. @get_app_model
  61. def get(self, app_model):
  62. account = current_user
  63. parser = reqparse.RequestParser()
  64. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  65. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  66. args = parser.parse_args()
  67. sql_query = """SELECT
  68. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  69. COUNT(DISTINCT messages.conversation_id) AS conversation_count
  70. FROM
  71. messages
  72. WHERE
  73. app_id = :app_id"""
  74. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  75. timezone = pytz.timezone(account.timezone)
  76. utc_timezone = pytz.utc
  77. if args["start"]:
  78. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  79. start_datetime = start_datetime.replace(second=0)
  80. start_datetime_timezone = timezone.localize(start_datetime)
  81. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  82. sql_query += " AND created_at >= :start"
  83. arg_dict["start"] = start_datetime_utc
  84. if args["end"]:
  85. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  86. end_datetime = end_datetime.replace(second=0)
  87. end_datetime_timezone = timezone.localize(end_datetime)
  88. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  89. sql_query += " AND created_at < :end"
  90. arg_dict["end"] = end_datetime_utc
  91. sql_query += " GROUP BY date ORDER BY date"
  92. response_data = []
  93. with db.engine.begin() as conn:
  94. rs = conn.execute(db.text(sql_query), arg_dict)
  95. for i in rs:
  96. response_data.append({"date": str(i.date), "conversation_count": i.conversation_count})
  97. return jsonify({"data": response_data})
  98. class DailyTerminalsStatistic(Resource):
  99. @setup_required
  100. @login_required
  101. @account_initialization_required
  102. @get_app_model
  103. def get(self, app_model):
  104. account = current_user
  105. parser = reqparse.RequestParser()
  106. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  107. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  108. args = parser.parse_args()
  109. sql_query = """SELECT
  110. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  111. COUNT(DISTINCT messages.from_end_user_id) AS terminal_count
  112. FROM
  113. messages
  114. WHERE
  115. app_id = :app_id"""
  116. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  117. timezone = pytz.timezone(account.timezone)
  118. utc_timezone = pytz.utc
  119. if args["start"]:
  120. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  121. start_datetime = start_datetime.replace(second=0)
  122. start_datetime_timezone = timezone.localize(start_datetime)
  123. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  124. sql_query += " AND created_at >= :start"
  125. arg_dict["start"] = start_datetime_utc
  126. if args["end"]:
  127. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  128. end_datetime = end_datetime.replace(second=0)
  129. end_datetime_timezone = timezone.localize(end_datetime)
  130. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  131. sql_query += " AND created_at < :end"
  132. arg_dict["end"] = end_datetime_utc
  133. sql_query += " GROUP BY date ORDER BY date"
  134. response_data = []
  135. with db.engine.begin() as conn:
  136. rs = conn.execute(db.text(sql_query), arg_dict)
  137. for i in rs:
  138. response_data.append({"date": str(i.date), "terminal_count": i.terminal_count})
  139. return jsonify({"data": response_data})
  140. class DailyTokenCostStatistic(Resource):
  141. @setup_required
  142. @login_required
  143. @account_initialization_required
  144. @get_app_model
  145. def get(self, app_model):
  146. account = current_user
  147. parser = reqparse.RequestParser()
  148. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  149. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  150. args = parser.parse_args()
  151. sql_query = """SELECT
  152. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  153. (SUM(messages.message_tokens) + SUM(messages.answer_tokens)) AS token_count,
  154. SUM(total_price) AS total_price
  155. FROM
  156. messages
  157. WHERE
  158. app_id = :app_id"""
  159. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  160. timezone = pytz.timezone(account.timezone)
  161. utc_timezone = pytz.utc
  162. if args["start"]:
  163. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  164. start_datetime = start_datetime.replace(second=0)
  165. start_datetime_timezone = timezone.localize(start_datetime)
  166. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  167. sql_query += " AND created_at >= :start"
  168. arg_dict["start"] = start_datetime_utc
  169. if args["end"]:
  170. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  171. end_datetime = end_datetime.replace(second=0)
  172. end_datetime_timezone = timezone.localize(end_datetime)
  173. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  174. sql_query += " AND created_at < :end"
  175. arg_dict["end"] = end_datetime_utc
  176. sql_query += " GROUP BY date ORDER BY date"
  177. response_data = []
  178. with db.engine.begin() as conn:
  179. rs = conn.execute(db.text(sql_query), arg_dict)
  180. for i in rs:
  181. response_data.append(
  182. {"date": str(i.date), "token_count": i.token_count, "total_price": i.total_price, "currency": "USD"}
  183. )
  184. return jsonify({"data": response_data})
  185. class AverageSessionInteractionStatistic(Resource):
  186. @setup_required
  187. @login_required
  188. @account_initialization_required
  189. @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
  190. def get(self, app_model):
  191. account = current_user
  192. parser = reqparse.RequestParser()
  193. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  194. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  195. args = parser.parse_args()
  196. sql_query = """SELECT
  197. DATE(DATE_TRUNC('day', c.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  198. AVG(subquery.message_count) AS interactions
  199. FROM
  200. (
  201. SELECT
  202. m.conversation_id,
  203. COUNT(m.id) AS message_count
  204. FROM
  205. conversations c
  206. JOIN
  207. messages m
  208. ON c.id = m.conversation_id
  209. WHERE
  210. c.app_id = :app_id"""
  211. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  212. timezone = pytz.timezone(account.timezone)
  213. utc_timezone = pytz.utc
  214. if args["start"]:
  215. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  216. start_datetime = start_datetime.replace(second=0)
  217. start_datetime_timezone = timezone.localize(start_datetime)
  218. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  219. sql_query += " AND c.created_at >= :start"
  220. arg_dict["start"] = start_datetime_utc
  221. if args["end"]:
  222. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  223. end_datetime = end_datetime.replace(second=0)
  224. end_datetime_timezone = timezone.localize(end_datetime)
  225. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  226. sql_query += " AND c.created_at < :end"
  227. arg_dict["end"] = end_datetime_utc
  228. sql_query += """
  229. GROUP BY m.conversation_id
  230. ) subquery
  231. LEFT JOIN
  232. conversations c
  233. ON c.id = subquery.conversation_id
  234. GROUP BY
  235. date
  236. ORDER BY
  237. date"""
  238. response_data = []
  239. with db.engine.begin() as conn:
  240. rs = conn.execute(db.text(sql_query), arg_dict)
  241. for i in rs:
  242. response_data.append(
  243. {"date": str(i.date), "interactions": float(i.interactions.quantize(Decimal("0.01")))}
  244. )
  245. return jsonify({"data": response_data})
  246. class UserSatisfactionRateStatistic(Resource):
  247. @setup_required
  248. @login_required
  249. @account_initialization_required
  250. @get_app_model
  251. def get(self, app_model):
  252. account = current_user
  253. parser = reqparse.RequestParser()
  254. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  255. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  256. args = parser.parse_args()
  257. sql_query = """SELECT
  258. DATE(DATE_TRUNC('day', m.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  259. COUNT(m.id) AS message_count,
  260. COUNT(mf.id) AS feedback_count
  261. FROM
  262. messages m
  263. LEFT JOIN
  264. message_feedbacks mf
  265. ON mf.message_id=m.id AND mf.rating='like'
  266. WHERE
  267. m.app_id = :app_id"""
  268. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  269. timezone = pytz.timezone(account.timezone)
  270. utc_timezone = pytz.utc
  271. if args["start"]:
  272. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  273. start_datetime = start_datetime.replace(second=0)
  274. start_datetime_timezone = timezone.localize(start_datetime)
  275. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  276. sql_query += " AND m.created_at >= :start"
  277. arg_dict["start"] = start_datetime_utc
  278. if args["end"]:
  279. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  280. end_datetime = end_datetime.replace(second=0)
  281. end_datetime_timezone = timezone.localize(end_datetime)
  282. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  283. sql_query += " AND m.created_at < :end"
  284. arg_dict["end"] = end_datetime_utc
  285. sql_query += " GROUP BY date ORDER BY date"
  286. response_data = []
  287. with db.engine.begin() as conn:
  288. rs = conn.execute(db.text(sql_query), arg_dict)
  289. for i in rs:
  290. response_data.append(
  291. {
  292. "date": str(i.date),
  293. "rate": round((i.feedback_count * 1000 / i.message_count) if i.message_count > 0 else 0, 2),
  294. }
  295. )
  296. return jsonify({"data": response_data})
  297. class AverageResponseTimeStatistic(Resource):
  298. @setup_required
  299. @login_required
  300. @account_initialization_required
  301. @get_app_model(mode=AppMode.COMPLETION)
  302. def get(self, app_model):
  303. account = current_user
  304. parser = reqparse.RequestParser()
  305. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  306. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  307. args = parser.parse_args()
  308. sql_query = """SELECT
  309. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  310. AVG(provider_response_latency) AS latency
  311. FROM
  312. messages
  313. WHERE
  314. app_id = :app_id"""
  315. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  316. timezone = pytz.timezone(account.timezone)
  317. utc_timezone = pytz.utc
  318. if args["start"]:
  319. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  320. start_datetime = start_datetime.replace(second=0)
  321. start_datetime_timezone = timezone.localize(start_datetime)
  322. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  323. sql_query += " AND created_at >= :start"
  324. arg_dict["start"] = start_datetime_utc
  325. if args["end"]:
  326. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  327. end_datetime = end_datetime.replace(second=0)
  328. end_datetime_timezone = timezone.localize(end_datetime)
  329. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  330. sql_query += " AND created_at < :end"
  331. arg_dict["end"] = end_datetime_utc
  332. sql_query += " GROUP BY date ORDER BY date"
  333. response_data = []
  334. with db.engine.begin() as conn:
  335. rs = conn.execute(db.text(sql_query), arg_dict)
  336. for i in rs:
  337. response_data.append({"date": str(i.date), "latency": round(i.latency * 1000, 4)})
  338. return jsonify({"data": response_data})
  339. class TokensPerSecondStatistic(Resource):
  340. @setup_required
  341. @login_required
  342. @account_initialization_required
  343. @get_app_model
  344. def get(self, app_model):
  345. account = current_user
  346. parser = reqparse.RequestParser()
  347. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  348. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  349. args = parser.parse_args()
  350. sql_query = """SELECT
  351. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  352. CASE
  353. WHEN SUM(provider_response_latency) = 0 THEN 0
  354. ELSE (SUM(answer_tokens) / SUM(provider_response_latency))
  355. END as tokens_per_second
  356. FROM
  357. messages
  358. WHERE
  359. app_id = :app_id"""
  360. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  361. timezone = pytz.timezone(account.timezone)
  362. utc_timezone = pytz.utc
  363. if args["start"]:
  364. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  365. start_datetime = start_datetime.replace(second=0)
  366. start_datetime_timezone = timezone.localize(start_datetime)
  367. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  368. sql_query += " AND created_at >= :start"
  369. arg_dict["start"] = start_datetime_utc
  370. if args["end"]:
  371. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  372. end_datetime = end_datetime.replace(second=0)
  373. end_datetime_timezone = timezone.localize(end_datetime)
  374. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  375. sql_query += " AND created_at < :end"
  376. arg_dict["end"] = end_datetime_utc
  377. sql_query += " GROUP BY date ORDER BY date"
  378. response_data = []
  379. with db.engine.begin() as conn:
  380. rs = conn.execute(db.text(sql_query), arg_dict)
  381. for i in rs:
  382. response_data.append({"date": str(i.date), "tps": round(i.tokens_per_second, 4)})
  383. return jsonify({"data": response_data})
  384. api.add_resource(DailyMessageStatistic, "/apps/<uuid:app_id>/statistics/daily-messages")
  385. api.add_resource(DailyConversationStatistic, "/apps/<uuid:app_id>/statistics/daily-conversations")
  386. api.add_resource(DailyTerminalsStatistic, "/apps/<uuid:app_id>/statistics/daily-end-users")
  387. api.add_resource(DailyTokenCostStatistic, "/apps/<uuid:app_id>/statistics/token-costs")
  388. api.add_resource(AverageSessionInteractionStatistic, "/apps/<uuid:app_id>/statistics/average-session-interactions")
  389. api.add_resource(UserSatisfactionRateStatistic, "/apps/<uuid:app_id>/statistics/user-satisfaction-rate")
  390. api.add_resource(AverageResponseTimeStatistic, "/apps/<uuid:app_id>/statistics/average-response-time")
  391. api.add_resource(TokensPerSecondStatistic, "/apps/<uuid:app_id>/statistics/tokens-per-second")