notification_template.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. """Notification template model for customizable notification messages."""
  2. from datetime import datetime
  3. from sqlalchemy import Boolean, DateTime, Integer, String, Text, func
  4. from sqlalchemy.orm import Mapped, mapped_column
  5. from backend.app.core.database import Base
  6. class NotificationTemplate(Base):
  7. """Model for notification message templates."""
  8. __tablename__ = "notification_templates"
  9. id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
  10. event_type: Mapped[str] = mapped_column(String(50), nullable=False, unique=True)
  11. name: Mapped[str] = mapped_column(String(100), nullable=False)
  12. title_template: Mapped[str] = mapped_column(Text, nullable=False)
  13. body_template: Mapped[str] = mapped_column(Text, nullable=False)
  14. is_default: Mapped[bool] = mapped_column(Boolean, default=True)
  15. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  16. updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
  17. # Default templates for seeding
  18. DEFAULT_TEMPLATES = [
  19. {
  20. "event_type": "print_start",
  21. "name": "Print Started",
  22. "title_template": "Print Started",
  23. "body_template": "{printer}: {filename}\nEstimated: {estimated_time}",
  24. },
  25. {
  26. "event_type": "print_complete",
  27. "name": "Print Completed",
  28. "title_template": "Print Completed",
  29. "body_template": "{printer}: {filename}\nTime: {duration}\nFilament: {filament_grams}g",
  30. },
  31. {
  32. "event_type": "print_failed",
  33. "name": "Print Failed",
  34. "title_template": "Print Failed",
  35. "body_template": "{printer}: {filename}\nTime: {duration}\nReason: {reason}",
  36. },
  37. {
  38. "event_type": "print_stopped",
  39. "name": "Print Stopped",
  40. "title_template": "Print Stopped",
  41. "body_template": "{printer}: {filename}\nTime: {duration}",
  42. },
  43. {
  44. "event_type": "print_progress",
  45. "name": "Print Progress",
  46. "title_template": "Print {progress}% Complete",
  47. "body_template": "{printer}: {filename}\nRemaining: {remaining_time}",
  48. },
  49. {
  50. # Post-print outcome confirmation (#1898). The body carries
  51. # {confirm_url}, which only opens the archive in Bambuddy: anything
  52. # that walks a URL out of a message body — a preview card, a mail
  53. # gateway, a proxy — then reaches a page that changes nothing.
  54. # {good_url} / {reject_url} are the single-use capability links and
  55. # stay available as variables, but they travel by default only in the
  56. # affordances nothing prefetches: the ntfy action buttons and the
  57. # Telegram inline keyboard, both built in notification_service.
  58. "event_type": "print_confirm_request",
  59. "name": "Print Outcome Confirmation",
  60. "title_template": "How did your print come out?",
  61. "body_template": "{printer}: {filename}\nConfirm: {confirm_url}",
  62. },
  63. {
  64. "event_type": "print_missing_spool_assignment",
  65. "name": "Missing Spool Assignment",
  66. "title_template": "Missing Spool Assignment",
  67. "body_template": "{printer}: print started with missing spool assignments\nSlots: {missing_slots}\nExpected profile:\n{missing_slot_details}",
  68. },
  69. {
  70. "event_type": "billing_charge_failed",
  71. "name": "Billing Charge Failed",
  72. "title_template": "Billing Charge Failed",
  73. "body_template": "{printer}: {filename}\nThe print charge could not be recorded. The budget reservation was retained.\nArchive: {archive_id}",
  74. },
  75. {
  76. "event_type": "printer_offline",
  77. "name": "Printer Offline",
  78. "title_template": "Printer Offline",
  79. "body_template": "{printer} has disconnected",
  80. },
  81. {
  82. "event_type": "printer_error",
  83. "name": "Printer Error",
  84. "title_template": "Printer Error: {error_type}",
  85. "body_template": "{printer}\n{error_detail}",
  86. },
  87. {
  88. "event_type": "ai_failure_detection",
  89. "name": "AI Failure Detection",
  90. "title_template": "Possible Print Failure Detected",
  91. "body_template": "{printer}: {task_name}\nConfidence: {confidence}\nAction taken: {action}",
  92. },
  93. {
  94. "event_type": "plate_not_empty",
  95. "name": "Plate Not Empty",
  96. "title_template": "Plate Not Empty - Print Paused",
  97. "body_template": "{printer}: Objects detected on build plate. Print has been paused. Clear plate and resume.",
  98. },
  99. {
  100. "event_type": "plate_clear_required",
  101. "name": "Plate Clear Required",
  102. "title_template": "Plate Clear Required",
  103. "body_template": "{printer}: print finished. Confirm the build plate is clear before the queue continues.",
  104. },
  105. {
  106. "event_type": "filament_low",
  107. "name": "Filament Low",
  108. "title_template": "Filament Low",
  109. "body_template": "{printer}: Slot {slot} at {remaining_percent}%",
  110. },
  111. {
  112. "event_type": "maintenance_due",
  113. "name": "Maintenance Due",
  114. "title_template": "Maintenance Due",
  115. "body_template": "{printer}:\n{items}",
  116. },
  117. {
  118. "event_type": "ams_humidity_high",
  119. "name": "AMS Humidity High",
  120. "title_template": "AMS Humidity Alert",
  121. "body_template": "{printer} {ams_label}: Humidity {humidity}% exceeds {threshold}% threshold",
  122. },
  123. {
  124. "event_type": "ams_temperature_high",
  125. "name": "AMS Temperature High",
  126. "title_template": "AMS Temperature Alert",
  127. "body_template": "{printer} {ams_label}: Temperature {temperature}°C exceeds {threshold}°C threshold",
  128. },
  129. {
  130. "event_type": "ams_drying_suspended",
  131. "name": "Auto-Drying Suspended",
  132. "title_template": "Auto-Drying Suspended",
  133. "body_template": (
  134. "{printer} {ams_label}: stopped automatic drying after {cycles} cycles left humidity at "
  135. "{humidity}%, still above the {threshold}% threshold. An AMS reads higher while it is warm, "
  136. "so raise the threshold or dry the spools off the printer."
  137. ),
  138. },
  139. {
  140. "event_type": "bed_cooled",
  141. "name": "Bed Cooled",
  142. "title_template": "Bed Cooled",
  143. "body_template": "{printer}: Bed cooled to {bed_temp}°C (threshold: {threshold}°C)",
  144. },
  145. {
  146. "event_type": "ha_sensor_alert",
  147. "name": "Printer Sensor Alert",
  148. "title_template": "Sensor Alert",
  149. "body_template": "{printer}: {sensor} is {state}",
  150. },
  151. {
  152. "event_type": "location_ha_sensor_alert",
  153. "name": "Storage Location Sensor Alert",
  154. "title_template": "Sensor Alert",
  155. "body_template": "{location}: {sensor} is {state}",
  156. },
  157. {
  158. "event_type": "first_layer_complete",
  159. "name": "First Layer Complete",
  160. "title_template": "First Layer Complete",
  161. "body_template": "{printer}: {filename}\nLayer 1/{total_layers} done",
  162. },
  163. {
  164. "event_type": "test",
  165. "name": "Test Notification",
  166. "title_template": "Bambuddy Test",
  167. "body_template": "This is a test notification. If you see this, notifications are working!",
  168. },
  169. # Queue notifications
  170. {
  171. "event_type": "queue_job_added",
  172. "name": "Queue Job Added",
  173. "title_template": "Job Queued",
  174. "body_template": "{job_name} added to queue for {target}",
  175. },
  176. {
  177. "event_type": "queue_job_assigned",
  178. "name": "Queue Job Assigned",
  179. "title_template": "Job Assigned",
  180. "body_template": "{job_name} assigned to {printer} (from Any {target_model} queue)",
  181. },
  182. {
  183. "event_type": "queue_job_started",
  184. "name": "Queue Job Started",
  185. "title_template": "Queue Job Started",
  186. "body_template": "{printer}: {job_name}\nEstimated: {estimated_time}",
  187. },
  188. {
  189. "event_type": "queue_job_waiting",
  190. "name": "Queue Job Waiting",
  191. "title_template": "Queue Job Waiting",
  192. "body_template": "{job_name} waiting for {target_model}\n{waiting_reason}",
  193. },
  194. {
  195. "event_type": "queue_job_skipped",
  196. "name": "Queue Job Skipped",
  197. "title_template": "Job Skipped",
  198. "body_template": "{printer}: {job_name}\nReason: {reason}",
  199. },
  200. {
  201. "event_type": "queue_job_failed",
  202. "name": "Queue Job Failed",
  203. "title_template": "Job Failed to Start",
  204. "body_template": "{printer}: {job_name}\nReason: {reason}",
  205. },
  206. {
  207. "event_type": "queue_completed",
  208. "name": "Queue Completed",
  209. "title_template": "Queue Complete",
  210. "body_template": "All {completed_count} queued jobs have finished",
  211. },
  212. {
  213. "event_type": "user_created",
  214. "name": "Welcome Email",
  215. "title_template": "Welcome to {app_name}",
  216. "body_template": "Welcome {username}!\n\nYour account has been created.\nUsername: {username}\nPassword: {password}\n\nLogin at: {login_url}",
  217. },
  218. {
  219. "event_type": "password_reset",
  220. "name": "Password Reset",
  221. "title_template": "{app_name} - Password Reset",
  222. "body_template": "Hello {username},\n\nYour password has been reset.\nNew Password: {password}\n\nLogin at: {login_url}",
  223. },
  224. # Inventory stock alert templates
  225. {
  226. "event_type": "stock_reorder_alert",
  227. "name": "Stock Reorder Alert",
  228. "title_template": "Reorder Alert: {material}",
  229. "body_template": "{material} ({brand}) has reached the reorder point.\nStock: {stock_g}g | Rate: {rate_g_day}g/day | Days left: {days_left}d\nReorder now to avoid a stock break.",
  230. },
  231. {
  232. "event_type": "stock_break_alert",
  233. "name": "Stock Break Alert",
  234. "title_template": "Stock Break Risk: {material}",
  235. "body_template": "{material} ({brand}) will run out before replenishment arrives.\nStock: {stock_g}g | Rate: {rate_g_day}g/day | Lead time: {lead_time_days}d\nOnly {days_left}d of stock remaining — order immediately.",
  236. },
  237. # User email notification templates (sent to the print job owner).
  238. # Names include " Email" so they aren't confused with the provider-level
  239. # `print_*` templates above, which share the same body shape but are
  240. # broadcast to admin-configured providers (ntfy/pushover/telegram/discord/
  241. # etc.) rather than mailed to a specific user.
  242. {
  243. "event_type": "user_print_start",
  244. "name": "User Print Started Email",
  245. "title_template": "Your Print Has Started",
  246. "body_template": "Hello {username},\n\nYour print job has started on {printer}.\n\nFile: {filename}\n\nYou will be notified when it completes.",
  247. },
  248. {
  249. "event_type": "user_print_complete",
  250. "name": "User Print Completed Email",
  251. "title_template": "Your Print Is Complete",
  252. "body_template": "Hello {username},\n\nYour print job has completed on {printer}.\n\nFile: {filename}",
  253. },
  254. {
  255. "event_type": "user_print_failed",
  256. "name": "User Print Failed Email",
  257. "title_template": "Your Print Has Failed",
  258. "body_template": "Hello {username},\n\nYour print job has failed on {printer}.\n\nFile: {filename}",
  259. },
  260. {
  261. "event_type": "user_print_stopped",
  262. "name": "User Print Stopped Email",
  263. "title_template": "Your Print Has Been Stopped",
  264. "body_template": "Hello {username},\n\nYour print job was stopped on {printer}.\n\nFile: {filename}",
  265. },
  266. ]