{
  "name": "AI Money Tracker Chatbot",
  "nodes": [
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "id": "node-tg-trigger",
      "name": "Telegram Message Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        400
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const msg = $input.first().json;\nconst text = (msg.message?.text || '').trim();\nconst chatId = msg.message?.chat?.id || '';\nconst from = msg.message?.from?.first_name || 'User';\nconst isCommand = text.startsWith('/');\nlet command = '';\nlet args = [];\nif (isCommand) {\n  const parts = text.split(' ');\n  command = parts[0].toLowerCase();\n  args = parts.slice(1);\n}\nreturn [{ json: { userMessage: text, chatId, from, isCommand, command, args } }];"
      },
      "id": "node-extract-msg",
      "name": "Extract Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "is-command-check",
              "leftValue": "={{ $json.userMessage.charAt(0) }}",
              "rightValue": "/",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "id": "node-if-command",
      "name": "Is Command?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        680,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const { command, args, chatId } = $input.first().json;\n\nlet sql = '';\nlet replyType = 'text';\nlet staticReply = '';\nlet responseTemplate = '';\n\nif (command === '/saldo') {\n  sql = \"SELECT saldo_sekarang, updated_at FROM saldo_rekening ORDER BY id DESC LIMIT 1\";\n  responseTemplate = 'SALDO';\n  replyType = 'query';\n\n} else if (command === '/pengeluaran') {\n  sql = \"SELECT TO_CHAR(bulan, 'Month YYYY') AS bulan_label, total_pengeluaran, total_pemasukan, target_saving, sisa_budget, pct_saving_risk, CASE WHEN pct_saving_risk >= 100 THEN 'DANGER \ud83d\udd34' WHEN pct_saving_risk >= 80 THEN 'WARNING \ud83d\udfe1' ELSE 'AMAN \ud83d\udfe2' END AS status FROM monthly_budget WHERE bulan = DATE_TRUNC('month', NOW())::DATE\";\n  responseTemplate = 'PENGELUARAN';\n  replyType = 'query';\n\n} else if (command === '/export') {\n  sql = \"SELECT TO_CHAR(tanggal_waktu AT TIME ZONE 'Asia/Jakarta', 'DD Mon YYYY HH24:MI') AS waktu, tipe, merchant_deskripsi AS merchant, kategori_otomatis AS kategori, nominal, saldo_akhir FROM jago_transactions WHERE DATE_TRUNC('month', tanggal_waktu) = DATE_TRUNC('month', NOW()) ORDER BY tanggal_waktu DESC LIMIT 200\";\n  responseTemplate = 'EXPORT';\n  replyType = 'export';\n\n} else if (command === '/budget') {\n  const newBudget = parseFloat(args[0]);\n  if (!newBudget || isNaN(newBudget)) {\n    staticReply = '\u274c Format salah. Gunakan: /budget <nominal>\\nContoh: /budget 4000000';\n  } else {\n    sql = `UPDATE monthly_budget SET target_saving = ${newBudget} WHERE bulan = DATE_TRUNC('month', NOW())::DATE`;\n    responseTemplate = `\u2705 Target saving diubah menjadi *Rp ${newBudget.toLocaleString('id-ID')}*`;\n    replyType = 'update';\n  }\n\n} else if (command === '/reset_saldo') {\n  const newSaldo = parseFloat(args[0]);\n  if (!newSaldo || isNaN(newSaldo)) {\n    staticReply = '\u274c Format salah. Gunakan: /reset\\_saldo <nominal>\\nContoh: /reset\\_saldo 3500000';\n  } else {\n    sql = `UPDATE saldo_rekening SET saldo_sekarang = ${newSaldo}, updated_at = NOW(), keterangan = 'Reset manual' WHERE id = (SELECT id FROM saldo_rekening ORDER BY id DESC LIMIT 1)`;\n    responseTemplate = `\u2705 Saldo direset menjadi *Rp ${newSaldo.toLocaleString('id-ID')}*`;\n    replyType = 'update';\n  }\n\n} else {\n  staticReply = '\ud83d\udcb0 *Money Tracker Bot \u2014 Daftar Command*\\n\\n' +\n    '\ud83d\udcca *Info Keuangan*\\n' +\n    '/saldo \u2014 Cek saldo rekening\\n' +\n    '/pengeluaran \u2014 Ringkasan budget bulan ini\\n' +\n    '/export \u2014 Export ke Google Sheets\\n\\n' +\n    '\u2699\ufe0f *Pengaturan*\\n' +\n    '/budget <nominal> \u2014 Set target saving\\n' +\n    'Contoh: /budget 4000000\\n\\n' +\n    '/reset\\_saldo <nominal> \u2014 Reset saldo manual\\n' +\n    'Contoh: /reset\\_saldo 3500000\\n\\n' +\n    '\ud83e\udd16 *AI Chat* (bahasa natural)\\n' +\n    'Contoh: \"Berapa saldo saya?\"\\n' +\n    'Contoh: \"Tampilkan transaksi kemarin\"\\n\\n' +\n    '/help \u2014 Tampilkan pesan ini';\n}\n\nconst needsDb = (replyType !== 'text');\nreturn [{ json: { sql, replyType, staticReply, responseTemplate, chatId, needsDb } }];"
      },
      "id": "node-build-cmd",
      "name": "Build Command",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "needs-db-check",
              "leftValue": "={{ $json.replyType }}",
              "rightValue": "text",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ]
        }
      },
      "id": "node-if-needs-db",
      "name": "Needs DB?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1120,
        220
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ $json.sql }}",
        "options": {}
      },
      "id": "node-exec-cmd-sql",
      "name": "Execute Command SQL",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        1340,
        120
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "is-export-check",
              "leftValue": "={{ $('Build Command').first().json.replyType }}",
              "rightValue": "export",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "id": "node-if-export",
      "name": "Is Export?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1560,
        120
      ]
    },
    {
      "parameters": {
        "operation": "clear",
        "documentId": {
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "value": "Transaksi",
          "mode": "name"
        }
      },
      "id": "node-clear-transaksi",
      "name": "Clear Transaksi Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1780,
        40
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "value": "Transaksi",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "node-write-transaksi",
      "name": "Write Transaksi Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2000,
        40
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT TO_CHAR(bulan, 'Month YYYY') AS bulan, total_pengeluaran, total_pemasukan, target_saving, sisa_budget, pct_saving_risk, CASE WHEN pct_saving_risk >= 100 THEN 'DANGER' WHEN pct_saving_risk >= 80 THEN 'WARNING' ELSE 'AMAN' END AS status FROM monthly_budget ORDER BY bulan DESC LIMIT 12",
        "options": {}
      },
      "id": "node-get-budget-export",
      "name": "Get Budget for Export",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        2220,
        40
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "clear",
        "documentId": {
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "value": "Budget",
          "mode": "name"
        }
      },
      "id": "node-clear-budget",
      "name": "Clear Budget Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2440,
        40
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "value": "Budget",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "node-write-budget",
      "name": "Write Budget Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2660,
        40
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const chatId = $('Build Command').first().json.chatId;\nconst spreadsheetLink = 'https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit';\nconst finalMessage = '\u2705 *Export Selesai!*\\n\\n' +\n  '\ud83d\udccb *Sheet Transaksi* \u2014 Transaksi bulan ini\\n' +\n  '\ud83d\udcca *Sheet Budget* \u2014 Ringkasan 12 bulan\\n\\n' +\n  `\ud83d\udc49 [Buka Spreadsheet](${spreadsheetLink})`;\nreturn [{ json: { finalMessage, chatId } }];"
      },
      "id": "node-export-reply-msg",
      "name": "Export Reply Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2880,
        40
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.finalMessage }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "node-tg-reply-export",
      "name": "Telegram Reply (Export)",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3100,
        40
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nconst chatId = $('Build Command').first().json.chatId;\nconst template = $('Build Command').first().json.responseTemplate;\nconst replyType = $('Build Command').first().json.replyType;\nconst rp = (n) => { const num = parseFloat(String(n).replace(/[^0-9.-]/g, '')); return isNaN(num) ? n : 'Rp ' + num.toLocaleString('id-ID'); };\nconst uangFields = ['nominal', 'saldo_sekarang', 'saldo_akhir', 'total_pengeluaran', 'total_pemasukan', 'target_saving', 'sisa_budget'];\nlet finalMessage = '';\n\nif (replyType === 'update') {\n  finalMessage = template;\n} else if (template === 'SALDO') {\n  const r = rows[0] || {};\n  const updatedAt = r.updated_at ? new Date(r.updated_at).toLocaleString('id-ID', {timeZone:'Asia/Jakarta'}) : '-';\n  finalMessage = `\ud83d\udcb0 *Saldo Rekening*\\n${rp(r.saldo_sekarang)}\\n\\n\ud83d\udd50 Update: ${updatedAt}`;\n} else if (template === 'PENGELUARAN') {\n  if (rows.length === 0) { finalMessage = '\ud83d\udced Belum ada data budget bulan ini.'; }\n  else {\n    const r = rows[0];\n    finalMessage = `\ud83d\udcca *Budget ${r.bulan_label}*\\n\\n\ud83d\udcb8 Pengeluaran: ${rp(r.total_pengeluaran)}\\n\ud83d\udcb5 Pemasukan: ${rp(r.total_pemasukan)}\\n\ud83c\udfaf Target Saving: ${rp(r.target_saving)}\\n\ud83c\udfe6 Sisa Budget: ${rp(r.sisa_budget)}\\n\ud83d\udcc8 Risiko: ${r.pct_saving_risk}%\\n\\nStatus: ${r.status}`;\n  }\n} else {\n  finalMessage = '\u2705 Selesai!';\n}\nreturn [{ json: { finalMessage, chatId } }];"
      },
      "id": "node-format-cmd",
      "name": "Format Command Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1780,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nreturn [{ json: { finalMessage: item.staticReply, chatId: item.chatId } }];"
      },
      "id": "node-static-reply",
      "name": "Static Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        320
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.finalMessage }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "node-tg-reply-cmd",
      "name": "Telegram Reply (Command)",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2000,
        260
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={\"contents\":[{\"parts\":[{\"text\":\"Kamu adalah asisten keuangan Money Tracker AI untuk Bank Jago.\\n\\nStruktur database PostgreSQL:\\n- jago_transactions: id, tanggal_waktu, tipe (debit/kredit/transfer_pocket), nominal, merchant_deskripsi, kategori_otomatis, saldo_akhir\\n- monthly_budget: bulan, target_saving, total_pengeluaran, total_pemasukan, sisa_budget, pct_saving_risk\\n- saldo_rekening: id, saldo_sekarang, updated_at\\n\\nPerintah pengguna: {{ $json.userMessage }}\\n\\nBerikan HANYA respons JSON berikut (tanpa markdown, tanpa komentar):\\n{\\\"action\\\":\\\"query atau update atau info\\\",\\\"sql\\\":\\\"SQL query PostgreSQL jika perlu, atau string kosong\\\",\\\"responseTemplate\\\":\\\"Pesan Bahasa Indonesia dengan emoji. Jika butuh data DB tulis {DATA} sebagai placeholder\\\",\\\"message\\\":\\\"Jika action=info isi pesan di sini, jika tidak biarkan kosong\\\"}\\n\\nAturan:\\n- action=query: untuk membaca data (SELECT)\\n- action=update: untuk mengubah/edit data (UPDATE/INSERT/DELETE)\\\\n- Jika user minta edit/ubah/hapus transaksi, buat query UPDATE/DELETE pada jago_transactions\\\\n- action=info: untuk percakapan biasa tanpa perlu DB\\n- Untuk bulan ini gunakan: DATE_TRUNC('month', NOW())::DATE\\n- responseTemplate diisi selalu kecuali action=info\\n\\nINGAT: HANYA output JSON!\"}]}]}",
        "options": {}
      },
      "id": "node-gemini-parse",
      "name": "Gemini: Parse Intent",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        580
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\nconst cleaned = rawText.replace(/```json/gi, '').replace(/```/g, '').trim();\nlet parsed;\ntry { parsed = JSON.parse(cleaned); } catch(e) {\n  parsed = { action: 'info', sql: '', responseTemplate: '', message: 'Maaf, saya tidak mengerti. Ketik /help untuk daftar command.' };\n}\nconst chatId = $('Extract Message').first().json.chatId;\nconst hasSql = parsed.action === 'query' || parsed.action === 'update';\nreturn [{ json: { ...parsed, chatId, hasSql } }];"
      },
      "id": "node-parse-response",
      "name": "Parse Gemini Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        580
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "has-sql-check",
              "leftValue": "={{ $json.action }}",
              "rightValue": "info",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ]
        }
      },
      "id": "node-if-sql",
      "name": "Has SQL?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1340,
        580
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ $json.sql }}",
        "options": {}
      },
      "id": "node-postgres-exec",
      "name": "Execute SQL",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        1560,
        480
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nconst chatId = $('Parse Gemini Response').first().json.chatId;\nconst template = $('Parse Gemini Response').first().json.responseTemplate || '\u2705 Selesai!';\nconst rp = (n) => { const num = parseFloat(String(n).replace(/[^0-9.-]/g, '')); return isNaN(num) ? n : 'Rp ' + num.toLocaleString('id-ID'); };\nconst uangFields = ['nominal', 'saldo_sekarang', 'saldo_akhir', 'total_pengeluaran', 'total_pemasukan', 'target_saving', 'sisa_budget'];\nlet dataText = '';\nif (rows.length === 0) { dataText = '(tidak ada data)'; }\nelse if (rows.length === 1) {\n  dataText = Object.entries(rows[0]).map(([k, v]) => `${k.replace(/_/g, ' ')}: ${uangFields.includes(k) ? rp(v) : v}`).join('\\n');\n} else {\n  dataText = rows.map((r, i) => `${i+1}. ${Object.entries(r).map(([k,v]) => uangFields.includes(k) ? rp(v) : v).join(' | ')}`).join('\\n');\n}\nconst finalMessage = template.replace('{DATA}', dataText);\nreturn [{ json: { finalMessage, chatId } }];"
      },
      "id": "node-format-result",
      "name": "Format SQL Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1780,
        480
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nreturn [{ json: { finalMessage: item.message, chatId: item.chatId } }];"
      },
      "id": "node-direct-reply",
      "name": "Direct Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        680
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.finalMessage }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "node-tg-reply-ai",
      "name": "Telegram Reply (AI)",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2000,
        560
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Telegram Message Trigger": {
      "main": [
        [
          {
            "node": "Extract Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Message": {
      "main": [
        [
          {
            "node": "Is Command?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Command?": {
      "main": [
        [
          {
            "node": "Build Command",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gemini: Parse Intent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Command": {
      "main": [
        [
          {
            "node": "Needs DB?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs DB?": {
      "main": [
        [
          {
            "node": "Execute Command SQL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Static Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Command SQL": {
      "main": [
        [
          {
            "node": "Is Export?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Export?": {
      "main": [
        [
          {
            "node": "Clear Transaksi Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Command Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear Transaksi Sheet": {
      "main": [
        [
          {
            "node": "Write Transaksi Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Transaksi Sheet": {
      "main": [
        [
          {
            "node": "Get Budget for Export",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Budget for Export": {
      "main": [
        [
          {
            "node": "Clear Budget Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear Budget Sheet": {
      "main": [
        [
          {
            "node": "Write Budget Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Budget Sheet": {
      "main": [
        [
          {
            "node": "Export Reply Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export Reply Message": {
      "main": [
        [
          {
            "node": "Telegram Reply (Export)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Command Reply": {
      "main": [
        [
          {
            "node": "Telegram Reply (Command)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Static Reply": {
      "main": [
        [
          {
            "node": "Telegram Reply (Command)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini: Parse Intent": {
      "main": [
        [
          {
            "node": "Parse Gemini Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Gemini Response": {
      "main": [
        [
          {
            "node": "Has SQL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has SQL?": {
      "main": [
        [
          {
            "node": "Execute SQL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Direct Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute SQL": {
      "main": [
        [
          {
            "node": "Format SQL Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format SQL Result": {
      "main": [
        [
          {
            "node": "Telegram Reply (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Direct Reply": {
      "main": [
        [
          {
            "node": "Telegram Reply (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": [
    {
      "name": "expense-tracker",
      "createdAt": "2026-02-26"
    }
  ]
}