================================================================================
      LANGGRAPH TECHNICAL EXPLANATION — FINSIM AKINATOR 2.0
      How the Multi-Agent Investment Prediction Engine Works
================================================================================

Written for: FinSim Project Technical Presentation
System: Akinator 2.0 — Multi-Agent Investment Prediction Engine
Framework: LangGraph (by LangChain) + Groq LLM + yfinance + SerpAPI
File: services/akinator.py


================================================================================
1. WHAT IS LANGGRAPH?
================================================================================

LangGraph is a framework built on top of LangChain for creating stateful,
multi-step AI workflows as directed graphs. Think of it as a flowchart where:

  - Each BOX in the flowchart is a "Node" (a Python function)
  - Each ARROW is an "Edge" (defines which node runs next)
  - A shared "State" object carries data between all nodes

Unlike a simple chain (A -> B -> C), LangGraph supports:
  - Conditional branching (if X, go to node A; else go to node B)
  - Cycles/loops (a node can route back to a previous node)
  - Shared persistent state across all nodes
  - Human-in-the-loop interruptions

LangGraph uses a "StateGraph" — a graph where every node reads from and
writes to a shared state dictionary. This is what makes our multi-agent
system possible: each analyst node adds its findings to the state, and
later nodes can access ALL previous findings.


================================================================================
2. OUR GRAPH ARCHITECTURE — 9 NODES
================================================================================

Here is the complete flow of our Akinator 2.0 system:

                         +----------+
                         |  START   |
                         +----+-----+
                              |
                              v
                      +-------+--------+
                      |   1. ROUTER    |  (classifies the user query)
                      +-------+--------+
                              |
                     _________+_________
                    |                   |
              [is_whatif?]         [regular query]
                    |                   |
                    v                   |
           +-------+--------+          |
           | 2. WHAT-IF     |          |
           |    ENGINE      |          |
           +-------+--------+          |
                    |                   |
                    +----->+<-----------+
                           |
                           v
                   +-------+--------+
                   | 3. ANALYST HUB |  (ReAct agent with 5 tools)
                   +-------+--------+
                           |
                           v
                 +---------+-----------+
                 | 4. NEWS SENTIMENT   |  (headline fetch + scoring)
                 +---------+-----------+
                           |
                           v
                +-----------+----------+
                | 5. CONFIDENCE SCORER |  (data-driven scoring)
                +-----------+----------+
                           |
                           v
                    +------+-------+
                    | 6. CRITIQUE  |  (self-correction loop)
                    +------+-------+
                           |
                           v
                 +---------+-----------+
                 | 7. JIT EDUCATION    |  (jargon detection)
                 +---------+-----------+
                           |
                           v
                 +---------+-----------+
                 | 8. MEMO GENERATOR   |  (professional summary)
                 +---------+-----------+
                           |
                           v
                 +---------+-----------+
                 | 9. FORMAT RESPONSE  |  (assemble output)
                 +---------+-----------+
                           |
                           v
                       +---+---+
                       |  END  |
                       +-------+


================================================================================
3. THE STATE SCHEMA — Data That Flows Through the Graph
================================================================================

Every node in the graph reads from and writes to a shared "State" dictionary.
Our state is defined as a Python TypedDict:

    class AkinatorState(TypedDict):
        messages: list              # Conversation history (HumanMessage/AIMessage)
        user_query: str             # The current user message
        analyst_report: str         # Main analysis from the ReAct agent
        news_sentiment: dict        # {headlines, score, label, topic}
        confidence_score: float     # 0-100 confidence percentage
        critique_note: str          # Self-correction note (if contradiction found)
        whatif_result: str           # What-if historical analysis result
        jargon_definitions: dict    # {term: plain_english_definition}
        final_memo: str             # Compiled investment memo
        panel_mode: bool            # Whether panel discussion mode is enabled
        is_whatif: bool             # Whether the query is a what-if scenario
        final_response: str         # The final combined response to the user

