{
  "name": "recherche-stage",
  "nodes": [
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Fusionner les donn\u00e9es Tavily avec les scores MCP\nconst searchData = $('Search').item.json;\nconst mcpData = $input.item.json;\n\nreturn {\n  query: searchData.query,\n  results: searchData.results,\n  city_score: mcpData\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        512,
        0
      ],
      "id": "dad4f474-f4f9-498f-a24d-de9491eaf2f1",
      "name": "Fusion"
    },
    {
      "parameters": {
        "query": "=internship {{ $json.technology }} in {{ $json.city }}",
        "options": {
          "max_results": 5
        }
      },
      "type": "@tavily/n8n-nodes-tavily.tavily",
      "typeVersion": 1,
      "position": [
        128,
        0
      ],
      "id": "aec1211e-adf5-4c48-8e9d-b0411b24c32c",
      "name": "Search",
      "credentials": {
        "tavilyApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=http://mcp-scorer:8080/find_city_score?city={{ $json.query.split(' in ')[1] }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        320,
        0
      ],
      "id": "1566058a-472d-4492-8c8f-6f227e98b363",
      "name": "MCP"
    },
    {
      "parameters": {
        "jsCode": "// Make an array of all combinaisons with the technologies and \n\nconst cities = [\n  {\"city\": \"Berlin\"},\n  {\"city\": \"Barcelona\"},\n  {\"city\": \"Amsterdam\"}\n];\n\nconst technology = [\n  {\"technology\": \"devops\"},\n  {\"technology\": \"software engineer\"}\n];\n\nconst combinations = cities.flatMap(cityObj => {\n  return technology.map(techObj => ({\n    city: cityObj.city,\n    technology: techObj.technology\n  }));\n});\n\nreturn combinations;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -80,
        0
      ],
      "id": "e85a4e15-8c2f-43ea-a7fc-1989a9dee7f7",
      "name": "Return combinations of cities and technology matrix"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -304,
        0
      ],
      "id": "40a9ace1-a9b1-4d7b-918d-61acdd120271",
      "name": "When clicking \u2018Execute workflow\u2019"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatMistralCloud",
      "typeVersion": 1,
      "position": [
        736,
        224
      ],
      "id": "58ec7cb4-daae-438b-9d89-5201a8eea98f",
      "name": "Mistral Cloud Chat Model",
      "credentials": {
        "mistralCloudApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "binaryPropertyName": "rapport",
        "options": {
          "fileName": "=stages_{{ $now.format('yyyy-MM-dd') }}.csv"
        }
      },
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        1744,
        416
      ],
      "id": "d8020a44-d70d-4213-8142-7c8adc79d89b",
      "name": "Convert to File"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatMistralCloud",
      "typeVersion": 1,
      "position": [
        1760,
        208
      ],
      "id": "b0de2c42-befa-44db-a564-d57be78f80fb",
      "name": "Mistral Cloud Chat Model1",
      "credentials": {
        "mistralCloudApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "authentication": "webhook",
        "content": "={{ $json.output }}",
        "options": {},
        "files": {
          "values": [
            {
              "inputFieldName": "rapport"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.discord",
      "typeVersion": 2,
      "position": [
        2384,
        16
      ],
      "id": "3a0c7f9b-e0ed-45f7-b1a5-7569444ea05e",
      "name": "Discord",
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1536,
        0
      ],
      "id": "8a0d90ed-2e56-49d6-968b-f9151bf52a96",
      "name": "Aggregate"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Tu es un assistant expert en recrutement.\n\nTA T\u00c2CHE :\nAnalyse les offres ci-dessous pour la ville concern\u00e9e.\nSCORE DE LA VILLE : {{ $json.city_score }} / 10\n\nR\u00c8GLES STRICTES :\n1. Retourne UNIQUEMENT un JSON valide.\n2. Pour CHAQUE offre, ajoutes-y le champ \"city_score\" avec la valeur {{ $json.city_score }} (ne l'invente pas, recopie-le).\n3. Format de sortie :\n[{\n  \"company\": \"Nom\",\n  \"title\": \"Titre\",\n  \"location\": \"Ville\",\n  \"city_score\": {{ $json.city_score }},\n  \"src_url\": \"URL\",\n  \"deadline\": \"Date ou Non sp\u00e9cifi\u00e9\",\n  \"skills\": [\"Comp\u00e9tence 1\"],\n  \"summary\": \"R\u00e9sum\u00e9 en 1 phrase\"\n}]\n\n4. IMPORTANT : Pas de markdown (```json), juste le JSON brut.\n\nDONN\u00c9ES \u00c0 ANALYSER :\n{{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        736,
        0
      ],
      "id": "7795a8eb-8977-4cad-a425-7a7cd9cfe482",
      "name": "Analyse"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Tu es un analyste Data pour le recrutement.\nVoici un tableau JSON complet des offres de stage trouv\u00e9es (avec un \"personal_score\" calcul\u00e9) :\n{{ JSON.stringify($json) }}\n\nG\u00e9n\u00e8re un rapport de synth\u00e8se structur\u00e9 et professionnel en suivant strictement ce plan :\n\n### 1. \ud83d\udcca Vue d'ensemble\n*   **Nombre total d'offres pertinentes :** [Nombre]\n*   **Villes repr\u00e9sent\u00e9es :** [Ex: Berlin (5), Amsterdam (3)]\n*   **Fourchettes identifi\u00e9es :** [Ex: Salaire moyen observ\u00e9 / Dur\u00e9es fr\u00e9quentes]\n\n### 2. \ud83c\udfc6 Top 3 Recommandations (Bas\u00e9 sur le score personnel)\n*S\u00e9lectionne les 3 offres avec le meilleur 'personal_score' ou les plus prestigieuses.*\n1.  **[Titre] chez [Entreprise]** ([Ville]) - Score: [Score]\n    *   *Pourquoi ce choix :* [Justification courte]\n2.  ...\n3.  ...\n\n### 3. \ud83d\udca1 Analyse & Conseils\n*   **Tendance du march\u00e9 :** [Ex: Beaucoup de demande en DevOps...]\n*   **Conseil strat\u00e9gique :** [Un conseil pour postuler]\n\nGarde un ton professionnel mais encourageant. Utilise des \u00e9mojis pour la lisibilit\u00e9.\n\nIMPORTANT : La r\u00e9ponse DOIT faire MOINS DE 1800 CARACT\u00c8RES (pour Discord).\nSois concis, utilise des listes \u00e0 puces (bullets) et va droit au but. Pas de blabla inutile.",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        1760,
        0
      ],
      "id": "3086aa6c-e74b-4d6a-a8db-ebfd31915a2a",
      "name": "Synth\u00e8se"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "llama3.2:1b",
          "mode": "list",
          "cachedResultName": "llama3.2:1b"
        },
        "messages": {
          "values": [
            {
              "content": "=Tu es un assistant expert en recrutement et analyse d'offres d'emploi.\n\nTA T\u00c2CHE :\nAnalyse le contenu brut des r\u00e9sultats de recherche fournis ci-dessous (format JSON) et extrais les informations cl\u00e9s pour chaque offre de stage pertinente.\n\nR\u00c8GLES STRICTES :\n1. Tu dois retourner UNIQUEMENT un objet JSON valide.\n2. Si une information est manquante, utilise \"Non sp\u00e9cifi\u00e9\".\n3. Le format de sortie doit \u00eatre exactement celui-ci :\n[{\n  \"company\": \"Nom de l'entreprise\",\n  \"title\": \"Intitul\u00e9 du poste\",\n  \"location\": \"Ville/Pays\",\n  \"src_url\": \"L'URL d'origine\",\n  \"deadline\": \"Date limite ou 'Non sp\u00e9cifi\u00e9'\",\n  \"salary\": \"Salaire/Gratification ou 'Non sp\u00e9cifi\u00e9'\",\n  \"duration\": \"Dur\u00e9e du stage ou 'Non sp\u00e9cifi\u00e9'\",\n  \"skills\": [\"Comp\u00e9tence 1\"],\n  \"summary\": \"R\u00e9sum\u00e9 en 1 phrase\"\n}]\n\n4. Si l'input contient plusieurs r\u00e9sultats, retourne un tableau d'objets : [...]\n5. IMPORTANT : Ne mets JAMAIS de backticks (```) ou de balises markdown. Donne uniquement le JSON brut.\n\nVOICI LES DONN\u00c9ES :\n{{ JSON.stringify($json) }}"
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.ollama",
      "typeVersion": 1,
      "position": [
        -176,
        448
      ],
      "id": "a6a918dc-e7a1-4a23-897c-74d83d391442",
      "name": "Analyse2",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "llama3.2:1b",
          "mode": "list",
          "cachedResultName": "llama3.2:1b"
        },
        "messages": {
          "values": [
            {
              "content": "=Tu es mon assistant de recherche de stage.\nVoici la liste finale des offres retenues :\n{{ JSON.stringify($json) }}\n\nFais-moi une synth\u00e8se rapide :\n1. Combien d'offres as-tu trouv\u00e9es ?\n2. Quelle est la MEILLEURE offre selon toi et pourquoi ?\n3. Donne-moi un conseil cl\u00e9 pour r\u00e9ussir ma candidature \u00e0 cette entreprise."
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.ollama",
      "typeVersion": 1,
      "position": [
        224,
        448
      ],
      "id": "708bda41-beb9-4713-876c-10b179a3162c",
      "name": "Synthese2",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## Mod\u00e8les Ollama \nVous pouvez aussi utiliser un LLM qui tourne en local (si jamais vous avez d\u00e9pass\u00e9 le nombre de tokens par exemple). \n\nPour t\u00e9l\u00e9chargez un mod\u00e8le : \n```bash\ndocker exec ollama ollama pull llama3.2:1b\n```",
        "height": 416,
        "width": 976
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        240
      ],
      "typeVersion": 1,
      "id": "491bc6c6-d711-431f-8708-036c075a7658",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "jsCode": "const results = [];\n\n// 1. Parsing du JSON\nfor (const item of $input.all()) {\n  try {\n    let content = item.json.output || item.json.message?.content || \"\";\n    content = content.replace(/```json/g, \"\").replace(/```/g, \"\").trim();\n    \n    const parsed = JSON.parse(content);\n\n    if (Array.isArray(parsed)) {\n        results.push(...parsed);\n    } else {\n        results.push(parsed);\n    }\n  } catch (error) {\n  }\n}\n\n// 2. BONUS : Calcul du Score Personnalis\u00e9\nresults.forEach(offer => {\n   if (offer.city_score) {\n       const qol = offer.city_score.quality_of_life_index || 0;\n       const safety = offer.city_score.safety_index || 0;\n       const climate = offer.city_score.climate_index || 0;\n\n       const score = (qol * 0.5) + (safety * 0.3) + (climate * 0.2);\n\n       offer.personal_score = parseFloat(score.toFixed(2));\n   } else {\n       offer.personal_score = 0;\n   }\n});\n\nreturn results;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        0
      ],
      "id": "9f111e0d-bb22-4b8a-b851-6bdf5853e5ff",
      "name": "Formattage"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "00a39bac-8d03-4b87-9a7d-a1dfbbccdf02",
              "leftValue": "={{ $json.personal_score }}",
              "rightValue": 100,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.3,
      "position": [
        1280,
        0
      ],
      "id": "11b06c7c-ccd8-4c2d-85ae-8c76cbf9d0fc",
      "name": "Filtres"
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2144,
        16
      ],
      "id": "d88ce249-f9ac-4cae-b63e-a911afdfd001",
      "name": "Merge"
    }
  ],
  "connections": {
    "Fusion": {
      "main": [
        [
          {
            "node": "Analyse",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search": {
      "main": [
        [
          {
            "node": "MCP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MCP": {
      "main": [
        [
          {
            "node": "Fusion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Return combinations of cities and technology matrix": {
      "main": [
        [
          {
            "node": "Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Return combinations of cities and technology matrix",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mistral Cloud Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Analyse",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Mistral Cloud Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Synth\u00e8se",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Synth\u00e8se",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyse": {
      "main": [
        [
          {
            "node": "Formattage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synth\u00e8se": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formattage": {
      "main": [
        [
          {
            "node": "Filtres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtres": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          },
          {
            "node": "Convert to File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to File": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "cc60ebaf-a2bd-45a7-bd42-f778431f74ee",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "C7wJhwtzndzvDniSRdzkx",
  "tags": []
}