File size: 6,453 Bytes
3616a04
406fad4
36a9daf
d9a5304
2cbe35b
0a2ca25
87bc35c
406fad4
 
fe5f7bc
0a2ca25
87bc35c
2cbe35b
fe5f7bc
 
 
 
 
 
 
 
 
8390921
b06b2f5
3616a04
63ecac2
 
2cbe35b
3616a04
63ecac2
3616a04
 
 
 
 
 
 
a690153
63ecac2
d081699
 
63ecac2
d081699
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bad375
d081699
3616a04
a690153
 
 
3616a04
 
 
a690153
 
 
 
3616a04
 
 
 
1e16f51
b890a05
a690153
d081699
 
 
446ec7d
7155194
1e16f51
135137f
625aab1
d6338e5
 
 
1e16f51
a690153
1e16f51
bea36fa
1e16f51
a690153
bea36fa
d081699
 
63ecac2
 
 
a690153
b890a05
6c1639c
a690153
2cbe35b
3616a04
1e16f51
63ecac2
d081699
1e16f51
d081699
3616a04
1e16f51
d081699
0a2ca25
d081699
 
0a2ca25
 
3616a04
cefe909
1e16f51
a690153
d081699
 
cefe909
a690153
1e16f51
3616a04
 
1e16f51
b890a05
3616a04
 
 
d081699
3616a04
a0c903a
3616a04
 
 
d081699
0a2ca25
d081699
 
 
2cbe35b
d081699
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import os
import nltk
import requests
import datetime

# Use a directory within the user's home directory
nltk_data_dir = os.path.expanduser("~/.nltk_data")
os.makedirs(nltk_data_dir, exist_ok=True)
nltk.data.path.append(nltk_data_dir)

# Download NLTK data
nltk.download('punkt', download_dir=nltk_data_dir, quiet=True)

import chainlit as cl
from llama_index.core import VectorStoreIndex, Document
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.groq import Groq
from llama_index.core import ServiceContext
from llama_index.core.node_parser import SentenceSplitter
from dotenv import load_dotenv
import yfinance as yf
import pandas as pd

load_dotenv()

GROQ_API_KEY = os.getenv("GROQ_API_KEY")
FMP_API_KEY = os.getenv("FMP_API_KEY")

embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
llm = Groq(model="llama3-70b-8192", api_key=GROQ_API_KEY)

service_context = ServiceContext.from_defaults(
    llm=llm,
    embed_model=embed_model,
    node_parser=SentenceSplitter(chunk_size=1000, chunk_overlap=200)
)

def fetch_annual_report_10k(symbol: str) -> str:
    """
    Tries up to 5 years (current year backward) to find a 10-K.
    Returns the raw text of the first successful result.
    """
    current_year = datetime.datetime.now().year

    # We'll attempt up to 5 years back
    for year_try in range(current_year, current_year - 5, -1):
        url = (
            "https://financialmodelingprep.com/api/v4/financial-reports-json"
            f"?symbol={symbol}&year={year_try}&period=FY&apikey={FMP_API_KEY}"
        )
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            text_data = response.text

            # If FMP returns an error message inside the JSON, skip and try the next year
            if "Error Message" in text_data or len(text_data.strip()) < 10:
                continue

            # If we got meaningful data, return it immediately
            return text_data

        except requests.exceptions.RequestException:
            # On request error or no data, try older year
            pass

    # If we exit the loop, no data was found for any year in that range
    return (
        f"No 10-K data found for {symbol} in the last 5 years "
        "(or API returned an error)."
    )

summary_prompt = (
    "You are a world-class financial analyst with extensive experience analyzing annual reports. "
    "Provide a comprehensive summary of the 10-K report. Focus on Strategic Insights, Key Financial Figures, and Risk Factors. "
    "Answer in extensive bullet points, summarizing the company's performance, strengths, and weaknesses."
)

question_prompt = (
    "You are a financial analyst with extensive experience analyzing annual reports. "
    "Read the 10-K report and generate 10 strategic questions focusing on the company's performance, risks, and financial figures. "
    "Ask questions that provide strategic insights into the company's long-term goals, revenue trends, competitive position, and more. "
    "Format the questions as a numbered list (e.g., '1. Question')."
)

@cl.on_chat_start
async def on_chat_start():
    ticker_response = await cl.AskUserMessage(
        content=(
            "This tool is designed to analyze 10-K annual reports for publicly traded companies. "
            "Provide the company's ticker symbol, and the tool will fetch the latest available 10-K report "
            "from the last few years. It generates summaries and strategic due diligence. "
            "Ask your own questions afterwards.\n\n"
            "Please enter the ticker symbol for the company you want to analyze (e.g. MSFT):"
        )
    ).send()

    if not ticker_response or 'content' not in ticker_response:
        await cl.Message(content="No ticker symbol provided. Please enter a valid ticker symbol to proceed.").send()
        return

    ticker_symbol = ticker_response['content'].upper()
    msg = cl.Message(content=f"Retrieving the latest 10-K report for {ticker_symbol}...")
    await msg.send()

    try:
        annual_report_text = fetch_annual_report_10k(ticker_symbol)

        # Check if we failed for all years
        if annual_report_text.startswith("No 10-K data found") or \
           annual_report_text.startswith("HTTP error") or \
           annual_report_text.startswith("Request error") or \
           annual_report_text.startswith("An unexpected error occurred"):
            await cl.Message(content=annual_report_text).send()
            return

        document = Document(text=annual_report_text, metadata={"company": ticker_symbol})
        index = VectorStoreIndex.from_documents([document], service_context=service_context)

        cl.user_session.set("index", index)
        query_engine = index.as_query_engine()

        summary_response = await cl.make_async(query_engine.query)(summary_prompt)
        await cl.Message(content=f"**Summary:**\n{summary_response}").send()

        questions_response = await cl.make_async(query_engine.query)(question_prompt)
        questions_format = str(questions_response).split('\n')
        relevant_questions = [
            question.strip()
            for question in questions_format
            if question.strip() and question.strip()[0].isdigit()
        ]

        await cl.Message(content="Generated strategic questions and answers:").send()
        for question in relevant_questions:
            await cl.Message(content=f"**{question}**").send()
            answer = await cl.make_async(query_engine.query)(question)
            await cl.Message(content=f"**Answer:**\n{answer}").send()

        msg.content = "Processing done. You can now ask more questions about the 10-K report!"
        await msg.update()

    except Exception as e:
        await cl.Message(content=f"An error occurred during processing: {str(e)}").send()

@cl.on_message
async def main(message: cl.Message):
    index = cl.user_session.get("index")
    
    if index is None:
        await cl.Message(content="Please provide a ticker symbol first before asking questions.").send()
        return

    query_engine = index.as_query_engine()
    response = await cl.make_async(query_engine.query)(message.content)

    response_message = cl.Message(content="")
    for token in str(response):
        await response_message.stream_token(token=token)

    await response_message.send()