HOW STATE UPDATES WORK:
  - When a node returns {"confidence_score": 85}, only that field is updated
  - All other fields remain unchanged
  - This allows each node to focus on its own responsibility
  - By the end of the graph, the state contains data from ALL nodes


================================================================================
4. DETAILED NODE EXPLANATIONS
================================================================================

----- NODE 1: ROUTER -----
File: router_node()
Purpose: Classifies the user query to determine the graph path.

How it works:
  1. Takes the user's message from state["user_query"]
  2. Checks against a list of "what-if" patterns using regex:
     - "what if I had invested..."
     - "if I bought... 5 years ago"
     - "what would have happened..."
  3. Sets state["is_whatif"] = True or False

This node feeds into a CONDITIONAL EDGE — LangGraph's branching mechanism:
  - If is_whatif is True  -> next node is "whatif_engine"
  - If is_whatif is False -> next node is "analyst_hub" (skip what-if)

Code for conditional routing:
    graph.add_conditional_edges("router", route_after_router, {
        "whatif_engine": "whatif_engine",
        "analyst_hub": "analyst_hub",
    })


----- NODE 2: WHAT-IF ENGINE -----
File: whatif_engine_node()
Purpose: Historical scenario analysis — "what if I had invested $X in Y, Z years ago?"

How it works:
  1. Parses the query to extract: ticker, time period, investment amount
  2. Uses yfinance to download historical price data
  3. Calculates:
     - Buy price at the start date
     - Number of shares purchased
     - Current value of those shares
     - Total return (percentage)
     - Maximum drawdown during the holding period
     - Peak portfolio value
  4. Writes a formatted analysis to state["whatif_result"]

This data is then passed to the Analyst Hub, which incorporates it into
the LLM's analysis. The analyst is instructed to reference the What-If
data in its prediction.


----- NODE 3: ANALYST HUB (Core Intelligence) -----
File: analyst_hub_node()
Purpose: The main AI analysis engine — runs a ReAct agent with 5 expert tools.

This is the most complex node. It uses LangGraph's built-in ReAct agent
pattern (create_react_agent), which is itself a mini-graph:

  ReAct Loop (inside Analyst Hub):
    1. LLM receives the user query + conversation history
    2. LLM decides which tool(s) to call
    3. Tool is executed, result returned to LLM
    4. LLM decides: call another tool, or generate final answer?
    5. Repeat until LLM produces a final answer

The 5 tools available to the agent:
  a) market_data_analyst — Fetches live prices from yfinance (PE ratio,
     market cap, 52-week range, recent returns)
  b) news_sentiment_analyst — Searches SerpAPI for investment news
  c) risk_assessment_analyst — Calculates volatility, Sharpe ratio, max
     drawdown, risk level using 1 year of historical data
  d) portfolio_strategy_search — Searches for expert portfolio advice
  e) fetch_news_headlines — Gets dedicated news headlines for sentiment

