Commit Graph

128 Commits

Author SHA1 Message Date
Hartmut c17b7d2e8f feat: execute workflow bridge nodes in graph runtime 2026-04-07 10:42:59 +02:00
Hartmut 6ad34ceed2 feat: add workflow run dispatch foundation 2026-04-07 10:11:46 +02:00
Hartmut 98b3eadcb2 feat: extract workflow notifications phase 3 2026-04-07 09:57:39 +02:00
Hartmut 160c198bb3 feat: extract workflow output save phase 3 2026-04-07 09:50:58 +02:00
Hartmut 9c93ecef49 feat: extract workflow bbox services phase 3 2026-04-07 09:42:06 +02:00
Hartmut 8f8d2e68b7 feat: extract workflow material services phase 3 2026-04-07 09:22:24 +02:00
Hartmut e3cda1c9f7 feat: extract workflow runtime phase 3 foundation 2026-04-07 09:09:40 +02:00
Hartmut 56ee5fc5bf feat: add workflow node registry phase 2 2026-04-07 08:59:27 +02:00
Hartmut 63e35ce807 feat: stabilize workflow phase 1 foundation 2026-04-07 08:48:48 +02:00
Hartmut 2a00abe91f fix: restore historical order visibility for HartOMat admins 2026-04-06 19:24:09 +02:00
Hartmut 448996b546 fix: stabilize HartOMat runtime startup 2026-04-06 13:10:51 +02:00
Hartmut b795f0e6d6 refactor: rebrand project to HartOMat 2026-04-06 12:45:47 +02:00
Hartmut fa7093307a chore: snapshot before HartOMat rebrand 2026-04-06 12:41:44 +02:00
Hartmut feef2a0827 feat: AI searches product part materials — finds products WITH Durotect
Added product_material filter to find_product_renders:
- Searches cad_part_materials JSONB for materials assigned to CAD parts
- find_product_renders(transparent_only=true, product_material="Durotect")
  → finds 9 products that naturally have Durotect parts with transparent renders

Two material levels explained in system prompt rule 13:
- product_material: materials from STEP/Excel (Durotect_M, Stahl, Bronze)
- material_override: single material forced on ALL parts at render time

AI now searches product_material FIRST when user asks "with Durotect material"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:47:01 +01:00
Hartmut 9c6b210d51 fix: AI agent shows best available render when exact match not found
Rule 13: when searching with multiple criteria (transparent + Durotect),
decompose the search. Show what exists (1220 transparent renders) and
explain what's missing (no Durotect material applied). Never say
"no renders found" when transparent renders DO exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:43:01 +01:00
Hartmut 54522a63d4 feat: render metadata from render_log for past renderings
find_product_renders now reads actual render metadata from the
render_log JSONB (stored at render time) via COALESCE fallback:
- engine: render_log.engine_used > render_log.engine > output_type setting
- width/height: render_log > output_type.render_settings
- samples: render_log > output_type.render_settings
- render_type: still/turntable/cinematic from render_log.type
- has_template: whether a .blend template was used

All past renderings now have correct metadata without re-processing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:35:19 +01:00
Hartmut 86921bacbd feat: render metadata in find_product_renders — material, format, resolution
find_product_renders now returns full render job metadata:
- material_override (effective: line override > output type override)
- output_format (with render_overrides applied)
- resolution (width x height)
- engine, samples
- order_number + order_id (for linking)
- is_animation flag

New material_override filter: search renders by material name
(e.g. transparent_only=true, material_override="Durotect" finds
renders with Durotect material on transparent background)

AI can now answer: "Show me a transparent Durotect render" by filtering
both transparency AND material in one query.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:24:02 +01:00
Hartmut 59f83f10ad fix: AI agent must confirm before ANY write action including overrides
Explicitly listed all write tools that require confirmation:
create_order, dispatch_renders, set_material_override, set_render_overrides

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:14:48 +01:00
Hartmut 8897afdebb fix: AI agent must confirm before creating orders or dispatching renders
Split rules into read-only vs write actions:
- READ (search, list, show images, status): execute immediately
- WRITE (create orders, dispatch, set overrides): ALWAYS ask for
  confirmation before executing

Prevents accidental render job creation from casual questions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:11:52 +01:00
Hartmut 20bcdee2a2 feat: AI agent links to products and orders in responses
System prompt rule 12: always format product mentions as
[ProductName](/products/UUID) and orders as [OrderNumber](/orders/UUID).

