feat: MCP server, Bambu Cloud task sync & display name fix (#7)

* added mcp initial trail files

* timestamp use your local django timezone

* added bambu cloud task sync with correct endpoint other than py cloud api

* back fill and relink print name using cloud if there is

* use correct bump-version
This commit is contained in:
RunLit
2026-03-29 23:15:59 +11:00
committed by GitHub
parent 9a91b14593
commit fa90ef11b6
20 changed files with 1761 additions and 12 deletions

View File

@@ -492,6 +492,47 @@ class FilamentSnapshot(models.Model):
return f"Tray {self.tray_id}: {filament_info}"
class BambuCloudTask(models.Model):
"""Cloud task record synced from Bambu Cloud API (v1/user-service/my/tasks)."""
task_id = models.BigIntegerField(unique=True, db_index=True, help_text="Bambu Cloud task ID (matches MQTT task_id)")
design_id = models.IntegerField(null=True, blank=True, help_text="Makerworld design ID")
design_title = models.CharField(max_length=500, blank=True, help_text="Human project name from Makerworld (designTitle)")
plate_title = models.CharField(max_length=500, blank=True, help_text="Plate/variant name (matches MQTT subtask_name)")
model_id = models.CharField(max_length=100, blank=True)
profile_id = models.BigIntegerField(null=True, blank=True, help_text="Bambu Cloud profile ID")
plate_index = models.SmallIntegerField(null=True, blank=True)
device_serial = models.CharField(max_length=100, blank=True, help_text="Printer serial number from cloud")
cover_url = models.URLField(max_length=1000, blank=True, help_text="Plate preview image URL from S3")
weight_grams = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True, help_text="Actual filament weight reported by cloud")
length_mm = models.IntegerField(null=True, blank=True, help_text="Filament length in mm")
cost_time_seconds = models.IntegerField(null=True, blank=True, help_text="Cloud-measured print duration in seconds")
cloud_status = models.SmallIntegerField(null=True, blank=True, help_text="2=finish, 3=failed")
bed_type = models.CharField(max_length=50, blank=True)
use_ams = models.BooleanField(default=True)
print_mode = models.CharField(max_length=50, blank=True, help_text="cloud_file, local, etc.")
ams_detail_mapping = models.JSONField(default=list, help_text="Per-slot filament weight breakdown from cloud")
cloud_start_time = models.DateTimeField(null=True, blank=True)
cloud_end_time = models.DateTimeField(null=True, blank=True)
raw_data = models.JSONField(default=dict, help_text="Full task response — preserved for future use")
synced_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "infrastructure_cloud_task"
verbose_name = "Bambu Cloud Task"
verbose_name_plural = "Bambu Cloud Tasks"
ordering = ["-cloud_start_time"]
indexes = [
models.Index(fields=["task_id"]),
models.Index(fields=["design_id"]),
models.Index(fields=["-cloud_start_time"]),
]
def __str__(self):
name = self.design_title or self.plate_title or f"task-{self.task_id}"
return f"{name} ({self.cloud_start_time.strftime('%Y-%m-%d') if self.cloud_start_time else 'unknown date'})"
class PrintJob(models.Model):
"""Represents a single print job from start to finish"""
@@ -505,6 +546,16 @@ class PrintJob(models.Model):
)
gcode_file = models.CharField(max_length=200, null=True, blank=True)
cloud_task = models.ForeignKey(
'BambuCloudTask', on_delete=models.SET_NULL,
null=True, blank=True, related_name='print_jobs',
help_text="Linked Bambu Cloud task record (set by bambu_sync_cloud or collector)"
)
cloud_task_id_raw = models.BigIntegerField(
null=True, blank=True, db_index=True,
help_text="MQTT task_id — captured at job start, used to link cloud task"
)
start_time = models.DateTimeField(help_text="When print started")
end_time = models.DateTimeField(null=True, blank=True, help_text="When print finished/failed")
duration_minutes = models.IntegerField(null=True, blank=True, help_text="Total print duration")
@@ -544,6 +595,13 @@ class PrintJob(models.Model):
status = self.final_status or 'In Progress'
return f"{self.project_name} ({status}) - {self.start_time.strftime('%Y-%m-%d %H:%M')}"
@property
def display_name(self):
"""Human-readable job name: cloud design_title if available, else project_name."""
if self.cloud_task_id and self.cloud_task and self.cloud_task.design_title:
return self.cloud_task.design_title
return self.project_name
def calculate_duration(self):
"""Calculate print duration if end_time is set"""
if self.end_time and self.start_time: