# Code examples from: Build a RAG Chatbot That Answers From Your Own PDFs and Excel Files (Complete Guide)
# https://www.atulvij.com/build-rag-chatbot-python
# Review and adapt before running; examples use sample values.

# ---- Example 1 (bash) ----
pip install pypdf sentence-transformers numpy pandas openpyxl requests
ollama pull llama3.2

# ---- Example 2 (python) ----
import glob
from pypdf import PdfReader
import pandas as pd

def chunk_text(text, source, size=800, overlap=150):
    text = " ".join(text.split())                   # normalise whitespace
    out = []
    for start in range(0, len(text), size - overlap):
        piece = text[start:start + size]
        if len(piece) > 100:
            out.append({"source": source, "text": piece})
    return out

def load_pdfs(folder):
    chunks = []
    for path in glob.glob(f"{folder}/*.pdf"):
        for n, page in enumerate(PdfReader(path).pages, 1):
            chunks += chunk_text(page.extract_text() or "", f"{path} p.{n}")
    return chunks

def load_excel(path, sheet=0):
    df = pd.read_excel(path, sheet_name=sheet).fillna("")
    # one chunk per row: "Column: value; Column: value"
    return [{"source": f"{path} row {i + 2}",
             "text": "; ".join(f"{c}: {v}" for c, v in row.items() if v != "")}
            for i, row in df.iterrows()]

chunks = load_pdfs("docs") + load_excel("docs/price_list.xlsx")
print(len(chunks), "chunks")

# ---- Example 3 (python) ----
import numpy as np
from sentence_transformers import SentenceTransformer

embedder = SentenceTransformer("all-MiniLM-L6-v2")      # ~90 MB, fast on CPU
vectors = embedder.encode([c["text"] for c in chunks],
                          normalize_embeddings=True, show_progress_bar=True)
np.save("vectors.npy", vectors)                         # reuse next time

# ---- Example 4 (python) ----
def search(question, k=4):
    q = embedder.encode([question], normalize_embeddings=True)[0]
    scores = vectors @ q
    best = np.argsort(-scores)[:k]
    return [chunks[i] | {"score": float(scores[i])} for i in best]

for hit in search("How many days do customers have to return an item?"):
    print(round(hit["score"], 2), hit["source"], hit["text"][:80])

# ---- Example 5 (python) ----
import requests

PROMPT = """Answer the question using ONLY the context below.
If the answer is not in the context, reply "I could not find this in the documents."
Cite sources in square brackets like [1] after each fact.

Context:
{context}

Question: {question}
Answer:"""

def ask(question, k=4, model="llama3.2"):
    hits = search(question, k)
    context = "\n\n".join(f"[{i + 1}] ({h['source']}) {h['text']}" for i, h in enumerate(hits))
    r = requests.post("http://localhost:11434/api/generate", timeout=300, json={
        "model": model, "stream": False, "options": {"temperature": 0.1},
        "prompt": PROMPT.format(context=context, question=question)})
    return r.json()["response"], hits

answer, hits = ask("What is the price of the 27-inch monitor and is there a bulk discount?")
print(answer)
for i, h in enumerate(hits, 1):
    print(f"[{i}] {h['source']}")

# ---- Example 6 (python) ----
while True:
    q = input("\nQuestion (blank to quit): ").strip()
    if not q:
        break
    answer, hits = ask(q)
    print("\n" + answer)
    print("Sources:", ", ".join(h["source"] for h in hits))

# ---- Example 7 (python) ----
tests = [("What is the return window?", "returns_policy.pdf"),
         ("Who approves travel above ₹50,000?", "travel_sop.pdf")]
for q, expected in tests:
    found = any(expected in h["source"] for h in search(q, k=4))
    print("OK " if found else "MISS", q)