ReactMarkdown in ChatPanel already renders these as clickable links
with accent color styling, so users can navigate directly from chat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:07:44 +01:00
Hartmut ef4c8eefc9 feat: AI agent knows material library — list_materials tool with alias search
Added list_materials tool to the chat agent:
- Searches SCHAEFFLER library materials by name, description, or alias
- Returns material name + schaeffler_code + aliases
- Enables: "zeig mir ein Bild mit Durotect-Material" → agent searches
  for "durotect" → finds SCHAEFFLER_020101_Durotect-Blue → uses as
  material_override

System prompt updated with rules 10-11:
- Explains alias → library material mapping
- Always use full SCHAEFFLER name for material_override

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 10:05:05 +01:00
Hartmut c159bff2df fix: clean frames directory before cinematic/turntable re-render
Old frame PNGs from previous render attempts persisted in the frames
directory, causing FFmpeg to stitch the wrong number of frames.
Now rmtree's the directory before creating it fresh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 22:02:20 +01:00
Hartmut 458c6cd813 refactor: cinematic render — linear keyframes, 3 segments, 250 frames, white bg
Changes per user feedback:
- Keyframe interpolation: BEZIER → LINEAR (all fcurves set to LINEAR)
- Removed segment 4 (closeup) — now 3 segments only
- Frame count: 480 → 250 (10 seconds at 25fps)
- FPS: 24 → 25
- Easing removed — pure linear interpolation between segment params
- White background by default (World node Color = white)
- Transparent bg still available as override

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:57:42 +01:00
Hartmut 75ad397c09 fix: unbuffered stdout for live cinematic frame progress
Two fixes for frame progress not appearing in frontend:
1. Added flush=True to all print() calls in cinematic_render.py
2. Set PYTHONUNBUFFERED=1 in subprocess environment

Without these, Python buffers stdout inside Blender, so all frame
progress lines arrive in a batch after the process exits instead
of streaming line-by-line during rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:53:26 +01:00
Hartmut e0714854d2 fix: stream Blender frame output to frontend for cinematic renders
The filter only matched [cinematic_render] tag but Blender outputs its
own "Saved: frame_NNNN.png" lines per frame. Now also matches "Saved:"
lines, extracts the frame number, and formats as:
  [cinematic_render] Frame 84/480 rendered

This shows live frame progress in the LiveRenderLog on the frontend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:42:36 +01:00
Hartmut caffe7809c feat: live frame progress streaming for cinematic renders
Replaced communicate() (blocking) with selectors-based line-by-line
stdout streaming — same pattern as still render. Each frame now
streams live to the frontend:

  [cinematic_render] Frame 42/480 -- 55.3s elapsed (0.76 fps)

Pipeline: Blender stdout → log_callback → emit() → Redis →
LiveRenderLog poll (2s) → frontend display

Also added log_callback parameter to cinematic render task call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:39:41 +01:00
Hartmut e26d76154b fix: cinematic render arg order — match turntable positional layout
The service was sending (glb, frames_dir, width, height, engine, samples...)
but the script expected turntable order (glb, frames_dir, frame_count, degrees,
width, height, engine, samples...). Fixed by adding frame_count and degrees
placeholders to match the expected positional layout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:32:34 +01:00
Hartmut f22b963be9 feat: cinematic highlight render — 20s procedural camera animation
New render type: 4-segment cinematic camera animation (480 frames @ 24fps)
for professional product highlight videos.

Camera sequence:
1. Establishing (5s): slow 45° orbit + push-in, 50mm lens
2. Detail sweep (5s): low-angle close arc, 85mm telephoto, shallow DOF
3. Crane up (5s): rising 30°→60°, 35mm wide reveal, pull-back
4. Hero close (5s): push-in to beauty angle, 65mm, smooth ease-out

Technical:
- cinematic_render.py: procedural camera from bounding sphere, cubic easing,
  per-frame keyframes (location, rotation, focal length, DOF)
