{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9edc94e3",
   "metadata": {},
   "source": [
    "## C5_02 — Construirea vector store-ului pentru o bulă\n",
    "În acest notebook construim un vector store FAISS pentru o singură bulă / un singur agent.\n",
    "Fiecare student lucrează pe bula lui. Scopul este să vedem clar cum textele curățate devin embeddings, apoi index FAISS.\n",
    "Mai târziu, aceeași logică va fi pusă într-un script `.py` care rulează automat pentru toate bulele."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4db7b086",
   "metadata": {},
   "source": [
    "## 0. Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b9f6f2d0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\PROJECTS\\echochamber-app\\.venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "import os, pickle\n",
    "import pandas as pd\n",
    "import faiss\n",
    "from sentence_transformers import SentenceTransformer\n",
    "\n",
    "while not Path(\"data/bubbles\").exists():\n",
    "    os.chdir(\"..\")\n",
    "\n",
    "BUBBLES_DIR = Path(\"data/bubbles\")\n",
    "VECTOR_DIR = Path(\"assets/vectorstores\")\n",
    "VECTOR_DIR.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "MODEL_NAME = \"paraphrase-multilingual-MiniLM-L12-v2\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25c1259a",
   "metadata": {},
   "source": [
    "## 1. Aleg bula mea\n",
    "Alege fișierul `.jsonl` al bulei tale.\n",
    "Acest fișier a fost creat în etapa anterioară, după verificarea manuală a textelor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "40758b86",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Bula: anti_sistem\n",
      "Texte: 50\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>agent</th>\n",
       "      <th>text</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>yt_joXkZDqGZQU_Ugyqb1XZ7P8GTnJS_4p4AaABAg</td>\n",
       "      <td>Anti-sistem</td>\n",
       "      <td>Semneaza Bo$$ ca la urmatoarele alegerii nu ma...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>yt_pO0JOPX1I7Q_UgyExKkzQjU2eOVTG7J4AaABAg</td>\n",
       "      <td>Anti-sistem</td>\n",
       "      <td>ESTE NEVOIE DE O FESTAÑIE LA TOATE NIVELURILE ...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>yt_6_Hc2S02Duw_Ugz2UatUIFNpL1SP7u54AaABAg</td>\n",
       "      <td>Anti-sistem</td>\n",
       "      <td>Orcii fac ore suplimentare!! Bravo! Daca ati m...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>yt_YFbJhBc_9jo_Ugx9GFXKkTHUqa4NYcZ4AaABAg</td>\n",
       "      <td>Anti-sistem</td>\n",
       "      <td>Câtă nesimțire!!! Câtă hoție pe fațaă!!! Ce ră...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>yt_YFbJhBc_9jo_UgzrK_gcZVEbQXhfL614AaABAg</td>\n",
       "      <td>Anti-sistem</td>\n",
       "      <td>Biserica, aceasta sinecura de sifonat bani, es...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                          id        agent  \\\n",
       "0  yt_joXkZDqGZQU_Ugyqb1XZ7P8GTnJS_4p4AaABAg  Anti-sistem   \n",
       "1  yt_pO0JOPX1I7Q_UgyExKkzQjU2eOVTG7J4AaABAg  Anti-sistem   \n",
       "2  yt_6_Hc2S02Duw_Ugz2UatUIFNpL1SP7u54AaABAg  Anti-sistem   \n",
       "3  yt_YFbJhBc_9jo_Ugx9GFXKkTHUqa4NYcZ4AaABAg  Anti-sistem   \n",
       "4  yt_YFbJhBc_9jo_UgzrK_gcZVEbQXhfL614AaABAg  Anti-sistem   \n",
       "\n",
       "                                                text  \n",
       "0  Semneaza Bo$$ ca la urmatoarele alegerii nu ma...  \n",
       "1  ESTE NEVOIE DE O FESTAÑIE LA TOATE NIVELURILE ...  \n",
       "2  Orcii fac ore suplimentare!! Bravo! Daca ati m...  \n",
       "3  Câtă nesimțire!!! Câtă hoție pe fațaă!!! Ce ră...  \n",
       "4  Biserica, aceasta sinecura de sifonat bani, es...  "
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MY_BUBBLE_FILE = \"anti_sistem.jsonl\" \n",
    "\n",
    "bubble_path = BUBBLES_DIR / MY_BUBBLE_FILE\n",
    "slug = bubble_path.stem\n",
    "\n",
    "df_bubble = pd.read_json(bubble_path, lines=True)\n",
    "\n",
    "print(\"Bula:\", slug)\n",
    "print(\"Texte:\", len(df_bubble))\n",
    "\n",
    "df_bubble[[\"id\", \"agent\", \"text\"]].head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5191798",
   "metadata": {},
   "source": [
    "## 2. Pregătim textele\n",
    "Pentru FAISS avem nevoie de o listă simplă de texte.\n",
    "Metadata rămâne separat, ca să putem lega fiecare vector de textul original."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "41a4a6d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Primul text:\n",
      "Semneaza Bo$$ ca la urmatoarele alegerii nu mai iesi presedinte. Noi ca tara si popor suntem rupti in cur cu salarii de vietnam si preturi de SIngapore.... dar ajutam cu banii Ukraina ... alta tara corupta la fel si Rusia\n"
     ]
    }
   ],
   "source": [
    "texts = df_bubble[\"text\"].fillna(\"\").tolist()\n",
    "metadata = df_bubble.to_dict(orient=\"records\")\n",
    "\n",
    "print(\"Primul text:\")\n",
    "print(texts[0][:500])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21aed8ad",
   "metadata": {},
   "source": [
    "## 3. Generăm embeddings\n",
    "Un embedding este o reprezentare vectorială a textului: texte apropiate ca sens primesc vectori apropiați în spațiul semantic.\n",
    "Folosim un model multilingv, deoarece corpusul este în limba română.\n",
    "Normalizăm vectorii la lungime 1, astfel încât produsul scalar din FAISS să funcționeze ca similaritate cosinus."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "775c1b17",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n",
      "Loading weights: 100%|██████████| 199/199 [00:00<00:00, 5094.68it/s]\n",
      "Batches: 100%|██████████| 2/2 [00:01<00:00,  1.67it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Număr texte: 50\n",
      "Dimensiune embeddings: (50, 384)\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model = SentenceTransformer(MODEL_NAME)\n",
    "embeddings = model.encode(\n",
    "    texts,\n",
    "    normalize_embeddings=True,\n",
    "    show_progress_bar=True\n",
    ").astype(\"float32\")\n",
    "print(\"Număr texte:\", len(texts))\n",
    "print(\"Dimensiune embeddings:\", embeddings.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "564f0533",
   "metadata": {},
   "source": [
    "### Verificare rapidă\n",
    "Răspunde în 1–2 propoziții în notebook:\n",
    "- Câte texte are bula ta?\n",
    "- Câți vectori au fost generați?\n",
    "- Ce înseamnă a doua valoare din `embeddings.shape`?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "913a5f47",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO student:\n",
    "# Bula mea are ... texte.\n",
    "# Au fost generați ... vectori.\n",
    "# A doua valoare din embeddings.shape reprezintă ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb9d6ad5",
   "metadata": {},
   "source": [
    "## 4. Construim indexul FAISS\n",
    "FAISS este biblioteca care caută rapid vectori apropiați.\n",
    "Indexul nu păstrează textele originale. El păstrează doar reprezentările vectoriale.\n",
    "De aceea salvăm două lucruri:\n",
    "- `index.faiss` = indexul vectorial;\n",
    "- `index.pkl` = textele originale și metadatele."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e9909666",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Salvat în: assets\\vectorstores\\anti_sistem\n",
      "Vectori în index: 50\n"
     ]
    }
   ],
   "source": [
    "index = faiss.IndexFlatIP(embeddings.shape[1])\n",
    "index.add(embeddings)\n",
    "out_dir = VECTOR_DIR / slug\n",
    "out_dir.mkdir(parents=True, exist_ok=True)\n",
    "faiss.write_index(index, str(out_dir / \"index.faiss\"))\n",
    "with open(out_dir / \"index.pkl\", \"wb\") as f:\n",
    "    pickle.dump(metadata, f)\n",
    "print(\"Salvat în:\", out_dir)\n",
    "print(\"Vectori în index:\", index.ntotal)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d7d9a26",
   "metadata": {},
   "source": [
    "## 5. Verificăm fișierele create\n",
    "Dacă totul a mers corect, bula ta are acum un folder propriu în `assets/vectorstores/`.\n",
    "Acest folder trebuie să conțină `index.faiss` și `index.pkl`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "93343354",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO student:\n",
    "# index.faiss există: ...\n",
    "# index.pkl există: ...\n",
    "# index.ntotal este egal cu numărul de texte: ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aba36ed3",
   "metadata": {},
   "source": [
    "## Ce am construit?\n",
    "Am transformat textele curate ale unei bule într-un index vectorial local.\n",
    "Acest index nu generează răspunsuri. El doar permite căutarea semantică.\n",
    "În următorul continuare vom testa dacă, pentru o întrebare, FAISS returnează texte relevante."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05084712",
   "metadata": {},
   "source": [
    "## 6. Testăm retrieval-ul\n",
    "Acum simulăm logica aplicației.\n",
    "- Utilizatorul introduce o știre sau o afirmație politică.\n",
    "- Retriever-ul caută în memoria bulei cele mai asemănătoare texte.\n",
    "- Nu generăm încă un răspuns cu LLM. Doar verificăm ce exemple sunt recuperate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "ffa1c36e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Text nou introdus în aplicație\n",
    "\n",
    "input_text = \"CCR a decis anularea alegerilor după suspiciuni privind influențe externe.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1ed6ab5f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Transformăm textul nou în embedding\n",
    "\n",
    "query_vector = model.encode(\n",
    "    [input_text],\n",
    "    normalize_embeddings=True\n",
    ").astype(\"float32\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "24db5e25",
   "metadata": {},
   "outputs": [],
   "source": [
    "# query_vector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "9e1c437e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Rezultat 1\n",
      "Scor: 0.341\n",
      "Text: Daca nici acum nu intelegeti cine este cg si il mai votati,sunteti de doamne fereste...in afara de boti...Daca nu se anulau alegerile,oricum nu ieseai presedinte...\n",
      "\n",
      "Rezultat 2\n",
      "Scor: 0.334\n",
      "Text: De vina sunt acei concetățeni care, la vot, nu au pe cine vota, stau acasă pentru că votul lor nu contează. I-a să iasă la vot 90% din populație, să vezi atunci care sunt partidele care ne reprezintă.\n",
      "\n",
      "Rezultat 3\n",
      "Scor: 0.323\n",
      "Text: Simion este un escroc , și - a lăsat parlamentarii acasă , a spus râzând că AUR vrea pace , dar au înlesnit votul \" pentru \" ! Dacă ajungea el președinte , era la fel ca Tăntălăul onest ! 🤮🤮🤮\n",
      "\n",
      "Rezultat 4\n",
      "Scor: 0.22\n",
      "Text: Știi ce cîștiga Robert, spălații pe creier? Bani, multi bani pe care îi primesc din banii noștrii! Singura soluție de a scăpa de acești paraziți este modificarea legii partidelor și să se termine cu banii dați de la buget partidelor! Dacă vor să plătească presă să îi pupe în cur , să plătească din banii lor!\n",
      "\n",
      "Rezultat 5\n",
      "Scor: 0.216\n",
      "Text: Dar timp de 26 de ani de ce ai tacut ,? Te ai desteptat cu venirea la guvernare a lui Bolojan ,a presedintelui usrist ? Spune adevarul cu mana pe Biblie\n"
     ]
    }
   ],
   "source": [
    "# Căutăm cele mai apropiate 5 texte din bula noastră\n",
    "\n",
    "scores, results = index.search(query_vector, k=5)\n",
    "\n",
    "for rank, pos in enumerate(results[0], start=1):\n",
    "    row = metadata[pos]\n",
    "    \n",
    "    print(f\"\\nRezultat {rank}\")\n",
    "    print(\"Scor:\", round(float(scores[0][rank-1]), 3))\n",
    "    print(\"Text:\", row[\"text\"][:500])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "770045ed",
   "metadata": {},
   "source": [
    "### TODO\n",
    "Schimbă `input_text` cu o afirmație potrivită pentru agentul tău.\n",
    "Rulează căutarea.\n",
    "Notează:\n",
    "- câte rezultate din 5 sunt relevante;\n",
    "- dacă textele recuperate exprimă vocea agentului;\n",
    "- dacă ai observat un text slab care ar trebui eliminat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c3fcfc18",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "86812671",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2bef815",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (.venv)",
   "language": "python",
   "name": "echochamber"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
