{
  "name": "My workflow",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "/grade-card",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -208,
        -64
      ],
      "id": "8e1b0603-a85a-407d-b490-064a3738e6ca",
      "name": "Webhook"
    },
    {
      "parameters": {
        "numberInputs": 4
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        -16,
        512
      ],
      "id": "8a63f0b7-6c7c-4dd8-bf67-52922cc9a0b6",
      "name": "Merge"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        640,
        1120
      ],
      "id": "286b4388-b5f6-423f-9f28-45ecb85485d7",
      "name": "Respond to Webhook"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "claude-haiku-4-5-20251001",
          "mode": "list",
          "cachedResultName": "Claude Haiku 4.5"
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        768,
        800
      ],
      "id": "d3343a40-aca9-4244-a3df-f368a6fe0793",
      "name": "Anthropic Chat Model",
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "claude-haiku-4-5-20251001",
          "mode": "list",
          "cachedResultName": "Claude Haiku 4.5"
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        640,
        832
      ],
      "id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
      "name": "Anthropic Chat Model 2",
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=http://backend:8000/agents/crop",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "file_path",
              "value": "={{ $json.body.saved_path }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        64,
        16
      ],
      "id": "c4c83424-af2e-49ae-aa53-f05e73f1ed5d",
      "name": "Preprocessing"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/agents/centering",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "file_path",
              "value": "={{ $json.cropped_path }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        -352,
        304
      ],
      "id": "0c605c7d-5208-44e9-a13b-eb8372808ec9",
      "name": "centering agent"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/agents/corner",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "file_path",
              "value": "={{ $json.cropped_path }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        0,
        288
      ],
      "id": "9b5c9ab8-401c-4612-86c2-e3f8108cfc3b",
      "name": "corner agent"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/agents/edge",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "file_path",
              "value": "={{ $json.cropped_path }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        720,
        208
      ],
      "id": "fad13dce-ca32-4bbf-ac7b-bd269ed27bf0",
      "name": "edge agent"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Be generous with surface_score. Minor scuffs, small scratches, or light\n  print lines that do not significantly affect the card's appearance should\n  still score 9.0 or above. Only score below 6 if there are clear, prominent\n  defects visible to the naked eye.\n\n  Respond in raw JSON only, no markdown:\n  {\"surface_score\": <0-10>, \"defects_found\": [\"...\"], \"reasoning\": \"...\"}\n\n  Scoring: 10=Gem Mint  8-9=Near Mint  6-7=Excellent  4-5=VG  0-3=Poor\n\n  Image (base64): {{ $json.cropped_image }}",
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        416,
        304
      ],
      "id": "a5195c47-22f8-49e9-828d-8d5ab1e07f66",
      "name": "surface agent"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are a trading card price analyst (Pokemon, Magic: The Gathering, Yu-Gi-Oh, sports cards, etc.).\n\nCard hint: {{ $json.card_name_hint || \"Unknown\" }}{{ $json.card_set_hint ? \" | Set: \" + $json.card_set_hint : \"\" }}\nPSA Grade: {{ $json.estimated_psa_grade }}\n\nIf the card name has a typo, correct it and use the closest matching real card.\nProvide estimated market price range in USD for this card at this PSA grade.\n\nRespond in raw JSON only \u2014 no markdown, no extra text, nothing after the closing brace:\n{\"card_name\":\"<corrected name>\",\"card_set\":\"<set or empty>\",\"card_number\":\"<e.g. 4/102 or empty>\",\"price_low\":<number>,\"price_mid\":<number>,\"price_high\":<number>,\"currency\":\"USD\"}",
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        384,
        928
      ],
      "id": "f1e2d3c4-b5a6-7890-1234-abcdef567890",
      "name": "price lookup"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://backend:8000/analysis/save",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "unique_filename",
              "value": "={{ $json.unique_filename }}"
            },
            {
              "name": "original_filename",
              "value": "={{ $json.original_filename }}"
            },
            {
              "name": "file_path",
              "value": "={{ $json.file_path }}"
            },
            {
              "name": "corner_score",
              "value": "={{ $json.corner_score }}"
            },
            {
              "name": "surface_score",
              "value": "={{ $json.surface_score }}"
            },
            {
              "name": "centering_score",
              "value": "={{ $json.centering_score }}"
            },
            {
              "name": "overall_score",
              "value": "={{ $json.overall_score }}"
            },
            {
              "name": "estimated_psa_grade",
              "value": "={{ $json.estimated_psa_grade }}"
            },
            {
              "name": "recommend_submit",
              "value": "={{ $json.recommend_submit }}"
            },
            {
              "name": "recommendation_reason",
              "value": "={{ $json.recommendation_reason }}"
            },
            {
              "name": "edge_score",
              "value": "={{ $json.edge_score }}"
            },
            {
              "name": "card_name",
              "value": "={{ $json.card_name }}"
            },
            {
              "name": "card_set",
              "value": "={{ $json.card_set }}"
            },
            {
              "name": "card_number",
              "value": "={{ $json.card_number }}"
            },
            {
              "name": "price_low",
              "value": "={{ $json.price_low }}"
            },
            {
              "name": "price_mid",
              "value": "={{ $json.price_mid }}"
            },
            {
              "name": "price_high",
              "value": "={{ $json.price_high }}"
            },
            {
              "name": "price_currency",
              "value": "={{ $json.price_currency }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        -48,
        1120
      ],
      "id": "8cbaca08-b99f-41b5-961f-ef9255987360",
      "name": "save database"
    },
    {
      "parameters": {
        "jsCode": "const webhookBody = $('Webhook').first().json.body;\n  const uniqueFilename   = webhookBody.unique_filename   || '';\n  const originalFilename = webhookBody.original_filename || '';\n  const savedPath        = webhookBody.saved_path        || '';\n  const cardNameHint     = webhookBody.card_name_hint    || '';\n  const cardSetHint      = webhookBody.card_set_hint     || '';\n\n  const all = $input.all();\n  const centering       = all[0].json.centering_score;\n  const corner          = all[1].json.corner_score;\n  const cornerBreakdown = all[1].json.corner_scores;\n  const edge            = all[3].json.edge_score;\n\n  const claudeText  = all[2].json.text;\n  const cleaned     = claudeText.replace(/```json\\s*/g, '').replace(/```\\s*/g, '').trim();\n  const surfaceData = JSON.parse(cleaned);\n  const surface     = surfaceData.surface_score;\n  const defects     = surfaceData.defects_found;\n  const reasoning   = surfaceData.reasoning;\n\n  const croppedImage = $('Preprocessing').first().json.cropped_image;\n\n  const overall = corner * 0.35 + surface * 0.25 + centering * 0.15 + edge * 0.25;\n\n  const thresholds = [\n    [10, 9.2], [9, 8.2], [8, 7.2], [7, 6.2],\n    [6,  5.2], [5, 4.2], [4, 3.2], [3, 2.2], [2, 1.2], [1, 0],\n  ];\n  let grade = 1;\n  for (const [g, min] of thresholds) {\n    if (overall >= min) { grade = g; break; }\n  }\n\n  const SUBMIT_THRESHOLD = 9.5;\n  const recommendSubmit  = grade >= SUBMIT_THRESHOLD;\n  const reason = recommendSubmit\n    ? `PSA ${grade} \u2014 gem mint (${overall.toFixed(1)}/10). High ROI.`\n    : `PSA ${grade} \u2014 below threshold (${overall.toFixed(1)}/10, need \\u2265 ${SUBMIT_THRESHOLD}). ${reasoning}`;\n\n  return [{\n    json: {\n      centering_score:       Math.round(centering * 10) / 10,\n      corner_score:          Math.round(corner    * 10) / 10,\n      surface_score:         Math.round(surface   * 10) / 10,\n      edge_score:            Math.round(edge      * 10) / 10,\n      corner_breakdown:      cornerBreakdown,\n      surface_defects:       defects,\n      surface_reasoning:     reasoning,\n      overall_score:         Math.round(overall * 100) / 100,\n      estimated_psa_grade:   grade,\n      recommend_submit:      recommendSubmit,\n      recommendation_reason: reason,\n      cropped_image:         croppedImage,\n      unique_filename:       uniqueFilename,\n      original_filename:     originalFilename,\n      file_path:             savedPath,\n      card_name_hint:        cardNameHint,\n      card_set_hint:         cardSetHint,\n    }\n  }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        112,
        768
      ],
      "id": "1242375e-ee44-485a-b823-8777fa8d9917",
      "name": "grade calculator"
    },
    {
      "parameters": {
        "jsCode": "const gradeData = $('grade calculator').first().json;\nconst priceText = $input.first().json.text || '';\n\nlet priceData = {};\ntry {\n  const match = priceText.match(/\\{[\\s\\S]*\\}/);\n  priceData = match ? JSON.parse(match[0]) : {};\n} catch (e) {\n  priceData = {};\n}\n\nreturn [{\n  json: {\n    ...gradeData,\n    card_name:      priceData.card_name     || 'Unknown',\n    card_set:       priceData.card_set      || '',\n    card_number:    priceData.card_number   || '',\n    price_low:      priceData.price_low     || 0,\n    price_mid:      priceData.price_mid     || 0,\n    price_high:     priceData.price_high    || 0,\n    price_currency: priceData.currency      || 'USD',\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        384,
        1040
      ],
      "id": "a2b3c4d5-e6f7-8901-2345-678901234567",
      "name": "merge price data"
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Preprocessing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "grade calculator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "surface agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model 2": {
      "ai_languageModel": [
        [
          {
            "node": "price lookup",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Preprocessing": {
      "main": [
        [
          {
            "node": "centering agent",
            "type": "main",
            "index": 0
          },
          {
            "node": "corner agent",
            "type": "main",
            "index": 0
          },
          {
            "node": "surface agent",
            "type": "main",
            "index": 0
          },
          {
            "node": "edge agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "centering agent": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "corner agent": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "edge agent": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "surface agent": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "grade calculator": {
      "main": [
        [
          {
            "node": "price lookup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "price lookup": {
      "main": [
        [
          {
            "node": "merge price data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "merge price data": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          },
          {
            "node": "save database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "9fe4f5dc-83c1-4bbd-9c7a-ae213c9d8642",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "tags": []
}