- render_cinematic_to_file(): service function (same pattern as turntable)
- Pipeline routing: render_settings.cinematic flag → cinematic path
- Depth of field enabled (f-stop scales with product size)
- use_persistent_data for BVH caching between frames
- Same material/template/USD pipeline as turntable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:25:56 +01:00
Hartmut 0dfadbfd56 fix: make AI agent proactive — never ask for info it can look up
System prompt rewritten with explicit RULES:
- Never ask user for info you can query yourself
- "Any product" / "beliebig" → just pick one, don't ask back
- Execute immediately, no confirmation needed
- Be concise, short answers preferred

find_product_renders tool:
- No longer requires product_name or product_id
- Call with empty params → returns any recent renders
- Enables "show me any render with transparent bg"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 19:56:21 +01:00
Hartmut 29f7103a8b feat: chat agent can find and show existing product renders
Added find_product_renders tool to the AI agent:
- Searches completed renders by product name/ID
- Returns viewable image URLs (relative paths)
- Supports transparent_only filter
- AI formats results as Markdown images/links in chat

Frontend:
- ChatPanel ReactMarkdown renders <img> and <a> tags
- Images shown inline (max 200px height, rounded, bordered)
- Links open in new tab with accent color

System prompt updated to instruct AI to use Markdown image syntax
when showing renders: ![description](/renders/...)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 19:52:45 +01:00
Hartmut 531994cccd fix: OCP import for BRepGProp + route metadata task to asset_pipeline
- Fixed: from OCP.BRepGProp import BRepGProp as brepgprop (was lowercase)
- Routed reextract_rich_metadata_task to asset_pipeline queue (render-worker
  has OCC/OCP installed, worker container does not)
- Backfill verified: 45/45 products updated with volume, surface area,
  part count, complexity metrics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 19:50:12 +01:00
Hartmut cfccdd5397 feat: rich product metadata extraction from STEP files
Extract volume, surface area, part count, assembly hierarchy, and
complexity from STEP files via OCC B-rep analysis.

Backend:
- extract_rich_metadata() in step_processor.py: computes per-part volume
  (BRepGProp), surface area, triangle/vertex count, assembly depth,
  instance count, complexity score, largest part identification
- cad_metadata JSONB column on Product model (DB migration)
- Auto-populated during STEP processing (non-fatal, 10s timeout)
- Also stored in cad_files.mesh_attributes["rich_metadata"]
- Batch re-extract endpoint: POST /admin/settings/reextract-rich-metadata

AI Agent:
- search_products returns part_count, volume_cm3, complexity, largest_part
- query_database tool description documents cad_metadata schema

Frontend:
- ProductDetail page: CAD Metadata section with stat cards
  (parts, volume, surface area, complexity, triangles, assembly depth)
- Admin System Tools: "Re-extract Rich Metadata" button for backfill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:49:50 +01:00
Hartmut 0ffc86589a fix: chat agent auth, auto-submit/dispatch, no confirmation prompts
- All httpx tool calls use real user_id instead of uuid(int=0) for
  service token — fixes 401 Unauthorized on dispatch/override
- create_order auto-submits and auto-dispatches in one step
- System prompt updated: execute immediately without asking for
  confirmation, respond in user's language
- Product search returns CAD dimensions (dim_x/y/z_mm)
- query_database tool description includes cad_files schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 15:59:53 +01:00
Hartmut f70a09886a feat: expose CAD dimensions (mm) in chat agent tools
- search_products now returns dim_x_mm, dim_y_mm, dim_z_mm from
  cad_files.mesh_attributes->'dimensions_mm'
- query_database tool description updated with cad_files schema
  including mesh_attributes->'dimensions_mm' path
- AI can now answer "what's the biggest product?" using real dimensions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 15:05:37 +01:00
Hartmut d37dd073bd feat: chat agent knows current page context (order/product)
Frontend: Layout extracts order/product UUID from URL path and passes
to ChatPanel as contextType/contextId. When on /orders/{uuid}, the
chat knows which order you're viewing.

Backend: System prompt now loads actual entity data for the context:
- Order: order_number, status, line counts (completed/processing/failed)
- Product: name, PIM-ID, category, STEP file status

The AI understands "this order", "current product" etc. Example:
"What's the status of this order?" → agent knows you mean SA-2026-00164

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:50:03 +01:00
Hartmut 48b5287baf fix: rollback DB session after failed tool execution in chat agent
When a tool like query_database fails (e.g., bad column name), the
SQLAlchemy session enters a failed transaction state. Subsequent
operations (like saving the assistant response) then also fail with
InFailedSQLTransactionError.

