"""
Worked cost example for a small RAG (retrieval-augmented generation) pipeline.

Every dollar figure in this script comes from ../pricing.json (site/public/data/pricing.json)
at the time it was run -- nothing is hardcoded from memory. Run this script
again after pricing.json updates and the numbers will change accordingly.

Scenario (all assumptions, stated up front -- change these constants to
model a different pipeline):
  - 500 source documents, 800 words each (a small internal knowledge base)
  - Words are converted to tokens at 0.75 words/token (1 token ~ 4 English
    characters ~ 0.75 words), the same heuristic already used elsewhere on
    this site. This is an estimate, not a tokenizer measurement.
  - Chunk size: 500 tokens, with 50 tokens of overlap between chunks
  - 20,000 queries per month
  - Each query retrieves the top 5 chunks
  - Each query has ~27 tokens of user text and a 150-token fixed system
    prompt/instructions block
  - Each answer is 300 output tokens

What this script does NOT model: prompt caching, batch-API discounts,
vector database hosting/storage cost, or embedding-query caching. It also
assumes every chunk is embedded exactly once (no re-embedding on edits).

Run:
    python rag_cost_example.py
"""

import json
import math
from pathlib import Path

# ---- assumptions (edit these to model a different pipeline) ----
NUM_DOCS = 500
WORDS_PER_DOC = 800
WORDS_PER_TOKEN = 0.75  # 1 token ~ 0.75 words, same heuristic used site-wide

CHUNK_SIZE_TOKENS = 500
CHUNK_OVERLAP_TOKENS = 50

MONTHLY_QUERIES = 20_000
CHUNKS_RETRIEVED_PER_QUERY = 5
QUERY_TEXT_TOKENS = 27
SYSTEM_PROMPT_TOKENS = 150
ANSWER_OUTPUT_TOKENS = 300

# Chat models compared for the generation step (must exist in pricing.json)
CHAT_MODELS_COMPARED = ["Claude Sonnet 5", "GPT-5.6 Terra", "Gemini 3.6 Flash"]
# Embedding models compared for the embedding steps
EMBEDDING_MODELS_COMPARED = [
    "OpenAI text-embedding-3-small",
    "OpenAI text-embedding-3-large",
    "Gemini embedding 001",
]

PRICING_PATH = Path(__file__).resolve().parent.parent / "pricing.json"


def num_chunks_for_doc(doc_tokens, chunk_size, overlap):
    """Same sliding-window chunking logic used across the site's examples."""
    if doc_tokens <= chunk_size:
        return 1
    step = chunk_size - overlap
    return math.ceil((doc_tokens - overlap) / step)


