{
  "id": "7DkUhZxnBGbzTgpK",
  "name": "Unified_AstroBot_Engine_With_Superbase_And_Telegram_Bot",
  "tags": [],
  "nodes": [
    {
      "id": "168eae06-6d9f-4d76-a692-e3db09fff24f",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -1648,
        176
      ],
      "parameters": {
        "updates": [
          "message",
          "callback_query"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "8ecc23bc-1a4c-4050-83c6-cc1a6c9115ab",
      "name": "Supabase: Get Session",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -1424,
        176
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "chat_id",
              "keyValue": "={{ $json.message?.chat?.id?.toString() || $json.callback_query?.message?.chat?.id?.toString() }}"
            }
          ]
        },
        "tableId": "sessions",
        "operation": "get"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "7abd99c5-c778-454f-88d1-7d440e4a9bdd",
      "name": "Logic Engine",
      "type": "n8n-nodes-base.code",
      "position": [
        -1200,
        176
      ],
      "parameters": {
        "jsCode": "const triggerData = $('Telegram Trigger').first().json;\nlet dbData = $('Supabase: Get Session').first().json || {};\n\nif (Array.isArray(dbData)) dbData = dbData[0] || {};\n\nlet chatId = triggerData.message?.chat?.id?.toString() || triggerData.callback_query?.message?.chat?.id?.toString();\nlet msg = triggerData.message?.text || triggerData.callback_query?.data || \"\";\n\nif (!chatId) return { json: { stop: true } };\n\nconst recordExists = !!(dbData && dbData.chat_id);\n\nlet currentState = dbData.state || \"START\";\nlet mode = dbData.mode || \"\";\nlet p1 = dbData.p1_data || \"\";\nlet p2 = dbData.p2_data || \"\";\n\nlet reply = \"I'm processing that... one moment.\";\nlet nextState = currentState;\nlet routeChat = false;\nlet isComplete = false;\n\nif (msg.toLowerCase().includes(\"/start\")) {\n  currentState = \"START\";\n}\n\nswitch (currentState) {\n  case \"START\":\n    reply = \"Welcome! Choose: \ud83d\udd2e <b>Chart</b> or \ud83d\udc9e <b>Synastry</b>?\";\n    nextState = \"AWAITING_CHOICE\";\n    break;\n\n  case \"AWAITING_CHOICE\":\n    if (msg.toLowerCase().includes(\"natal\") || msg.toLowerCase().includes(\"chart\")) {\n      mode = \"natal\";\n      reply = \"\ud83d\udd2e <b>Personal Chart Mode</b>\\nSend your birth data (City, DD/MM/YYYY HH:mm):\";\n      nextState = \"AWAITING_NATAL_DATA\";\n    } else if (msg.toLowerCase().includes(\"synastry\")) {\n      mode = \"synastry\";\n      reply = \"\ud83d\udc9e <b>Synastry Mode</b>\\nSend Person 1 data (City, DD/MM/YYYY HH:mm):\";\n      nextState = \"AWAITING_P1\";\n    } else {\n      reply = \"Please choose: \ud83d\udd2e <b>Chart</b> or \ud83d\udc9e <b>Synastry</b>?\";\n      nextState = \"AWAITING_CHOICE\";\n    }\n    break;\n\n  case \"AWAITING_NATAL_DATA\":\n    p1 = msg;\n    reply = \"\u2728 Calculating your personal natal alignment... \u2728\";\n    nextState = \"CHAT\";\n    isComplete = true;\n    break;\n\n  case \"AWAITING_P1\":\n    p1 = msg;\n    reply = \"\u2705 Got Person 1. Now send <b>Person 2 data</b> (City, DD/MM/YYYY HH:mm):\";\n    nextState = \"AWAITING_P2\";\n    break;\n\n  case \"AWAITING_P2\":\n    p2 = msg;\n    reply = \"\ud83c\udf0c Comparing soul dynamics... \u2728\";\n    nextState = \"CHAT\";\n    isComplete = true;\n    break;\n\n  case \"CHAT\":\n    routeChat = true;\n    nextState = \"CHAT\";\n    break;\n    \n  default:\n    reply = \"Welcome! Choose: \ud83d\udd2e <b>Chart</b> or \ud83d\udc9e <b>Synastry</b>?\";\n    nextState = \"AWAITING_CHOICE\";\n    break;\n}\n\nreturn {\n  json: {\n    chatId,\n    reply,\n    nextState,\n    recordExists,\n    mode,\n    p1_data: p1,\n    p2_data: p2,\n    routeChat,\n    isComplete,\n    userQuery: msg\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e534839f-a1fc-4477-8e8c-c68e59851fed",
      "name": "Record Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        -976,
        176
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check_exists",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.recordExists }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "bfaae5ea-69ad-4c6e-acc3-d28613a55aae",
      "name": "Supabase: Update Session",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -768,
        64
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "chat_id",
              "keyValue": "={{ $json.chatId }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "sessions",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "state",
              "fieldValue": "={{ $json.nextState }}"
            },
            {
              "fieldId": "mode",
              "fieldValue": "={{ $json.mode }}"
            },
            {
              "fieldId": "p1_data",
              "fieldValue": "={{ $json.p1_data }}"
            },
            {
              "fieldId": "p2_data",
              "fieldValue": "={{ $json.p2_data }}"
            }
          ]
        },
        "operation": "update"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1588cc7e-7c01-467f-a3b2-8bb26e19d79e",
      "name": "Supabase: Insert Session",
      "type": "n8n-nodes-base.supabase",
      "position": [
        -768,
        288
      ],
      "parameters": {
        "tableId": "sessions",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "chat_id",
              "fieldValue": "={{ $json.chatId }}"
            },
            {
              "fieldId": "state",
              "fieldValue": "={{ $json.nextState }}"
            },
            {
              "fieldId": "mode",
              "fieldValue": "={{ $json.mode }}"
            },
            {
              "fieldId": "p1_data",
              "fieldValue": "={{ $json.p1_data }}"
            },
            {
              "fieldId": "p2_data",
              "fieldValue": "={{ $json.p2_data }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d6c8a9b9-2905-45b2-b5e6-0871f81f85d7",
      "name": "Is Pure Chat?",
      "type": "n8n-nodes-base.if",
      "position": [
        -496,
        176
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check_rag",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Logic Engine').item.json.routeChat }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1f889e97-35b9-4d45-b073-9a0312d5f128",
      "name": "Calculate Reading Now?",
      "type": "n8n-nodes-base.if",
      "position": [
        -288,
        272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is_complete",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Logic Engine').item.json.isComplete }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "10b47fcb-5631-4973-808e-e81582d411ae",
      "name": "Parse Intake Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -80,
        384
      ],
      "parameters": {
        "jsCode": "const engineData = $('Logic Engine').item.json;\nconst p1Raw = engineData.p1_data || \"\";\nconst p2Raw = engineData.p2_data || \"\";\n\nfunction parseInput(str, personName) {\n  const parts = str.split(',');\n  const city = parts[0] ? parts[0].trim() : \"Tbilisi\";\n  const timedata = parts[1] ? parts[1].trim().split(' ') : [];\n  const datePart = timedata[0] || \"19/05/2026\";\n  const timePart = timedata[1] || \"12:00\";\n  \n  const [day, month, year] = datePart.split('/').map(Number);\n  const [hour, min] = timePart.split(':').map(Number);\n  \n  return { person: personName, city, day: day||19, month: month||5, year: year||2026, hour: hour||12, min: min||0 };\n}\n\nconst items = [];\nif (p1Raw) items.push({ json: parseInput(p1Raw, \"p1\") });\nif (p2Raw) items.push({ json: parseInput(p2Raw, \"p2\") });\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "853632e0-d668-49ce-978c-ac4a2804c69b",
      "name": "Get Coordinates",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        112,
        384
      ],
      "parameters": {
        "url": "https://nominatim.openstreetmap.org/search",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "format",
              "value": "json"
            },
            {
              "name": "q",
              "value": "={{ $json.city }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "99cdc5f9-d14b-422d-aa3c-8fec19d25c57",
      "name": "Get Timezone Offset",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        320,
        384
      ],
      "parameters": {
        "url": "https://secure.geonames.org/timezoneJSON",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "lat",
              "value": "={{ $json.lat }}"
            },
            {
              "name": "lng",
              "value": "={{ $json.lon }}"
            },
            {
              "name": "username",
              "value": "sally.tkhilaishvili"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "bacb26b5-00e0-4eae-b3a0-661fc787bdb9",
      "name": "Astrology API Engine",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        736,
        384
      ],
      "parameters": {
        "url": "={{ $json.apiUrl }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ $json.payload }}",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "queryParameters": {
          "parameters": [
            {}
          ]
        }
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        },
        "httpBearerAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "035e7f17-13c1-42db-a10f-cc4a90bca7df",
      "name": "Download RAG JSON",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        32,
        144
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "list",
          "value": "15q08k0TPfRboMZmTuUaklpv0yWfP7jl6",
          "cachedResultUrl": "https://drive.google.com/file/d/15q08k0TPfRboMZmTuUaklpv0yWfP7jl6/view?usp=drivesdk",
          "cachedResultName": "synastry.json"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "330d3098-9698-4a68-b380-8713e88a71a9",
      "name": "Final Similarity Filter",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        144
      ],
      "parameters": {
        "jsCode": "const rawSnippets = $json.snippets || [];\nlet processedRagContext = \"No additional manuscript contexts retrieved.\";\n\nif (Array.isArray(rawSnippets) && rawSnippets.length > 0) {\n  processedRagContext = rawSnippets.join('\\n');\n} else if (typeof rawSnippets === 'string') {\n  processedRagContext = rawSnippets;\n}\n\nreturn {\n  json: {\n    processedRagContext\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "db97358b-3c55-47b7-ac6a-c50d26a9272f",
      "name": "Supabase: Log Reading History",
      "type": "n8n-nodes-base.supabase",
      "position": [
        2000,
        384
      ],
      "parameters": {
        "tableId": "astrology_readings",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "chat_id",
              "fieldValue": "={{ $('Logic Engine').item.json.chatId }}"
            },
            {
              "fieldId": "mode",
              "fieldValue": "={{ $('Logic Engine').item.json.mode }}"
            },
            {
              "fieldId": "p1_data",
              "fieldValue": "={{ $('Logic Engine').item.json.p1_data }}"
            },
            {
              "fieldId": "p2_data",
              "fieldValue": "={{ $('Logic Engine').item.json.p2_data }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7c33f607-ca1f-4987-a028-c0141a555cdc",
      "name": "Telegram Bot Response",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2192,
        384
      ],
      "parameters": {
        "text": "={{ $('Code in JavaScript').item.json.text }}",
        "chatId": "={{ $('Logic Engine').item.json.chatId }}",
        "additionalFields": {
          "parse_mode": "HTML"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "fa1cd9b8-f508-42dc-840f-8c91f6b08010",
      "name": "Send a text message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        0,
        0
      ],
      "parameters": {
        "text": "={{ $('Logic Engine').item.json.reply }}",
        "chatId": "={{ $('Logic Engine').item.json.chatId }}",
        "additionalFields": {
          "parse_mode": "HTML"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "75c1db7a-0346-4a73-a9ff-c8f20d2554d9",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        1808,
        384
      ],
      "parameters": {
        "jsCode": "try {\n    // Looks for 'output' or fallback 'text' from the incoming node data\n    let rawOutput = $input.first().json.output || $input.first().json.text || \"\";\n    return [{ json: { text: rawOutput } }];\n} catch (e) {\n    return [{ json: { text: \"\u26a0\ufe0f <b>System Error:</b> Breakdown framing contextual response data.\" } }];\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "955d3c22-3d63-462c-848d-a1e07cac45f7",
      "name": "Prepare input for natal/synastry calculation",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        384
      ],
      "parameters": {
        "jsCode": "// 1. Fetch data safely\nconst logicEngine = $('Logic Engine').first()?.json || {};\n\n// If we are just chatting, skip deep lookups to prevent execution crashes\nif (logicEngine.routeChat && !logicEngine.isComplete) {\n  return [{\n    json: {\n      apiUrl: \"https://json.astrologyapi.com/v1/western_horoscope\",\n      payload: {},\n      isChatRoute: true\n    }\n  }];\n}\n\nlet parsedIntake = [];\nlet geoCoordinates = [];\nlet tzOffsets = [];\n\n// Use try/catch blocks to prevent crashes if nodes weren't executed\ntry { parsedIntake = $('Parse Intake Data').all().map(item => item.json || {}); } catch(e) {}\ntry { geoCoordinates = $('Get Coordinates').all().map(item => item.json || {}); } catch(e) {}\ntry { tzOffsets = $('Get Timezone Offset').all().map(item => item.json || {}); } catch(e) {}\n\nconst isSynastry = !!logicEngine.p2_data;\nconst targetUrl = isSynastry \n  ? 'https://json.astrologyapi.com/v1/synastry_horoscope' \n  : 'https://json.astrologyapi.com/v1/western_horoscope';\n\n// 2. Robust Parser\nfunction getPersonData(index) {\n  const intake = parsedIntake[index] || {};\n  const geo = geoCoordinates[index] || {};\n  const tz = tzOffsets[index] || {};\n\n  return {\n    day: parseInt(intake.day, 10) || 19,\n    month: parseInt(intake.month, 10) || 5,\n    year: parseInt(intake.year, 10) || 2026,\n    hour: parseInt(intake.hour, 10) || 12,\n    min: parseInt(intake.min, 10) || 0,\n    latitude: parseFloat(geo.lat || geo[0]?.lat || 41.7151),\n    longitude: parseFloat(geo.lon || geo.lng || geo[0]?.lon || 44.8271),\n    timezone: parseFloat(tz.gmtOffset || 4.0)\n  };\n}\n\n// 3. Construct Payload (Adjusted for flat structure)\nlet finalPayload = {};\n\nif (isSynastry) {\n  const p1 = getPersonData(0);\n  const p2 = getPersonData(1);\n  \n  finalPayload = {\n    p_day: p1.day, p_month: p1.month, p_year: p1.year,\n    p_hour: p1.hour, p_min: p1.min, p_lat: p1.latitude, \n    p_lon: p1.longitude, p_tzone: p1.timezone,\n    \n    s_day: p2.day, s_month: p2.month, s_year: p2.year,\n    s_hour: p2.hour, s_min: p2.min, s_lat: p2.latitude, \n    s_lon: p2.longitude, s_tzone: p2.timezone\n  };\n} else {\n  const p1 = getPersonData(0);\n  finalPayload = {\n    day: p1.day, month: p1.month, year: p1.year,\n    hour: p1.hour, min: p1.min, lat: p1.latitude, \n    lon: p1.longitude, tzone: p1.timezone\n  };\n}\n\n// 4. Return\nreturn [{\n  json: {\n    apiUrl: targetUrl,\n    payload: finalPayload,\n    isChatRoute: false,\n    chatId: $('Record Exists?').first().json.chatId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "49d864a7-41c2-4145-8adf-c79d8957800e",
      "name": "Convert service output to context",
      "type": "n8n-nodes-base.code",
      "position": [
        960,
        384
      ],
      "parameters": {
        "jsCode": "const logicEngineData = $('Logic Engine').first()?.json || {};\nconst userPrompt = logicEngineData.userQuery || \"Analyze chart\"; \n\nlet diagnosticStr = \"NO_LIVE_CHART_CALCULATED\";\nlet processedRagContext = \"No additional manuscript contexts retrieved.\";\n\n// 1. Check for LIVE calculations safely using try/catch\ntry {\n  const apiData = $('Astrology API Engine').first()?.json;\n  \n  // Only parse if the node actually returned real planetary positions\n  if (apiData && (apiData.planets || apiData.first)) {\n    diagnosticStr = parseAstrologyPayload(apiData);\n  }\n} catch (e) {\n  // Node wasn't executed or was bypassed, diagnosticStr remains 'NO_LIVE_CHART_CALCULATED'\n}\n\n// 2. Try to fetch RAG Context cleanly\ntry {\n  const ragNode = $('Final Similarity Filter').first()?.json;\n  if (ragNode && ragNode.processedRagContext) {\n    processedRagContext = ragNode.processedRagContext;\n  }\n} catch (e) {}\n\n// Helper function to stringify planetary data cleanly\nfunction parseAstrologyPayload(apiData) {\n  const formatPlanets = (planetList, label) => {\n    return planetList.map(p => `${label}: ${p.name} in ${p.sign} (House ${p.house})`).join(', ');\n  };\n  if (apiData.first && apiData.second) {\n    let str = `${formatPlanets(apiData.first, \"Person 1\")} | ${formatPlanets(apiData.second, \"Person 2\")}`;\n    if (apiData.aspects) {\n      const aspects = apiData.aspects.map(a => `${a.aspecting_planet} ${a.type} ${a.aspected_planet} (Orb: ${a.orb}\u00b0)`).join(', ');\n      str += ` | Aspects: ${aspects}`;\n    }\n    return str;\n  } else if (apiData.planets) {\n    return apiData.planets.map(p => `${p.name} in ${p.sign} (House ${p.house})`).join(', ');\n  }\n  return \"NO_LIVE_CHART_CALCULATED\";\n}\n\nreturn {\n  json: {\n    chatInput: userPrompt,\n    driveContext: processedRagContext,\n    astrologyData: diagnosticStr,\n    isCalculationRoute: (diagnosticStr !== \"NO_LIVE_CHART_CALCULATED\"),\n    sessionId :$('Record Exists?').first().json.chatId \n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "2ceeeefb-a02e-4c90-979e-46c54e9f4364",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1408,
        384
      ],
      "parameters": {
        "options": {
          "systemMessage": "=You are a precision-focused Astrological Agent interacting with a user over a continuous chat interface.\n\nCURRENT LIVE CHART DATA SOURCE:\n{{ $json.sanitizedAstrologyData }}\n\nCRITICAL EXECUTION PROTOCOL:\n- Check the 'CURRENT LIVE CHART DATA SOURCE' block above. If metrics are present, you must synthesize BOTH the planetary placements and the critical aspects to build your analysis.\n- If it says \"NO_LIVE_CALCULATION_THIS_TURN_USE_HISTORY_MEMORY\", scan your internal chat history memory window. Look at the profile parameters built earlier in this conversation thread to answer their new questions.\n- You are STRICTLY FORBIDDEN from giving generic textbook listings or telling users what to \"look for.\" Synthesize their specific data placements directly.\n\nCRITICAL TELEGRAM DELIVERY & LENGTH CONSTRAINT:\n- The delivery platform has a strict technical ceiling. Your entire generated response MUST be brief, concise, and under 3,200 characters total (including all spaces, markdown text, and HTML tags). \n- Avoid wordy preambles, introductory fluff, or long conclusions. Get straight to the analysis of the chart positions so the response easily fits in a single message payload.\n\nCRITICAL FORMATTING SANITIZATION LAWS:\n1. NEVER output raw \"\\n\" text strings. Use true keyboard carriage returns to separate paragraphs.\n2. DO NOT use HTML <br> tags under any circumstances.\n3. Use HTML bold tags for core thematic anchors (e.g., <b>\ud83e\ude90 The Pluto-Midheaven Conjunction</b>).\n4. Use code style formatting tags to isolate numerical values, angles, or houses (e.g., <code>Pluto Conjunction Midheaven (Orb: 0.73\u00b0)</code>).\n5. Prefix your main content section titles with a single cosmic emoji (\u2728, \ud83e\ude90, \ud83c\udf19)."
        }
      },
      "typeVersion": 3.1
    },
    {
      "id": "5c7c1068-e461-4d2e-b4a2-8fe8060d9692",
      "name": "Google Gemini Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1408,
        608
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-3.1-flash-lite"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "fe92d63b-1e30-41ba-b2d6-0963e85dfa6b",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        1504,
        608
      ],
      "parameters": {},
      "typeVersion": 1.4
    },
    {
      "id": "a10e6051-2254-488c-886a-25d2a9f955c4",
      "name": "Prepare data for agent",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        384
      ],
      "parameters": {
        "jsCode": "let isSynastryRoute = false;\nlet natalPlacements = [];\nlet p1Placements = [];\nlet p2Placements = [];\nlet consolidatedAspects = [];\nlet formattedText = \"NO_LIVE_CALCULATION_THIS_TURN_USE_HISTORY_MEMORY\";\nlet activeSessionId = \"\";\nlet activeChatInput = \"\";\n\n// Native, safe n8n global helper input retrieval\nconst items = $input.all();\n\nif (items && items.length > 0) {\n  items.forEach(item => {\n    const data = item.json;\n    if (!data) return;\n\n    // Safely trap passthrough parameters\n    if (data.sessionId) activeSessionId = data.sessionId;\n    if (data.chatInput) activeChatInput = data.chatInput;\n    if (!activeSessionId && activeChatInput) activeSessionId = activeChatInput;\n\n    // --- 1. PARSE OUT RAW PAYLOAD PAYLOAD STRINGS ---\n    if (data.astrologyData && typeof data.astrologyData === 'string') {\n      if (data.astrologyData.includes('Person 2:') || data.astrologyData.includes('P2 ')) {\n        isSynastryRoute = true;\n      }\n      \n      let parts = data.astrologyData.split('|');\n      parts.forEach(part => {\n        let elements = part.split(',').map(e => e.trim());\n        elements.forEach(el => {\n          if (el.startsWith('Person 1:') || el.startsWith('P1 ')) {\n            p1Placements.push(`\u2022 ${el.replace(/Person 1:\\s*|P1\\s*/i, '')}`);\n          } else if (el.startsWith('Person 2:') || el.startsWith('P2 ')) {\n            p2Placements.push(`\u2022 ${el.replace(/Person 2:\\s*|P2\\s*/i, '')}`);\n          } else if (el.length > 0) {\n            natalPlacements.push(`\u2022 ${el}`);\n          }\n        });\n      });\n    }\n    \n    // --- 2. BACKUP: SCENARIO ARRAY PROCESSING ---\n    else if ((data.first && data.first.length > 0) || (data.second && data.second.length > 0)) {\n      isSynastryRoute = true;\n      if (Array.isArray(data.first)) {\n        data.first.forEach(p => p1Placements.push(`\u2022 ${p.name}: ${p.sign} (${p.house}H)`));\n      }\n      if (Array.isArray(data.second)) {\n        data.second.forEach(p => p2Placements.push(`\u2022 ${p.name}: ${p.sign} (${p.house}H)`));\n      }\n    }\n    else if (data.planets && Array.isArray(data.planets)) {\n      data.planets.forEach(p => natalPlacements.push(`\u2022 ${p.name}: ${p.sign} (${p.house}H)`));\n    }\n\n    // --- 3. CAPTURE AUTOMATED ASPECTS (IF AVAILABLE) ---\n    let targets = data.aspects || data;\n    if (Array.isArray(targets)) {\n      targets.forEach(a => {\n        if (a.aspecting_planet && a.aspected_planet) {\n          let orbVal = parseFloat(a.orb) || 0;\n          consolidatedAspects.push({\n            string: `<code>${a.aspecting_planet} ${a.type} ${a.aspected_planet} (Orb: ${orbVal.toFixed(2)}\u00b0)</code>`,\n            orb: orbVal\n          });\n        }\n      });\n    }\n  });\n\n  // Sort aspects by exact mathematical closeness\n  consolidatedAspects.sort((a, b) => a.orb - b.orb);\n  let aspectStrings = consolidatedAspects.map(a => a.string);\n\n  // --- 4. ASSEMBLE STRUCTURAL WORKFLOW OUTPUT ---\n  let output = `[LIVE DATA PACKET RATIFIED]\\n`;\n  output += `ROUTE_DETECTED: ${isSynastryRoute ? 'SYNASTRY_COMPATIBILITY' : 'NATAL_BLUEPRINT'}\\n\\n`;\n\n  if (isSynastryRoute) {\n    output += `### PROFILE 1 CORE PLACEMENTS (HOME BASELINE)\\n` + (p1Placements.join('\\n') || 'No P1 Placements Found') + `\\n\\n`;\n    output += `### PROFILE 2 CORE PLACEMENTS (INCOMING ENERGY)\\n` + (p2Placements.join('\\n') || 'No P2 Placements Found') + `\\n\\n`;\n    output += `### CRITICAL CROSS-CHART GEOMETRIC INTERSECTIONS (SORTED BY URGENCY)\\n`;\n    output += aspectStrings.length > 0 ? aspectStrings.join('\\n') : 'No tight inter-chart alignments under 4.0\u00b0. Focus heavily on the House Overlays above.';\n  } else {\n    output += `### INDIVIDUAL NATAL PLACEMENTS\\n` + (natalPlacements.join('\\n') || 'No Natal Placements Found') + `\\n\\n`;\n    output += `### INTERNAL ASPECT GEOMETRY (SORTED BY FORCE INTENSITY)\\n`;\n    output += aspectStrings.length > 0 ? aspectStrings.join('\\n') : 'No major planetary aspects active.';\n  }\n\n  formattedText = output;\n}\n\nreturn [\n  {\n    json: {\n      sessionId: activeSessionId,\n      chatInput: activeChatInput,\n      sanitizedAstrologyData: formattedText\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "5f951300-72fe-463d-83f6-ae7648a7dd5d",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2160,
        -128
      ],
      "parameters": {
        "width": 432,
        "height": 688,
        "content": "\ud83c\udf0c Unified AstroBot Engine v3\nHow it works\nTrigger: The workflow captures incoming Telegram messages or button callback queries.\n\nSession Retrieval: It fetches the conversation state from Supabase using the user's unique chat_id.\n\nLogic Engine: A centralized JavaScript routing engine processes user inputs, handles state changes (START, AWAITING_CHOICE, CHAT), and updates the session data.\n\nData Enrichment & RAG: If the data collection for a reading is complete, the engine coordinates background calls to parse location strings, grab coordinates via Nominatim, determine timezone details, and fetch context files from Google Drive.\n\nResponse Delivery: The workflow dynamically routes the execution path to either send operational messages or full AI/astrological readings back to the user via Telegram.\n\nSetup steps\n[ ] Add your Telegram Bot API credentials to the trigger and reply nodes.\n\n[ ] Connect your Supabase credentials and verify that your sessions table matches the core fields (chat_id, state, mode, p1_data, p2_data).\n\n[ ] Authenticate your Google Drive node to allow the engine to access your reference data (synastry.json).\n\n[ ] Verify your Astro API / GeoNames credentials for geo-data enrichment.\n\nCustomization\nYou can expand the switch-case conditions inside the Logic Engine node to handle advanced interactive states or modify the Parse Intake Data structure to accept localized date-time formatting patterns.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "7fd94a2f-9442-44e6-9e55-a4ac18028741",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1696,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 1344,
        "height": 448,
        "content": "## Work on chat's input output format and send user's feedback to superbase database\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b6943ad9-4b3a-4590-acdf-30d0b8ad0d5c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 432,
        "content": "## Reitrive file from storage and analize how similar it is to user's request\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3e2853b3-f147-4ef5-81d5-2e72a9e954e3",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 1424,
        "height": 272,
        "content": "## After analyzing user request, get coordinates, timezones and use user input to calulate natal chart by external api, and format output to send parsed data to AI Agent\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "afff7fa0-2e95-4d0a-9a43-564ef3ec8998",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1360,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 1008,
        "height": 560,
        "content": "## Get AI output based on user prompt, RAG restul and user's request and send them to superbase database and to telegram message bot."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "e3327ffd-a010-4a23-8fea-dfd09afbe68a",
  "connections": {
    "AI Agent": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Logic Engine": {
      "main": [
        [
          {
            "node": "Record Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Pure Chat?": {
      "main": [
        [
          {
            "node": "Download RAG JSON",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calculate Reading Now?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Record Exists?": {
      "main": [
        [
          {
            "node": "Supabase: Update Session",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Supabase: Insert Session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Coordinates": {
      "main": [
        [
          {
            "node": "Get Timezone Offset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Supabase: Get Session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download RAG JSON": {
      "main": [
        [
          {
            "node": "Final Similarity Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Intake Data": {
      "main": [
        [
          {
            "node": "Get Coordinates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Supabase: Log Reading History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Timezone Offset": {
      "main": [
        [
          {
            "node": "Prepare input for natal/synastry calculation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Astrology API Engine": {
      "main": [
        [
          {
            "node": "Convert service output to context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase: Get Session": {
      "main": [
        [
          {
            "node": "Logic Engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Reading Now?": {
      "main": [
        [
          {
            "node": "Parse Intake Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare data for agent": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Final Similarity Filter": {
      "main": [
        [
          {
            "node": "Convert service output to context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase: Insert Session": {
      "main": [
        [
          {
            "node": "Is Pure Chat?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase: Update Session": {
      "main": [
        [
          {
            "node": "Is Pure Chat?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Supabase: Log Reading History": {
      "main": [
        [
          {
            "node": "Telegram Bot Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert service output to context": {
      "main": [
        [
          {
            "node": "Prepare data for agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare input for natal/synastry calculation": {
      "main": [
        [
          {
            "node": "Astrology API Engine",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}