Fix: rollback the session in the except block of _execute_tool().
Also improved query_database tool description with correct column
names (category_key not category) to help the AI write valid SQL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:32:33 +01:00
Hartmut 9f72840722 fix: better error handling for chat AI errors
Catch all exceptions (not just ValueError) and return meaningful
error messages from OpenAI API errors instead of generic 500.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:22:54 +01:00
Hartmut 502e2d0387 fix: use max_completion_tokens instead of max_tokens for GPT-4o
Azure OpenAI GPT-4o and newer models require 'max_completion_tokens'
instead of 'max_tokens'. Fixed in all 3 services:
- chat_service.py (2 call sites)
- azure_ai.py (validation service)
- tenants/router.py (test connection endpoint)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:20:40 +01:00
Hartmut 59ce61098c feat: tenant AI chat agent with function calling
Actionable AI assistant that uses per-tenant Azure OpenAI credentials
to execute natural language commands against the render pipeline.

Backend:
- ChatMessage model + migration (session-based conversations)
- Chat service with 10 OpenAI function-calling tools:
  list_orders, search_products, create_order, dispatch_renders,
  get_order_status, set_material_override, set_render_overrides,
  get_render_stats, check_materials, query_database
- All tools tenant-scoped (queries filtered by tenant_id)
- Write operations use httpx to call backend API internally
- Chat API: POST /chat/messages, GET /chat/sessions, DELETE session
- Conversation history preserved in DB (last 50 messages per session)

Frontend:
- Slide-out ChatPanel (right side, w-96, animated)
- User/assistant message styling with avatars and timestamps
- Session management (new chat, session history, delete)
- Typing indicator while waiting for AI response
- Floating chat button in bottom-right corner
- Error state for unconfigured AI tenants

Example: "Render all Kugellager products as WebP at 1024x1024"
→ Agent calls search_products + create_order + dispatch_renders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 12:46:21 +01:00
Hartmut daad2c64f3 fix: revert dual queue to single GPU — light worker caused 2x regression
Root cause: render-worker and render-worker-light shared the same GPU,
causing contention. Complex TRB renders went from 17s → 36s (2x slower).

Changes:
- Thumbnails back to asset_pipeline queue (not asset_pipeline_light)
- Dispatch routing always uses asset_pipeline (no queue splitting)
- render-worker-light gated behind "multi-gpu" profile — only starts with:
  docker compose --profile multi-gpu up -d
- For single-GPU setups: all rendering is sequential on one worker

The dual queue approach is correct for multi-GPU machines where each
worker gets its own GPU. On single-GPU, serial execution is faster
than concurrent GPU contention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 12:33:26 +01:00
Hartmut b892f72f7e feat: per-line render overrides — override any output type setting at order time
Instead of duplicating output types for every variation (WebP vs PNG,
different resolution), keep one canonical output type and override
specific fields per order line via render_overrides JSONB.

Backend:
- render_overrides JSONB column on OrderLine (DB migration)
- Render task merges overrides with output type settings (format, width,
  height, samples, engine, denoiser, transparent_bg, cycles_device)
- POST /orders/{id}/batch-render-overrides endpoint for bulk override
- PatchLineBody accepts render_overrides for per-line patching

Frontend:
- Batch render overrides section on OrderDetail: output format dropdown
  (PNG/JPG/WebP) + resolution dropdown (512-4096)
- Clear button to remove overrides

MCP:
- create_order tool: accepts product_ids, output_type, render_overrides,
  material_override — enables "render all products as WebP" via Claude
- set_render_overrides tool: batch override on existing orders

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 12:26:38 +01:00
Hartmut 5a148554c0 perf: dual queue, GLB caching, WebP output, persistent BVH
Task 4: Dual render queue
- render-worker: heavy (asset_pipeline, concurrency=1) — HQ 2048x2048, animations
- render-worker-light: light (asset_pipeline_light, concurrency=2) — thumbnails, <=1024
- Thumbnails routed to light queue automatically
- Order line renders routed by resolution at dispatch time

Task 5: GLB caching (skip re-tessellation)
- Before tessellating, check if gltf_geometry MediaAsset exists for the cad_file_id
- If found, copy to expected path — render_blender.py finds it and skips tessellation
- Saves 7-11s per re-render of the same product

