diff --git a/README.md b/README.md
index c31aa4f..d87387c 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ En plattform för att utforska riksdagsdebatter och metadata. Backenden bygger p
└──────────────┘
```
-- **ArangoDB**: primär databas. Sök görs via ArangoSearch-vyer och AQL; inga relationsfrågor används.
+- **ArangoDB**: primär databas. Sök görs via ArangoSearch-vyer i stället för SQL.
- **ChromaDB**: håller embeddings för chatten och mer avancerad semantisk sökning.
- **LLM**: genererar chattsvar med hjälp av `_llm.LLM` (kopplad till t.ex. vLLM/OpenAI API).
@@ -101,6 +101,7 @@ riksdagen/
- `search(...)`: bygger en ArangoSearch AQL-query med filter (parti, år, talare) samt scoring via `BM25()` eller `TFIDF()`.
- `get_metadata(...)`: hämtar distinkta värden till frontendens filter.
- `get_document_by_id(...)`: används för detaljerad visning/snippets.
+- **Viktigt:** ingen SQL används. AQL byggs dynamiskt och exekveras mot en ArangoSearch-vy (se `_arango/queries`).
### `backend/chat.py`
@@ -148,30 +149,26 @@ riksdagen/
### Sökning via ArangoSearch
-1. **Frontend (`SearchPanel.tsx`)** samlar användarens filter och anropar `searchTalks` i `frontend/src/api.ts`.
-2. **FastAPI (`backend/search.py`)** tar emot POST `/api/search`, instansierar `SearchService` och skickar vidare parametrar.
-3. **`SearchService.search`** bygger en AQL-fråga mot ArangoSearch-vyn (t.ex. `talks_view`) där filter och scoring läggs till. Se `_arango/queries/search_view.py` för den faktiska queryn.
- ```aql
- FOR doc IN talks_view
- SEARCH ANALYZER(doc.anforande_text, "text_sv") %PHRASE(@query)%
- FILTER (@party == null OR doc.party == @party)
- FILTER (@year == null OR doc.year == @year)
- SORT BM25(doc) DESC
- LIMIT @offset, @limit
- RETURN MERGE(doc, { snippet: doc.preview })
- ```
-4. **ArangoDB** returnerar matchande dokument. Backend formaterar svaren (färger, snippet) och skickar JSON tillbaka.
-5. **Frontend** renderar resultatet i `ResultsTable.tsx`, uppdaterar statistikpaneler och hanterar paginering.
+1. **Frontend** samlar filtervärden (t.ex. `query="skola"`, `party="S"`, `year=2023`).
+2. `api.searchTalks` gör POST till `/api/search`.
+3. **FastAPI** tar emot request → skapar `SearchService`.
+4. `SearchService.search`:
+ - Bygger AQL med `SEARCH`-klasuler mot en ArangoSearch-vy (exempel: `SEARCH ANALYZER(doc.anforande_text, "text_sv") LIKE "%skola%"`).
+ - Använder `SORT BM25(doc)` eller `SORT doc.datum` beroende på begärda sorteringsparametrar.
+ - Begränsar resultat (`LIMIT offset, limit`) och returnerar träffar + total count.
+5. Backend serialiserar dokumenten till JSON (inklusive metadata som färger, snippet, etc.).
+6. **Frontend** renderar resultat och uppdaterar paginering/statistikpaneler.
-### Chattflöde med LLM och Chroma
+### Chatt med LLM
-1. **Frontend (`ChatPanel.tsx`)** skickar konversationen till `POST /api/chat`.
-2. **`backend/chat.py`**:
- - Extraherar senaste användarfrågan.
- - Hämtar relevanta chunkar via Chroma (`_chromadb.collections.get_collection(...).query(...)`).
- - Optionellt kompletterar kontexten med `SearchService.search` för ArangoSearch-träffar.
- - Bygger prompten, anropar `_llm.LLM(model="vllm")` och returnerar svaret.
-3. **Frontend** lägger till AI-svaret i konversationen och visar källor/metadata för transparens.
+1. Användaren skriver fråga i chat-panelen.
+2. `api.chat` skickar en lista av meddelanden (`[{ role: 'user', content: ... }, ...]`).
+3. `backend/chat.py`:
+ - Tar ut senaste användarfrågan.
+ - `vector_search(...)` i samma modul eller via `_chromadb` hämtar semantiskt relevanta avsnitt.
+ - Kan komplettera svar med `SearchService`-resultat för att säkerställa faktabas.
+ - Lägger samman kontext och anropar `_llm.LLM`.
+4. AI-svaret skickas tillbaka till frontenden och läggs på konversationen.
---
@@ -179,65 +176,47 @@ riksdagen/
| Fil | Funktion |
|-----|----------|
-| `arango_client.py` | Initierar Arango-klienten med anslutningsuppgifter från miljön. |
-| `_arango/_arango.py` | Wrapper som kapslar uppkoppling och hjälpfunktioner mot ArangoDB och views. |
-| `_arango/queries/*` | Samling återskapbara AQL-snippets för sök, metadata och detaljhämtning. |
-| `_chromadb/client.py` | Skapar ChromaDB-klienten (lokal DuckDB + Parquet som standard). |
-| `_chromadb/collections.py` | Hjälper till att skapa/hämta Chroma-samlingar, lägga in chunkar och göra `query`. |
-| `_llm/llm.py` | OpenAI-kompatibel klient. Använd `LLM(model='vllm')` och `chat(...)` för generering. |
-| `backend/search.py` | Innehåller `SearchService` och API-routes för sök, bygger AQL mot ArangoSearch. |
-| `backend/chat.py` | Chat- och vector-endpoints. Kopplar ihop Chroma-resultat med LLM-svar. |
-| `frontend/src/api.ts` | Axios-klient för att prata med backenden (`searchTalks`, `chat`, `vectorSearch`, `getMeta`). |
-| `frontend/src/components/*` | UI-komponenter i React/TypeScript för sök, resultat, chat, statistik. |
+| `arango_client.py` | Central Arango-klient. Återanvänds i backend och scripts. |
+| `_arango/_arango.py` | Wrapper som läser miljövariabler, öppnar anslutningar och ger helpers för views/collections. |
+| `_arango/queries/*` | Samlade AQL-snippets för sökning, metadata, detaljer. Läs dessa för att förstå exakta fält och filtreringslogik. |
+| `_chromadb/client.py` | Skapar en Chroma-klient (kan vara persistent eller in-memory beroende på config). |
+| `_chromadb/collections.py` | Hjälpfunktioner för att skapa/hämta Chroma-samlingar och göra `query`/`add`. |
+| `_llm/llm.py` | LLM-wrapper. `LLM(model='vllm')` returnerar en klient med `.chat(messages=...)`. |
+| `backend/search.py` | `SearchService` med AQL/ArangoSearch-hantering och responsformatering. |
+| `backend/chat.py` | Chat-endpoints, orchestrerar Chroma + LLM + (valfritt) Arango-sök. |
+| `frontend/src/api.ts` | Samlar alla HTTP-anrop till backend. Lätt att mocka i tester. |
+| `frontend/src/components/*` | React-komponenter. Kommentera gärna i koden var data kommer ifrån och vad som skickas tillbaka till backend. |
---
## 🗄️ ArangoDB och ArangoSearch
-- **Kollektioner**: rådata om tal, talare och debatter lagras i Arango-kollektioner.
-- **Views**: exempelvis `talks_view` indexerar textfält med analysatorn `text_sv`. Alla fritextfrågor går via vyn.
-- **AQL-byggstenar**:
+- **Data** ligger i ArangoDB-kollektioner, ofta `talks`, `speakers`, `debates`, etc.
+- **Views**: en ArangoSearch-vy (t.ex. `talks_view`) indexerar textfält med svenska analyser (`text_sv`). Detta gör att `SEARCH`-frågor kan använda ranking (BM25/TFIDF) och filtrera på fält.
+- **AQL-byggstenar** (se `_arango/queries`):
- `FOR doc IN talks_view`
- - `SEARCH` med `ANALYZER`, `PHRASE` eller token-matchning.
- - `FILTER` för parti, år, kategori och talare.
- - `SORT BM25(doc)` eller `SORT doc.datum` beroende på begärd sortering.
- - `LIMIT @offset, @limit` för paginering.
-- **Metadata**: hämtas via dedikerade queries i `_arango/queries/meta.py` (t.ex. `COLLECT party = doc.party RETURN DISTINCT party`).
-
----
+ - `SEARCH ANALYZER(doc.anforande_text, "text_sv") LIKE "%..."%`
+ - `FILTER doc.party == @party`
+ - `SORT BM25(doc) DESC`
+ - `LIMIT @offset, @limit`
+- **Metadata**: Distinkta listor (ex. partier, år) hämtas via AQL `COLLECT`.
-## 🧠 ChromaDB för vektorsök
-
-- Embeddings genereras via `scripts/build_embeddings.py` som:
- 1. Läser dokument från Arango via `arango_client`.
- 2. Delar text i chunkar (`_chromadb/embeddings.py`).
- 3. Beräknar embeddings (vanligen med `_llm` eller annan embeddingsgenerator).
- 4. Sparar chunkar + metadata i en Chroma-samling.
-- Chatten hämtar liknande chunkar med `collection.query(query_texts=[...], n_results=top_k)` och använder resultaten i prompten.
+> **Ny i ArangoSearch?** Läs igenom `_arango/queries` och Arangos officiella exempel. Notera att ArangoSearch skiljer sig från vanliga AQL-index genom att all textindexering sker i vyn, inte kollektionen.
---
-## 🔧 Utvecklingsflöde
-
-| Steg | Befallning | Kommentar |
-|------|------------|-----------|
-| Starta backend i utvecklingsläge | `uvicorn backend.app:app --reload --port 8000` | Kör i projektroten. |
-| Starta frontend i dev-läge | `cd frontend && npm run dev` | Öppna http://localhost:5173. |
-| Bygg frontend för produktion | `make frontend` | Skapar `frontend/dist`. |
-| Ladda om nginx (om du serverar statiskt) | `sudo systemctl reload nginx` | Efter ny frontend-build. |
-| Kör embeddings-script | `python -m scripts.build_embeddings --limit 500` | Uppdaterar Chroma-samlingen. |
-| Kör Arango-inmatning | `python -m scripts.ingest_arguments` | Läser in tal/metadata i Arango. |
-
----
+## 🧠 ChromaDB för vektorsök
-## 🧪 Felsökning och tips
+- `_chromadb/client.py` konfigurerar anslutningen (lokal fil, DuckDB + Parquet som standard).
+- Embeddings genereras i `scripts/build_embeddings.py`:
+ 1. Hämtar tal från ArangoDB med `arango_client`.
+ 2. Delar upp text i chunkar (se `_chromadb/embeddings.py` för heuristiken).
+ 3. Kör embeddingsgenerator (vanligen via `_llm` eller egen modul).
+ 4. Lagrar chunkar + metadata (`talk_id`, `speaker`, `party`, `source`) i Chroma-samlingen.
-- **Arango-anslutning**: kör `python arango_client.py` eller `arangosh --server.endpoint tcp://127.0.0.1:8529`.
-- **Kontrollera AQL**: använd ArangoDB Web UI eller `arangosh` för att testa samma query som `SearchService` byggde.
-- **Chroma-status**: kontrollera `persist_directory` i `_chromadb/client.py`; ta bort katalogen om du behöver återskapa samlingen.
-- **Embeddings**: kör `python -m scripts.build_embeddings --limit 20` för att uppdatera Chroma när nytt material importeras.
-- **LLM-svar**: logga prompt och kontext i `backend/chat.py` om svaren känns irrelevanta.
-- **React/TypeScript**: använd `useQuery` för GET, `useMutation` för POST och typsätt API-kontrakt via `frontend/src/types.ts`.
+- Vid chat:
+ - `vector_search` skickar användarfrågan som embedding till Chroma (`collection.query(query_texts=[...], n_results=top_k)`).
+ - Resultatet innehåller `ids`, `documents`, `metadatas`, `distances` som matas in i prompten.
---
@@ -263,3 +242,1163 @@ riksdagen/
> Undvik onödiga `try/except`. Fel bubbla upp så att du ser den faktiska orsaken (t.ex. nätverksproblem, fel API-nyckel).
+---
+
+## 🛠️ Utvecklingsflöde
+
+| Steg | Befallning | Kommentar |
+|------|------------|-----------|
+| Starta backend i utvecklingsläge | `uvicorn backend.app:app --reload --port 8000` | Körs i projektroten. Automatiskt reload vid filändring. |
+| Starta frontend i dev-läge | `cd frontend && npm run dev` | Öppna http://localhost:5173 |
+| Bygg frontend för produktion | `make frontend` | Skapar `frontend/dist`. |
+| Ladda om nginx (om du serverar statiskt) | `sudo systemctl reload nginx` | Efter ny frontend-build. |
+| Kör embeddings-script | `python -m scripts.build_embeddings --limit 500` | Uppdaterar Chroma-samlingen. |
+| Kör anpassade Arango-script | `python -m scripts.ingest_arguments` | Exempel, kontrollera scriptens docstrings. |
+
+---
+
+## 🧪 Felsökning och tips
+
+- **Arango-anslutning**: kör `python arango_client.py` för att testa att lösenord och URL stämmer.
+- **Chroma-data**: öppna `_chromadb/client.py` och kontrollera `persist_directory`. Rensa katalogen om du behöver resetta.
+- **Chat-svar saknar fakta**: kontrollera att `build_embeddings.py` har körts nyligen och att Chroma-samlingen innehåller rätt metadata.
+- **React/TypeScript**:
+ - Använd `useQuery` (för GET) och `useMutation` (för POST).
+ - Håll komponenter små och bryt ut delkomponenter vid behov (t.ex. `FilterControls`, `ChatMessageList`).
+ - Typsätt props, state och API-svar med `types.ts` för bättre editorstöd.
+- **Python-stil**:
+ - Lägg till type hints på funktioner (ex: `def search(self, params: SearchRequest) -> SearchResponse:`).
+ - Skriv docstrings som beskriver syfte, parametrar och returvärden.
+ - Undvik generella `try/except`; analysera felen vid körning och fixa orsaken.
+
+---
+
+## 🤔 Nästa steg
+
+1. **Kommentera kod**: lägg till docstrings i `SearchService` och chat-hjälpare.
+2. **Testa ArangoQueries**: skapa enhetstester som kör små AQL-scenarier med testdata.
+3. **Frontend-guidning**: överväg inline-kommentarer i React-komponenterna för att förklara state-hantering.
+4. **Observabilitet**: logga vilka ArangoSearch-queries som körs vid olika filterkombinationer.
+
+---
+
+## 📎 Referenser
+
+- [ArangoDB Documentation – ArangoSearch](https://www.arangodb.com/docs/stable/arangosearch.html)
+- [ChromaDB Python API](https://docs.trychroma.com/)
+- [FastAPI – Depends och Pydantic](https://fastapi.tiangolo.com/)
+- [React + TypeScript Cheatsheets](https://react-typescript-cheatsheet.netlify.app/)
+- `batch_embed()` – Processar flera texter samtidigt
+
+**Exempel**:
+```python
+def get_embedding(text: str, model: str = "embeddinggemma") -> List[float]:
+ """
+ Genererar en embedding-vektor för given text.
+
+ Args:
+ text: Texten att embedda
+ model: Ollama-modell (default: embeddinggemma)
+
+ Returns:
+ Lista med floats (3072 dimensioner för embeddinggemma)
+ """
+ # Anropar http://192.168.1.11:11434/api/embeddings
+ # Returnerar vektor
+```
+
+#### `backend/services/vector_store.py`
+
+Hanterar pgvector-operationer:
+- `create_embeddings_table()` – Skapar `talk_embeddings`-tabell
+- `store_embedding()` – Sparar embedding i databas
+- `query_similar_talks()` – Semantisk sökning
+
+---
+
+### Database: `backend/database.py`
+
+**Viktigt**: Denna fil skapar databaskopplingen.
+
+```python
+# Skapar engine (kopplingen till PostgreSQL)
+engine = create_engine(DATABASE_URL)
+
+# Aktiverar pgvector-extension
+with engine.connect() as conn:
+ conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
+
+# Session factory – används för att skapa databas-sessioner
+SessionLocal = sessionmaker(bind=engine)
+
+# Dependency för FastAPI – ger varje endpoint en databas-session
+def get_db():
+ db = SessionLocal()
+ try:
+ yield db # Ger sessionen till endpointen
+ finally:
+ db.close() # Stänger sessionen efter request
+```
+
+**Hur används det**:
+```python
+@router.post("/api/search")
+def search_endpoint(request: SearchRequest, db: Session = Depends(get_db)):
+ # db är nu en aktiv databas-session
+ results = search_talks(db, ...)
+```
+
+---
+
+## ⚛️ Frontend-arkitektur
+
+### Huvudfil: `frontend/src/App.tsx`
+
+Detta är root-komponenten som renderas i `index.html`.
+
+```typescript
+function App() {
+ const [mode, setMode] = useState<'search' | 'chat' | 'vector'>('search');
+
+ // mode styr vilket läge användaren är i:
+ // - 'search': Vanlig nyckelordssökning
+ // - 'chat': AI-chattläge
+ // - 'vector': Semantisk vektorsökning
+
+ return (
+
+ {/* Knappar för att växla läge */}
+
+
+
+ {/* Visar olika komponenter baserat på mode */}
+ {mode === 'search' && }
+ {mode === 'chat' && }
+ {mode === 'vector' && }
+
+ );
+}
+```
+
+**Viktiga React-koncept**:
+- `useState`: Skapar state-variabel som kan ändras. När `mode` ändras, re-renderar React komponenten.
+- `{ }`: JavaScript-uttryck inuti JSX (React's HTML-liknande syntax)
+- `&&`: Villkorlig rendering – `condition && ` renderar bara om condition är true
+
+---
+
+### API-klient: `frontend/src/api.ts`
+
+Detta är hur frontend pratar med backend.
+
+```typescript
+import axios from 'axios';
+
+// Base URL – i produktion går detta via nginx reverse proxy
+const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
+
+// axios-instans med konfigurerad base URL
+const api = axios.create({
+ baseURL: API_BASE_URL,
+ headers: { 'Content-Type': 'application/json' }
+});
+
+// Funktion för att söka
+export async function searchTalks(params: SearchParams): Promise {
+ const response = await api.post('/search', params);
+ return response.data; // Backend returnerar JSON
+}
+
+// Funktion för att chatta
+export async function chat(messages: ChatMessage[]): Promise {
+ const response = await api.post('/chat', { messages });
+ return response.data;
+}
+```
+
+**TypeScript-koncept**:
+- `async/await`: Hanterar asynkrona operationer (API-anrop)
+- `Promise`: Representerar ett framtida värde av typ T
+- `interface`: Definierar shape av objekt (se `types.ts`)
+
+---
+
+### Komponenter
+
+#### `frontend/src/components/SearchPanel.tsx`
+
+Sökformulär med filter.
+
+**State-hantering**:
+```typescript
+const [query, setQuery] = useState(''); // Fritextsök
+const [party, setParty] = useState(''); // Valt parti
+const [year, setYear] = useState(''); // Valt år
+// ...fler filter
+
+// react-query mutation för att söka
+const searchMutation = useMutation({
+ mutationFn: searchTalks,
+ onSuccess: (data) => {
+ // data innehåller SearchResponse från backend
+ // Skickas till ResultsTable via props
+ }
+});
+
+// När användaren klickar "Sök"
+const handleSearch = () => {
+ searchMutation.mutate({
+ query,
+ party,
+ year,
+ // ...andra filter
+ });
+};
+```
+
+**React Query (`useMutation`)**:
+- Hanterar API-anrop automatiskt
+- Ger tillgång till `isLoading`, `error`, `data`
+- Cachas resultat för snabbare återanvändning
+
+**JSX-exempel**:
+```tsx
+ setQuery(e.target.value)}
+ placeholder="Sök efter innehåll..."
+/>
+```
+
+**Hur det fungerar**:
+1. `value={query}` – Input-fältet visar värdet av `query` state
+2. `onChange` – När användaren skriver, uppdateras `query` med `setQuery()`
+3. Detta är "controlled component" pattern i React
+
+---
+
+#### `frontend/src/components/ResultsTable.tsx`
+
+Visar sökresultat i tabell.
+
+```typescript
+interface ResultsTableProps {
+ results: TalkResult[]; // Array av anföranden från backend
+ total: int; // Totalt antal träffar
+ isLoading: boolean; // Om data laddas
+}
+
+function ResultsTable({ results, total, isLoading }: ResultsTableProps) {
+ if (isLoading) return
Laddar...
;
+
+ if (results.length === 0) return
Inga resultat
;
+
+ return (
+
+
+
+
Datum
+
Talare
+
Parti
+
Innehåll
+
+
+
+ {results.map((talk) => (
+
+
{talk.datum}
+
{talk.talare}
+
+ {talk.parti}
+
+
{talk.snippet}
+
+ ))}
+
+
+ );
+}
+```
+
+**Viktigt**:
+- `.map()`: Loopar över array och returnerar JSX för varje element
+- `key={talk.id}`: React behöver unika keys för att effektivt uppdatera listan
+- Props nedåt: Parent (SearchPanel) skickar data till child (ResultsTable)
+
+---
+
+#### `frontend/src/components/ChatPanel.tsx`
+
+AI-chattgränssnitt.
+
+**State**:
+```typescript
+const [messages, setMessages] = useState([
+ { role: 'assistant', content: 'Hej! Vad vill du veta om riksdagsdebatter?' }
+]);
+const [input, setInput] = useState(''); // Användarens input
+
+const chatMutation = useMutation({
+ mutationFn: (msgs: ChatMessage[]) => chat(msgs),
+ onSuccess: (response) => {
+ // Lägg till AI-svaret i messages
+ setMessages(prev => [...prev, { role: 'assistant', content: response.reply }]);
+ }
+});
+
+const handleSend = () => {
+ // Lägg till användarens meddelande
+ const newMessages = [...messages, { role: 'user', content: input }];
+ setMessages(newMessages);
+ setInput(''); // Töm input-fält
+
+ // Skicka till backend
+ chatMutation.mutate(newMessages);
+};
+```
+
+**Rendering**:
+```tsx
+