def main():
    pricing = json.loads(PRICING_PATH.read_text(encoding="utf-8"))
    chat_by_name = {m["name"]: m for m in pricing["chat"]}
    embed_by_name = {m["name"]: m for m in pricing["embedding"]}

    doc_tokens = WORDS_PER_DOC / WORDS_PER_TOKEN
    chunks_per_doc = num_chunks_for_doc(doc_tokens, CHUNK_SIZE_TOKENS, CHUNK_OVERLAP_TOKENS)
    total_chunks = chunks_per_doc * NUM_DOCS
    total_embedding_tokens_initial = total_chunks * CHUNK_SIZE_TOKENS

    monthly_query_embedding_tokens = MONTHLY_QUERIES * QUERY_TEXT_TOKENS

    input_tokens_per_query = (
        SYSTEM_PROMPT_TOKENS
        + QUERY_TEXT_TOKENS
        + CHUNKS_RETRIEVED_PER_QUERY * CHUNK_SIZE_TOKENS
    )
    monthly_generation_input_tokens = MONTHLY_QUERIES * input_tokens_per_query
    monthly_generation_output_tokens = MONTHLY_QUERIES * ANSWER_OUTPUT_TOKENS

    results = {
        "pricing_source": pricing["source_name"],
        "pricing_last_synced": pricing["last_synced"],
        "assumptions": {
            "num_docs": NUM_DOCS,
            "words_per_doc": WORDS_PER_DOC,
            "words_per_token": WORDS_PER_TOKEN,
            "chunk_size_tokens": CHUNK_SIZE_TOKENS,
            "chunk_overlap_tokens": CHUNK_OVERLAP_TOKENS,
            "monthly_queries": MONTHLY_QUERIES,
            "chunks_retrieved_per_query": CHUNKS_RETRIEVED_PER_QUERY,
            "query_text_tokens": QUERY_TEXT_TOKENS,
            "system_prompt_tokens": SYSTEM_PROMPT_TOKENS,
            "answer_output_tokens": ANSWER_OUTPUT_TOKENS,
        },
        "derived": {
            "doc_tokens": round(doc_tokens, 1),
            "chunks_per_doc": chunks_per_doc,
            "total_chunks": total_chunks,
            "total_embedding_tokens_initial": total_embedding_tokens_initial,
            "monthly_query_embedding_tokens": monthly_query_embedding_tokens,
            "input_tokens_per_query": input_tokens_per_query,
            "monthly_generation_input_tokens": monthly_generation_input_tokens,
            "monthly_generation_output_tokens": monthly_generation_output_tokens,
        },
        "embedding_cost_by_model": {},
        "generation_cost_by_model": {},
    }

    print(f"Pricing source: {pricing['source_name']} (last synced {pricing['last_synced']})")
    print()
    print(f"Doc tokens (est.): {doc_tokens:.1f} | chunks/doc: {chunks_per_doc} | total chunks: {total_chunks}")
    print(f"Initial embedding tokens: {total_embedding_tokens_initial:,}")
    print(f"Monthly query-embedding tokens: {monthly_query_embedding_tokens:,}")
    print(f"Input tokens/query (system+query+{CHUNKS_RETRIEVED_PER_QUERY} chunks): {input_tokens_per_query}")
    print(f"Monthly generation input tokens: {monthly_generation_input_tokens:,}")
    print(f"Monthly generation output tokens: {monthly_generation_output_tokens:,}")
    print()

    print(f"{'Embedding model':<32}{'$/1M':>8}{'Initial $':>14}{'Monthly query $':>18}")
    for name in EMBEDDING_MODELS_COMPARED:
        m = embed_by_name[name]
        rate = m["in"]
        initial_cost = total_embedding_tokens_initial / 1_000_000 * rate
        monthly_query_cost = monthly_query_embedding_tokens / 1_000_000 * rate
        results["embedding_cost_by_model"][name] = {
            "rate_per_million": rate,
            "initial_embedding_cost_usd": round(initial_cost, 4),
            "monthly_query_embedding_cost_usd": round(monthly_query_cost, 4),
        }
        print(f"{name:<32}{rate:>8.3f}{initial_cost:>14.2f}{monthly_query_cost:>18.4f}")
    print()

    print(f"{'Chat model':<20}{'$/1M in':>10}{'$/1M out':>10}{'Monthly input $':>18}{'Monthly output $':>18}{'Total $':>10}")
    for name in CHAT_MODELS_COMPARED:
        m = chat_by_name[name]
        in_rate, out_rate = m["in"], m["out"]
        input_cost = monthly_generation_input_tokens / 1_000_000 * in_rate
        output_cost = monthly_generation_output_tokens / 1_000_000 * out_rate
        total = input_cost + output_cost
        results["generation_cost_by_model"][name] = {
            "in_rate_per_million": in_rate,
            "out_rate_per_million": out_rate,
            "monthly_input_cost_usd": round(input_cost, 2),
            "monthly_output_cost_usd": round(output_cost, 2),
            "monthly_total_cost_usd": round(total, 2),
        }
        print(f"{name:<20}{in_rate:>10.2f}{out_rate:>10.2f}{input_cost:>18.2f}{output_cost:>18.2f}{total:>10.2f}")
    print()

    # combined monthly total = query embedding (cheapest embedding model) + generation
    cheapest_embed_name = min(
        EMBEDDING_MODELS_COMPARED,
        key=lambda n: results["embedding_cost_by_model"][n]["monthly_query_embedding_cost_usd"],
    )
    for chat_name in CHAT_MODELS_COMPARED:
        gen = results["generation_cost_by_model"][chat_name]
        embed_monthly = results["embedding_cost_by_model"][cheapest_embed_name]["monthly_query_embedding_cost_usd"]
        combined = gen["monthly_total_cost_usd"] + embed_monthly
        gen["combined_monthly_with_query_embedding_usd"] = round(combined, 2)
        print(f"{chat_name} + {cheapest_embed_name} query embedding: ${combined:.2f}/month recurring")

    out_path = Path(__file__).with_name("rag_cost_example.results.json")
    out_path.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
    print()
    print(f"Wrote {out_path}")


if __name__ == "__main__":
    main()