Task 6: WebP output format
- New 'webp' option in output_format (OutputType admin)
- Blender renders PNG intermediate, Pillow converts to WebP (quality=90, method=4)
- 50-70% smaller files with no visible quality loss
- Correct MIME type (image/webp) in MediaAsset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 12:07:12 +01:00
Hartmut ffe3eebfca perf: render pipeline optimizations — sample scaling, USD logging, persistent BVH
Task 1: Resolution-aware sample count
- Auto-scale samples for resolutions <= 1024: max(32, samples * max_dim / 2048)
- 512x512 thumbnails: 256 → 64 samples (75% GPU savings)
- Thumbnail tasks capped at 64 samples via context manager
- 2048x2048 HQ renders unchanged

Task 2: USD path preference audit + logging
- Verified USD master path is correctly preferred over GLB tessellation
- Added clear emit() messages: "Using USD master" vs "No USD master — GLB path"
- Dynamic render log label: "USD → Blender" vs "STEP → GLB → Blender"

Task 3: Persistent BVH for turntable animations
- Added scene.render.use_persistent_data = True before frame loop
- BVH acceleration structure cached between frames (not rebuilt per frame)
- Applies to both camera orbit and object rotation modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 12:03:31 +01:00
Hartmut 2c7eb81aab refactor: clean up Render Settings — remove 11 unused settings, fix Blender status
Removed from UI (saved to DB but never read by any service):
- Max Concurrent Renders, Stall Timeout, Thumbnail Format, Product Thumbnail Priority
- Render Linear/Angular Deflection (only Scene deflections are used)
- GLB Scale Factor, Smooth Normals, GLB Material Mode, PBR Roughness, PBR Metallic

Fixed Blender status check:
- Old: called is_blender_available() in backend container (Blender not installed there)
- New: dispatches Celery task on asset_pipeline queue → runs in render-worker container
- Returns: available=true, version="Blender 5.0.1", binary path
- Status card moved to System Tools tab with refresh button

Kept active: engine, device, samples, smooth angle, tessellation, scene deflections,
3D viewer zoom limits

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 09:37:54 +01:00
Hartmut 24833ce52e fix: pass material_override through when creating order lines
create_order and add_order_line endpoints were not passing
material_override from the request body to the OrderLine constructor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:48:30 +01:00
Hartmut d84ce8252e feat: batch material override — apply to all lines in an order at once
- POST /orders/{id}/batch-material-override endpoint
- Dropdown above the lines table: "Apply to all lines…"
- Options: clear all overrides, or select a library material
- Updates all order lines in one request

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:36:18 +01:00
Hartmut 9d6def84c1 feat: per-order-line material override — override materials for individual renders
- Add `material_override` nullable column on OrderLine (DB migration)
- Line override takes priority over OutputType override
- PATCH /orders/{id}/lines/{id} endpoint to update material_override
- Inline dropdown on each order line in the OrderDetail page
- Amber background when override is active
- Same output type, different material per line — no need to create a new output type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:33:00 +01:00
Hartmut c054236d22 fix: material override pipeline — pass --material-override CLI arg to Blender scripts
The initial implementation only overrode the material_map dict in the task,
but the Blender USD primvar path bypassed it. Now:
- Added --material-override named CLI arg parsed in _blender_args.py
- Both Mode A (factory) and Mode B (template) in _blender_scene_setup.py
  override usd_material_lookup and material_map when set
- Passed through full chain: task → step_processor → render_blender → CLI → Blender
- Tested: 175-part bearing rendered with single Steel-Bare material (1/1 materials)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:19:21 +01:00
Hartmut 7c606953ec feat: global material override on OutputType for x-ray/clay render modes
- Add `material_override` nullable column on OutputType (DB migration)
- When set, ALL product parts get rendered with this single material
- Override applies after alias resolution in render_order_line task
- Admin UI: dropdown in OutputType table to select a library material
- Display: amber badge showing active override material name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:16:00 +01:00
Hartmut b6bac080bb feat: duplicate product detection — STEP conflict warnings on Excel import and CAD upload
- Excel preview detects when a product already has a different STEP file linked
- Excel preview detects intra-Excel conflicts (same product, different CAD model names)
- Product STEP upload warns when replacing an existing file and shows render count
- All warnings are non-blocking (amber badges, toast warnings)
- LEARNINGS.md: all open items resolved

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:05:40 +01:00