The LLM (Groq's llama-3.3-70b-versatile) orchestrates which tools to call
and in what order. It typically calls ALL tools before synthesizing a report.

If Panel Mode is enabled, the system prompt includes instructions for the
LLM to simulate a 10-persona roundtable debate before making its prediction.


----- NODE 4: NEWS SENTIMENT -----
File: news_sentiment_node()
Purpose: Dedicated headline fetching and keyword-based sentiment scoring.

How it works:
  1. Extracts the main topic from the user query
  2. Fetches news headlines via SerpAPI's News tab
  3. Scores sentiment using keyword matching:
     - POSITIVE_WORDS: "surge", "rally", "growth", "bullish", etc.
     - NEGATIVE_WORDS: "crash", "decline", "bearish", "recession", etc.
  4. Calculates a score (0-100):
     - score = (positive_count / total_count) * 100
     - Label: Bullish (>=65), Bearish (<=35), Mixed (36-64), Neutral (no data)
  5. Writes to state["news_sentiment"] with headlines, score, and label

This is independent from the analyst hub's news tool — it runs AFTER the
analyst to provide a separate, quantitative sentiment signal that feeds
into the confidence scorer and critique nodes.


----- NODE 5: CONFIDENCE SCORER -----
File: confidence_scorer_node()
Purpose: Calculates a data-driven confidence percentage (0-95%).

Scoring algorithm:
  - Base score: 40%
  - Report substance (length > 500 chars): +15%
  - News data available: +15%
  - Strong sentiment direction: +5%
  - Specific numbers/prices in report: +3% each (up to +15%)
  - What-if analysis included: +10%
  - Each tool usage marker found: +5% each (up to +20%)
  - Cap: minimum 10%, maximum 95% (never claim 100% confidence)

This is a PURELY COMPUTATIONAL node — no LLM call needed. It analyzes
the data already in the state to produce a confidence metric.


----- NODE 6: CRITIQUE (Self-Correction Loop) -----
File: critique_node()
Purpose: Reviews the prediction against news sentiment for contradictions.

How it works:
  1. Detects prediction direction from the analyst report:
     - Counts "buy/bullish/upside" keywords vs "sell/bearish/downside"
     - Determines if prediction is bullish, bearish, or neutral
  2. Compares against news sentiment label:
     - If prediction is BULLISH but news is BEARISH -> issue correction
     - If prediction is BEARISH but news is BULLISH -> issue correction
  3. Scans headlines for high-impact events:
     - "fed", "interest rate", "recession", "war", "crisis", etc.
     - If found, issues an additional warning
  4. Writes correction to state["critique_note"]

This implements the "self-correction loop" requirement — the system
reviews its own output and flags potential issues. In the UI, this
appears as a yellow warning box.


----- NODE 7: JIT (Just-In-Time) EDUCATION -----
File: jit_education_node()
Purpose: Identifies financial jargon and provides plain-English definitions.

How it works:
  1. Maintains a dictionary of 30+ financial terms with simple definitions
     (P/E Ratio, Volatility, Sharpe Ratio, ETF, Stop-Loss, etc.)
  2. Scans the analyst report text for matches
  3. Collects all found terms and their definitions
  4. Writes to state["jargon_definitions"]

In the UI, these appear as an expandable "Financial Terms Explained" panel
at the bottom of the Akinator response. This helps students learn financial
terminology as they use the system.


----- NODE 8: MEMO GENERATOR -----
File: memo_generator_node()
Purpose: Compiles the entire session into a professional investment memo.

The memo includes:
  - Timestamp and AI model used
  - The original query
  - Confidence score with label (High/Moderate/Low)
  - News sentiment score and label
  - Self-correction note (if any)
  - Key headlines from news analysis
  - Number of educational terms identified
  - Data sources used
  - Disclaimer

In the UI, this appears as a collapsible "Investment Memo" section that
users can expand to see a structured summary of the entire analysis.


----- NODE 9: FORMAT RESPONSE -----
File: format_response_node()
Purpose: Assembles the final user-facing response text.

Combines:
  - The main analyst report
  - What-if analysis results (if applicable)

The confidence score, sentiment data, critique, jargon, and memo are
sent as separate JSON fields alongside the reply, so the frontend can
render them as distinct UI components (badges, panels, etc.).


================================================================================
5. HOW THE GRAPH IS BUILT AND COMPILED
================================================================================

The graph is built using LangGraph's StateGraph API:

    def build_akinator_graph():
        graph = StateGraph(AkinatorState)

        # Register all 9 nodes
        graph.add_node("router", router_node)
        graph.add_node("whatif_engine", whatif_engine_node)
        graph.add_node("analyst_hub", analyst_hub_node)
        graph.add_node("news_sentiment", news_sentiment_node)
        graph.add_node("confidence_scorer", confidence_scorer_node)
        graph.add_node("critique", critique_node)
        graph.add_node("jit_education", jit_education_node)
        graph.add_node("memo_generator", memo_generator_node)
        graph.add_node("format_response", format_response_node)

        # Wire the edges (the flow)
        graph.add_edge(START, "router")

        # Conditional branching after router
        graph.add_conditional_edges("router", route_after_router, {
            "whatif_engine": "whatif_engine",
            "analyst_hub": "analyst_hub",
        })

        # Sequential pipeline
        graph.add_edge("whatif_engine", "analyst_hub")
        graph.add_edge("analyst_hub", "news_sentiment")
        graph.add_edge("news_sentiment", "confidence_scorer")
        graph.add_edge("confidence_scorer", "critique")
        graph.add_edge("critique", "jit_education")
        graph.add_edge("jit_education", "memo_generator")
        graph.add_edge("memo_generator", "format_response")
        graph.add_edge("format_response", END)

        return graph.compile()

IMPORTANT: The graph is compiled ONCE at module load time:
    akinator_graph = build_akinator_graph()

This means the graph structure is created when the server starts and
reused for every request. Only the STATE changes per request.


================================================================================
6. EXECUTION FLOW — WHAT HAPPENS WHEN A USER ASKS A QUESTION
================================================================================

Example: User asks "Should I invest in AAPL right now?"

Step 1 — API Call:
  Frontend sends POST /api/akinator with the message.

Step 2 — State Initialization:
  get_akinator_response() creates the initial state:
    {
      messages: [previous chat history],
      user_query: "Should I invest in AAPL right now?",
      analyst_report: "",
      news_sentiment: {},
      confidence_score: 0,
      critique_note: "",
      whatif_result: "",
      jargon_definitions: {},
      final_memo: "",
      panel_mode: False,
      is_whatif: False,
      final_response: "",
    }

Step 3 — Graph Execution:
  akinator_graph.invoke(initial_state) runs all nodes in order:

  3a. ROUTER: Scans for "what if" -> is_whatif = False
  3b. Conditional: is_whatif is False -> skip to ANALYST HUB
  3c. ANALYST HUB: ReAct agent calls all 5 tools:
      - market_data_analyst("AAPL") -> price, PE, returns
      - news_sentiment_analyst("AAPL") -> latest news
      - risk_assessment_analyst("AAPL") -> volatility, Sharpe
      - portfolio_strategy_search("AAPL invest") -> expert advice
      - fetch_news_headlines("AAPL") -> headlines
      -> Synthesizes full prediction report
  3d. NEWS SENTIMENT: Fetches AAPL news, calculates score (72/100 = Bullish)
  3e. CONFIDENCE SCORER: Evaluates data -> 78% confidence
  3f. CRITIQUE: Prediction is bullish, news is bullish -> no contradiction
  3g. JIT EDUCATION: Found terms: P/E Ratio, Volatility, Market Cap, etc.
  3h. MEMO GENERATOR: Compiles full memo
  3i. FORMAT RESPONSE: Assembles final text

Step 4 — Response:
  Returns rich JSON: {reply, confidence_score, news_sentiment, jargon_definitions, memo, ...}

Step 5 — Frontend Rendering:
  - Main report rendered with markdown-to-HTML (marked.js)
  - Confidence badge (green/yellow/red pill)
  - Sentiment badge (bullish/bearish indicator)
  - Critique box (if contradiction found)
  - Expandable jargon definitions panel
  - Expandable investment memo


================================================================================
7. WHAT-IF SCENARIO FLOW
================================================================================

Example: "What if I had invested $10,000 in Tesla 3 years ago?"

The flow changes at the ROUTER node:

  1. ROUTER: Detects "what if" + "3 years ago" -> is_whatif = True
  2. WHAT-IF ENGINE: (runs BEFORE analyst hub)
     - Extracts: ticker=TSLA, years_ago=3, amount=$10,000
     - Downloads 3 years of TSLA price data
     - Calculates: buy_price, current_price, shares, return, drawdown
     - Writes formatted analysis to state["whatif_result"]
  3. ANALYST HUB: Receives the what-if data appended to the user query
     - Incorporates historical analysis into its prediction
     - Still calls all 5 tools for current market context
  4-9. Rest of pipeline runs normally, with the what-if data flowing through


================================================================================
8. THE REACT AGENT PATTERN (Inside Analyst Hub)
================================================================================

The Analyst Hub uses LangChain's ReAct (Reasoning + Acting) pattern:

    agent = create_react_agent(llm, tools, prompt=system_prompt)

This creates a mini-graph inside the node:

    +--------+     +----------+     +--------+
    |  LLM   | --> | Tool     | --> |  LLM   | --> (repeat or finish)
    | Reason |     | Execute  |     | Reason |
    +--------+     +----------+     +--------+

The LLM "reasons" about what it needs, "acts" by calling a tool, then
"reasons" again based on the tool's output. This loop continues until
the LLM decides it has enough information to give a final answer.

In our system, the LLM typically:
  1. Calls market_data_analyst first (get hard numbers)
  2. Calls news_sentiment_analyst (get context)
  3. Calls risk_assessment_analyst (evaluate risk)
  4. Calls portfolio_strategy_search (get advice)
  5. Produces final synthesis

The LLM autonomously decides the order and which tools to use.


================================================================================
9. SESSION MANAGEMENT
================================================================================

Sessions are stored in memory:
    _akinator_sessions: dict[str, list] = {}

  - Key: session_id (generated by frontend)
  - Value: list of HumanMessage/AIMessage objects

After each graph execution:
  1. User message is appended to history
  2. AI response is appended to history
  3. History is trimmed to last 10 messages (to prevent token overflow)

This allows multi-turn conversations where the AI remembers context.


================================================================================
10. ERROR HANDLING — RATE LIMITS
================================================================================

Groq's free API has rate limits (tokens per minute/day). When exceeded:

  1. The exception is caught in get_akinator_response()
  2. _format_rate_limit_error() parses the error message
  3. Extracts the wait time from patterns like "try again in 45.5s"
  4. Returns {"rate_limited": True, "wait_seconds": 46}
  5. Frontend shows a countdown timer
  6. ONLY real rate-limit errors trigger this (not generic "tokens" errors)


================================================================================
11. TECHNOLOGY STACK SUMMARY
================================================================================

  Backend:
    - FastAPI (Python web framework)
    - LangGraph (graph-based AI orchestration)
    - LangChain (LLM integration framework)
    - Groq API (LLM provider — llama-3.3-70b-versatile)
    - yfinance (real-time stock market data)
    - SerpAPI (web search and news headlines)
    - MySQL (scenario database)

  Frontend:
    - Vanilla HTML/CSS/JavaScript
    - marked.js (markdown rendering)
    - Responsive dark-mode design

  Key Design Decisions:
    - StateGraph over simple chains: enables conditional branching and
      shared state across nodes
    - ReAct agent for tool calling: LLM autonomously decides which
      tools to use and in what order
    - Computational nodes (no LLM): Confidence, JIT, Critique, and
      Memo nodes use Python logic instead of LLM calls to minimize
      API usage and avoid rate limits
    - Rich JSON responses: Frontend receives structured data to render
      confidence badges, sentiment indicators, jargon panels, etc.


================================================================================
12. FILES MODIFIED / CREATED
================================================================================

  services/akinator.py    — Complete rewrite with LangGraph StateGraph
  app.py                  — Updated /api/akinator endpoint for rich responses
  static/index.html       — Markdown rendering + metadata UI panels
  LangGraph_Explanation.txt — This file (technical documentation)


================================================================================
END OF DOCUMENT
================================================================================
