bhanug2026 commited on
Commit
e31aacb
·
verified ·
1 Parent(s): ee490fb

Upload 10 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ╔══════════════════════════════════════════════════════════════════╗
2
+ # ║ BubbleBusters — Hugging Face Space Dockerfile ║
3
+ # ║ Uses pre-built R binaries via Posit Package Manager ║
4
+ # ║ → R package install goes from ~30 min to ~60 sec ║
5
+ # ╚══════════════════════════════════════════════════════════════════╝
6
+
7
+ FROM python:3.11-slim
8
+
9
+ # ── 1. System packages + R ──────────────────────────────────────────
10
+ # We add the CRAN apt repo so `apt-get install r-base` gives us a
11
+ # recent R (4.x) without needing to compile it either.
12
+ RUN apt-get update && apt-get install -y --no-install-recommends \
13
+ gnupg2 \
14
+ curl \
15
+ ca-certificates \
16
+ && curl -fsSL https://cloud.r-project.org/bin/linux/debian/marutter_pubkey.asc \
17
+ | gpg --dearmor -o /usr/share/keyrings/r-project.gpg \
18
+ && echo "deb [signed-by=/usr/share/keyrings/r-project.gpg] \
19
+ https://cloud.r-project.org/bin/linux/debian bookworm-cran40/" \
20
+ > /etc/apt/sources.list.d/r-project.list \
21
+ && apt-get update && apt-get install -y --no-install-recommends \
22
+ r-base \
23
+ libcurl4-openssl-dev \
24
+ libssl-dev \
25
+ libxml2-dev \
26
+ && rm -rf /var/lib/apt/lists/*
27
+
28
+ # ── 2. R packages — binary install via Posit Package Manager ────────
29
+ # PPM serves pre-built binaries for Linux; no compilation needed.
30
+ RUN Rscript -e "\
31
+ options(repos = c(CRAN = 'https://packagemanager.posit.co/cran/__linux__/bookworm/latest')); \
32
+ install.packages(c( \
33
+ 'vctrs', 'rlang', 'pillar', \
34
+ 'readr', 'dplyr', 'tidyr', 'lubridate', \
35
+ 'ggplot2', 'MASS', 'zoo', 'jsonlite', 'scales' \
36
+ ), Ncpus = 2)"
37
+
38
+ # ── 3. IRkernel ──────────────────────────────────────────────────────
39
+ RUN Rscript -e "\
40
+ options(repos = c(CRAN = 'https://packagemanager.posit.co/cran/__linux__/bookworm/latest')); \
41
+ install.packages('IRkernel')" \
42
+ && Rscript -e "IRkernel::installspec(user = FALSE)"
43
+
44
+ # ── 4. Non-root user (Hugging Face Spaces requirement) ──────────────
45
+ RUN useradd -m -u 1000 user
46
+ USER user
47
+
48
+ ENV HOME=/home/user \
49
+ PATH=/home/user/.local/bin:$PATH
50
+
51
+ WORKDIR /home/user/app
52
+
53
+ # ── 5. Python dependencies ──────────────────────────────────────────
54
+ COPY --chown=user requirements.txt .
55
+ RUN pip install --no-cache-dir --upgrade pip \
56
+ && pip install --no-cache-dir -r requirements.txt
57
+
58
+ # ── 6. App source ───────────────────────────────────────────────────
59
+ COPY --chown=user . .
60
+
61
+ # ── 7. Gradio networking ─────────────────────────────────────────────
62
+ ENV GRADIO_SERVER_NAME="0.0.0.0" \
63
+ GRADIO_SERVER_PORT=7860
64
+
65
+ EXPOSE 7860
66
+
67
+ CMD ["python", "app.py"]
ai_bubble_clean.csv ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Date,Platform,Topic,Sentiment,Comment,Source_Link,YearMonth,Year,SentScore
2
+ 2024-01-15,HackerNews,hype,neutral,On the 'AI is just a bubble' side we have: AI is not as good as claimed therefore its impact is not as significant as advertised - ergo it is a passing fad & market caps are inflated. On the 'AI is legit' side we have the counter: AI is as at least nearly as good as claimed and/or has potential to be - ergo the hype is warranted & it's here to stay & market caps are warranted,https://news.ycombinator.com/item?id=38925301,2024-01-01,2024,0
3
+ 2024-04-03,HackerNews,investment,bullish,"If you look at total software spend in the US a year it's 500B, total spend on payroll is 5T. If companies become 10% more efficient because of GenAI you're effectively doubling the size of current software market. Actual value capture would obviously be lower, but 10% efficiency gains is a low estimate based on the studies coming out. There's a ton of terrible startups right now, but some of them will become whales.",https://news.ycombinator.com/item?id=39924053,2024-04-01,2024,1
4
+ 2024-04-03,HackerNews,skepticism,bearish,I haven't seen LLMs do anything more useful than generating a wedding speech in the style of H.P. Lovecraft and there are people out there earnestly predicting this technology will increase efficiency 10% across entire economies. I genuinely feel like I'm living in a different world to you all.,https://news.ycombinator.com/item?id=39924889,2024-04-01,2024,-1
5
+ 2024-04-04,HackerNews,productivity,bearish,I have yet to see an example of Copilot do something that doesn't seem like basic autocomplete/snippets that editors and IDEs have been doing for decades or code so basic that it betrays the lack of competence by the user.,https://news.ycombinator.com/item?id=39924920,2024-04-01,2024,-1
6
+ 2024-04-04,HackerNews,productivity,neutral,"I find the Copilot completions to be slightly better than IDE auto complete, but the latency is too high for it to be useful IMO. It's too big a break in concentration to wait for it to type almost what you were about to. IMO chat-based question-asking when encountering unfamiliar language constructs or framework idioms is where LLMs shine in coding assistance.",https://news.ycombinator.com/item?id=39925162,2024-04-01,2024,0
7
+ 2024-04-04,HackerNews,productivity,bullish,It can write code. I can assure you that. Still need a developer but it can make a developer incredibly more efficient much much more than 10%.,https://news.ycombinator.com/item?id=39924916,2024-04-01,2024,1
8
+ 2024-04-03,HackerNews,hype,bearish,"I know a company that claims to do AI. Their models didn't work so they ended hiring humans to manually do the job AI was supposed to do. Obviously that won't scale, but they still call themselves an AI company.",https://news.ycombinator.com/item?id=39923655,2024-04-01,2024,-1
9
+ 2024-04-03,HackerNews,hype,bearish,AI is just a marketing term for unproven technology to generate hype. Once something is proven and use cases/limitations understood its no longer AI. Spellcheck was once considered AI.,https://news.ycombinator.com/item?id=39923436,2024-04-01,2024,-1
10
+ 2024-04-04,HackerNews,investment,bearish,"To be fair, a lot of software spending in Enterprise is what I would like to call Bullshit SaaS. Stuff that was subscribed to by some PM once upon a time, but then later sat unused, even as the company gets billed on it annually or monthly. Not to mention a lot of SaaS simply buying and using each other's products, fully funded by VC capital (which is the YC model).",https://news.ycombinator.com/item?id=39925320,2024-04-01,2024,-1
11
+ 2024-04-05,HackerNews,productivity,bearish,I've tried all of the above. I've asked it to summarize information about what languages in a class had strong symmetric multiprocessing characteristics and it made up over half of the details. I've asked it to generate code for a simple linked list and it created garbage. I've found it to be nearly useless for anything where a semblance of correctness matters.,https://news.ycombinator.com/item?id=39972568,2024-04-01,2024,-1
12
+ 2024-04-04,HackerNews,productivity,bearish,"I find the Copilot completions to be slightly better than IDE auto complete, but the latency is too high. It gets decent/usable results like 40% of the time, but latency kills it. I've found chat-based questioning to be unproductive. It gets details wrong frequently enough that I can't trust the output which effectively doubles the cognitive effort because you have to validate against a more reliable source.",https://news.ycombinator.com/item?id=39930975,2024-04-01,2024,-1
13
+ 2024-04-04,HackerNews,productivity,bullish,The auto complete is much more effective than what IDE has... Even if you type at 140wpm it is effective.,https://news.ycombinator.com/item?id=39925131,2024-04-01,2024,1
14
+ 2024-04-04,HackerNews,productivity,bullish,"In some cases it will give me the exact 10 lines of code I wanted to write myself with few seconds. That is significant. Also it never makes typoes or rarely index+1 errors, things like that. It is much less mental fatigue compared to if I have to make sure I did those things correctly myself.",https://news.ycombinator.com/item?id=39925512,2024-04-01,2024,1
15
+ 2024-04-03,HackerNews,hype,bearish,I've read somewhere that AGI = A Guy in India.,https://news.ycombinator.com/item?id=39924897,2024-04-01,2024,-1
16
+ 2024-04-03,HackerNews,hype,bearish,Its just 'human in the loop' AI as long as you don't reveal the humans are the whole loop.,https://news.ycombinator.com/item?id=39923885,2024-04-01,2024,-1
17
+ 2025-01-20,HackerNews,hype,neutral,"I think a better title would be 'Big Tech is struggling to add real capabilities and improve productivity despite slapping LLM models in every one of their existing apps'. For the bubble to burst, investors would need to stop believing in the potential of AI in the near-to-medium term. I don't think we're quite there yet.",https://news.ycombinator.com/item?id=42770948,2025-01-01,2025,0
18
+ 2025-01-20,HackerNews,hype,bearish,There's a tremendous amount of useful things you can do with Transformers and diffusion models. It's kind of fascinating how businesses are not able to turn those straight forward things into businesses and instead think that they should run commercials insisting people will want to have an AI write heartfelt thankyou letters or do creative tasks for them.,https://news.ycombinator.com/item?id=42771103,2025-01-01,2025,-1
19
+ 2025-01-20,HackerNews,productivity,bearish,"I wonder if it's because it's hard to do most things with LLMs in a reliable way when not supervised. As programmers we complain about the ~1% from copilot-type models where the code is terrible. A 1% error rate for many things, and with no bounds of how terrible the hallucinated error is, perhaps that's unworkable.",https://news.ycombinator.com/item?id=42771336,2025-01-01,2025,-1
20
+ 2025-01-20,HackerNews,skepticism,bearish,"Just thinking of it. 1% error rate, say 1 in 100 customers gets some wrong information. And they go to place trusting it. Just to hear that AI lied to them. Say you have 1000 or 10000 customers using system, now you potentially have 10 or 100 one star negative reviews.",https://news.ycombinator.com/item?id=42773276,2025-01-01,2025,-1
21
+ 2025-01-21,HackerNews,skepticism,bearish,"Even for internal use. Our corp approved a small project where NN will do the analysis of nightly test runs. For now it does classification of results into several existing broad categories. But since even 1% false rate (it is actually in double digits in real life) would mean that we QAs need to verify all results anyway. So no time saved, and this NN software is useless.",https://news.ycombinator.com/item?id=42781310,2025-01-01,2025,-1
22
+ 2025-01-21,HackerNews,skepticism,bearish,"A 1% error rate just won't fly in most use cases. You're really talking about stuff which doesn't matter at all (people are rarely willing to pay very much for this) or where its output is guaranteed to be reviewed by a human expert (at which point, in many cases, well, why bother).",https://news.ycombinator.com/item?id=42778381,2025-01-01,2025,-1
23
+ 2025-01-20,HackerNews,hype,bearish,"Good read - I use ChatGPT almost every day but a lot of my friends only tried it when it came out, weren't impressed and haven't been back since. I don't know any non-tech friends who pay for it.",https://news.ycombinator.com/item?id=42770952,2025-01-01,2025,-1
24
+ 2025-01-20,HackerNews,skepticism,bearish,"Non-tech friends don't have coding problems, which is the only problem LLMs are ok at.",https://news.ycombinator.com/item?id=42771122,2025-01-01,2025,-1
25
+ 2025-01-20,HackerNews,hype,bearish,Companies which have already invested a gazillion dollars into AI aren't going to entertain the idea that users simply don't want AI at all.,https://news.ycombinator.com/item?id=42771355,2025-01-01,2025,-1
26
+ 2025-01-20,HackerNews,hype,bearish,"Companies that haven't invested a gazillion dollars ought to entertain that idea, though...",https://news.ycombinator.com/item?id=42771679,2025-01-01,2025,-1
27
+ 2025-01-21,HackerNews,productivity,bullish,"In the real world, judges I know are using it to do case summaries that used to take weeks, Goldman is using it to do 95% of IPO filings work and I personally am using O1 pro to write a ton of code. AI's biggest use cases are for doing actual work, not necessarily replacing regular interactions with your mobile or entertainment devices.",https://news.ycombinator.com/item?id=42777015,2025-01-01,2025,1
28
+ 2025-01-20,HackerNews,hype,neutral,I don't know if the bubble framing is particularly helpful. Every new technology and medium goes through a period where people try to unsuccessfully apply it to old paradigms before discovering the new ones that make it shine. Motion picture cameras seemed like a goofy fad for decades before people finally understood the unique potentials of the medium.,https://news.ycombinator.com/item?id=42771643,2025-01-01,2025,0
29
+ 2025-01-21,HackerNews,skepticism,bearish,"Research didn't end after the last six or so AI bubbles collapsed, either; this pattern goes back to the 1960s. The bubble referenced is the financial/economic bubble which currently exists around AI. 'People refuse to pay for the Big New Thing, we must force it upon them' is usually a sign that the end is nigh.",https://news.ycombinator.com/item?id=42778365,2025-01-01,2025,-1
30
+ 2025-01-20,HackerNews,hype,bearish,The free AI in my phone isn't good enough to even improve dictation recognition rate - it's as bad as it was 2 years ago. Real useful AI costs money and costs privacy (can't run it locally on the phone).,https://news.ycombinator.com/item?id=42771200,2025-01-01,2025,-1
31
+ 2025-01-20,HackerNews,investment,neutral,I paid 20/month for a few months with ChatGPT but stopped because you could get basically the same for free. If there were no free options I might pay but when free versions are pretty much forced upon you there's not much compulsion.,https://news.ycombinator.com/item?id=42772312,2025-01-01,2025,0
32
+ 2025-01-21,HackerNews,hype,neutral,"I think the main issue is that the average consumer does not know what to do with a raw transformer model. While the base technology is now there and is rapidly improving, a lot of the 'glue' and 'plumbing' is still missing. What is the best way to integrate these tools into our normal workflows?",https://news.ycombinator.com/item?id=42777616,2025-01-01,2025,0
33
+ 2025-01-20,HackerNews,skepticism,bearish,"If I were a small business, I would not be excited about having a magic robot 'hallucinate' things about my business. Like, this is the company that brought you glue on pizza.",https://news.ycombinator.com/item?id=42778389,2025-01-01,2025,-1
34
+ 2025-01-20,HackerNews,skepticism,bearish,"The level of contempt big tech has for users really is something. Google kept pointing me to when Gemini was a per-user subscription, not a UI nightmare that they decided to force everyone to use. These are paying customers. This isn't a case of 'you're the product.'",https://news.ycombinator.com/item?id=42770994,2025-01-01,2025,-1
35
+ 2025-01-20,HackerNews,hype,bearish,Perhaps they want the real alternative: no AI.,https://news.ycombinator.com/item?id=42771331,2025-01-01,2025,-1
36
+ 2024-11-26,HackerNews,productivity,bullish,"I don't know how to precisely quantify the value I get, but Im happily paying $20/mo for OpenAI tools. I'll never again 'read documentation' directly nor will I sift through useless SEO optimized bog-spam to learn about a new hot thing. Between these 2 activities, thats pretty much all I use the internet for and AI has become a core part of my life/work process.",https://news.ycombinator.com/item?id=42241633,2024-11-01,2024,1
37
+ 2024-11-26,HackerNews,productivity,bullish,Having an infinitely patient expert (who's occasionally fuzzy on the details) summarize absolutely any topic and answer questions about it is so overwhelmingly valuable to me it's a little nuts.,https://news.ycombinator.com/item?id=42243160,2024-11-01,2024,1
38
+ 2024-12-02,HackerNews,productivity,bullish,"For me, even having an infinitely patient intern who is quite often wrong is useful. Even when they're only acting as competently as a university student, they're doing that at dozens of subjects which I stopped doing at 16. Back in 2004-2005, I was an intern; the best LLMs today would be similarly valuable despite the much lower cost.",https://news.ycombinator.com/item?id=42296461,2024-12-01,2024,1
39
+ 2024-11-26,HackerNews,investment,neutral,"With the intensive energy and compute requirements, does $20 yield a profit for OpenAI, even cover the costs of an active user? How much would you be willing to pay for this service as it is now?",https://news.ycombinator.com/item?id=42242721,2024-11-01,2024,0
40
+ 2024-11-26,HackerNews,investment,bullish,I would pay $300 a month for the time it saves me.,https://news.ycombinator.com/item?id=42242853,2024-11-01,2024,1
41
+ 2024-11-26,HackerNews,productivity,neutral,"I was skeptical at first, still am somewhat. However, I've been using OpenAI's app and its new search engine feature I can't see myself going back to traditional search engines. I use copilot with Claude for software development, I cannot see myself going back to traditional autocomplete. However, I do not see how else AI will revolutionize anything else.",https://news.ycombinator.com/item?id=42241431,2024-11-01,2024,0
42
+ 2024-11-26,HackerNews,skepticism,bearish,AI is really bad at law right now. Hallucinated legal citations build legal arguments on a foundation of mud. No lawyer should be using chat gpt or any of the common public models.,https://news.ycombinator.com/item?id=42242244,2024-11-01,2024,-1
43
+ 2024-11-26,HackerNews,hype,bullish,"AI hallucinates with everything, coding in particular as well. By your rationale no developer should be using AI either but you will soon be unable to keep your employment without it. I have already used AI for legal things that would have cost me thousands of dollars and lawyers are using LLMs daily.",https://news.ycombinator.com/item?id=42251138,2024-11-01,2024,1
44
+ 2024-11-26,HackerNews,hype,bullish,It has growing influence in computational chemistry where heuristic approaches can give massive speedups. Mixing AI and theorem proofing together is going to be big. Increasing use in customer service will be massive annoyance and great business success.,https://news.ycombinator.com/item?id=42241520,2024-11-01,2024,1
45
+ 2024-11-26,HackerNews,hype,bullish,"In the 1980's, managers at major corporations doubted and sometimes actively fought the use of personal computers by their professional employees. AI is just the next disruptive phase of this technology timeline. Will the stock market bubble burst? Maybe... I am certainly paying attention.",https://news.ycombinator.com/item?id=42242340,2024-11-01,2024,1
46
+ 2024-11-26,HackerNews,hype,neutral,"The AI to look at is Waymo's computer driven cars. They're here, they work, but Uber and Lyft aren't going away in the cities that Waymo drives in. The same goes for everything an LLM can do today. The systemic effects of where they're at today are going to take a while to iron out.",https://news.ycombinator.com/item?id=42241835,2024-11-01,2024,0
47
+ 2025-07-27,HackerNews,investment,neutral,"I like Claude Code. I think it's the best thing that's happened to software development in a long time, actually. But I'd hardly call that a transformational technology ushering in a new era of civilization. It took decades for electricity to transform the way we live. I suspect it will be the same for AI.",https://news.ycombinator.com/item?id=44682658,2025-07-01,2025,0
48
+ 2025-07-27,HackerNews,hype,bullish,"AI usage is real and is building on Smartphone platform as well as PC. Even PC, Internet and Smartphone all three in their era didn't get as quickly adopted as AI. CoPilot literally widely deployed from Government to Fortune 500. I dont believe in AGI or ASI. But I also dont think we are currently in an AI bubble.",https://news.ycombinator.com/item?id=44688384,2025-07-01,2025,1
49
+ 2025-07-27,HackerNews,investment,neutral,There is no 'AI' bubble as far as the public market besides Nvidia and TSLA which has always been a meme stock. The values of none of the BigTech companies have seen their values rise because of AI. Their fundamental business value and revenues are mostly the same sources. The bubble bursting will affect VC and private equity and mostly private companies.,https://news.ycombinator.com/item?id=44684409,2025-07-01,2025,0
50
+ 2025-07-27,HackerNews,productivity,bullish,"I don't think AI is as big of a bubble as you think. The hype is very high, but non tech companies are starting to pay gobs of money in monthly subscriptions to get their employees to be a bit more productive. If you can get a six figure worker to be 20% more productive for a grand a month, you're going to do that. That's a huge market!",https://news.ycombinator.com/item?id=44686181,2025-07-01,2025,1
51
+ 2025-07-27,HackerNews,investment,neutral,S&P 500 as always. Timing the market is a fool's errand.,https://news.ycombinator.com/item?id=44683026,2025-07-01,2025,0
52
+ 2025-07-27,HackerNews,investment,bearish,I am both bearish on AI and don't see the bubble popping soon. People are too invested. Some of the most powerful people in Silicon Valley have staked their reputations in it. The freaking president of the United States is all in. The market can stay irrational longer than you can stay solvent.,https://news.ycombinator.com/item?id=44682687,2025-07-01,2025,-1
53
+ 2025-07-27,HackerNews,hype,bearish,"The AI bubble won't pop because too many CEOs have discovered that it's a convenient fig leaf for mass firings, which is then laundered into a broader narrative about 'operational efficiency'. Statements like '30% of our code is now written by AI' cannot be audited so leaders can't really be called on the veracity.",https://news.ycombinator.com/item?id=44685756,2025-07-01,2025,-1
54
+ 2025-07-27,HackerNews,investment,neutral,People have short memories. Their reputations will be fine.,https://news.ycombinator.com/item?id=44683216,2025-07-01,2025,0
55
+ 2025-07-27,HackerNews,investment,bullish,"A likely catalyst would be a DeepSeek like model where it has the capability of top model but in a much smaller footprint. But overall, AI is here to stay even if the market crashes, so it's not really a AI bubble pop, more like a GPU pop.",https://news.ycombinator.com/item?id=44685254,2025-07-01,2025,1
56
+ 2025-07-27,HackerNews,investment,bullish,"Even if AI pops, I do not think it is going away; the question is only how long it takes to recover. Additionally, robots are on the horizon. I am still very bullish on tech and honestly think it is foolish to be bearish on tech.",https://news.ycombinator.com/item?id=44683738,2025-07-01,2025,1
57
+ 2025-07-27,HackerNews,hype,bullish,"It's definitely a bubble, but it's like the Dotcom or housing bubble. Just because it bursts doesn't mean it's going away, just means it was overmarketed and PR'd. Companies spent billions on foundational models and the hype was necessary for their B2B sales.",https://news.ycombinator.com/item?id=44683893,2025-07-01,2025,1
58
+ 2025-07-27,HackerNews,investment,bullish,AI hardware is not a bubble. We're not even close to getting capable AI hardware out to every man woman and child at scale. NVDIA was $90 earlier this year. Overthink at your own peril.,https://news.ycombinator.com/item?id=44683358,2025-07-01,2025,1
59
+ 2025-07-27,HackerNews,investment,bearish,"My backup plan --- don't backup at all. Instead, short the stocks of overvalued AI players and ride them on the way down to something closer to reality.",https://news.ycombinator.com/item?id=44682632,2025-07-01,2025,-1
60
+ 2025-07-27,HackerNews,investment,bearish,How about all of them at once? Just short the NASDAQ 100. See SQQQ.,https://news.ycombinator.com/item?id=44683414,2025-07-01,2025,-1
61
+ 2025-07-27,HackerNews,investment,bearish,The real extreme frauds aren't traded on the major public exchanges. They are mostly into venture capital and private investment. But any AI meltdown will hurt the major public companies too. NVDA being probably the largest with the most to lose.,https://news.ycombinator.com/item?id=44683844,2025-07-01,2025,-1
62
+ 2025-07-27,HackerNews,investment,neutral,As long as NVDA's forward projections are bullish there is not going to be a pop.,https://news.ycombinator.com/item?id=44683671,2025-07-01,2025,0
63
+ 2025-07-27,HackerNews,investment,bearish,I don't think OpenAI still has a lead in LLMs so a disappointing GPT-5 will at maximum burst openai's bubble.,https://news.ycombinator.com/item?id=44682727,2025-07-01,2025,-1
64
+ 2025-07-27,HackerNews,investment,bearish,"The skeptics were talking about an imminent AI bubble burst 10 years ago. You can definitely act on your hunch by simply shorting FAANG stocks, but you would be in big gain or huge loss territory.",https://news.ycombinator.com/item?id=44684146,2025-07-01,2025,-1
65
+ 2025-03-05,HackerNews,investment,bearish,"If the bubble pops (meaning these massive compute costs never turn into actual profits and the VC money dries up) what does the tech landscape look like? A lot of us use Copilot, Claude, or ChatGPT daily for coding and docs. If the subsidized cheap access vanishes because these companies can't eat the losses anymore, do the tools just disappear?",https://news.ycombinator.com/item?id=47034849,2025-03-01,2025,-1
66
+ 2025-03-05,HackerNews,investment,bearish,I get concerned about the concentration of value (30ish percent of the S&P 500's value is tied to 7 companies) and the sheer size of the capex going on (conservatively hundreds of billions). Even a normal correction of the stock prices of those 7 will wipe out trillions of dollars in market value and hit everyone.,https://news.ycombinator.com/item?id=47047952,2025-03-01,2025,-1
67
+ 2025-03-05,HackerNews,hype,neutral,"In 2000 and 2008 dot com stocks and house prices stopped going up and fell, new investor money dried up and companies that relied on it went bust. Tech advances and housing went on though. I guess similarly in an AI bust some companies that rely on new investor money would go bust but things would go on.",https://news.ycombinator.com/item?id=47042369,2025-03-01,2025,0
68
+ 2025-03-05,HackerNews,investment,bullish,"Your assumption is wrong. The LLM companies are profitable on the current gen models. Inference is profitable, rather than subsidized. They are raising the biggest chunk of capital to buy data center compute that will come online ~2 years from now. The bear case for the labs is that they're Cisco, not Pets.com.",https://news.ycombinator.com/item?id=47035538,2025-03-01,2025,1
69
+ 2025-03-05,HackerNews,investment,neutral,There will be consolidation as few players will have the revenues to justify training their own model. Google has enough cash and revenues to be one of the survivors. OpenAI and Claude will survive in some form or another. xAI will burn through SpaceX revenues. China will keep subsidizing models.,https://news.ycombinator.com/item?id=47039138,2025-03-01,2025,0
70
+ 2025-03-05,HackerNews,hype,neutral,Possibly AI hysteria heads into the plateau and people can finally start using AI as an amazing instrument rather than hyping about it. I think local models will become productive and cheaper to run.,https://news.ycombinator.com/item?id=47040197,2025-03-01,2025,0
71
+ 2025-03-05,HackerNews,hype,neutral,The Gartner hype cycle is still as relevant now as it was during the dot com boom. At the moment we're at the peak of inflated expectations - we might stay there for a quite a while.,https://news.ycombinator.com/item?id=47039987,2025-03-01,2025,0
72
+ 2025-03-05,HackerNews,investment,bearish,The correct analogy is IMO the railway mania. LLM research will go back to government funded research labs with government funded supercomputers. All AI investment will need to be written off. Running the LLMs will be a low-margin business not justifying high multiples.,https://news.ycombinator.com/item?id=47048962,2025-03-01,2025,-1
73
+ 2025-03-05,HackerNews,investment,bearish,"The entire US economy stands on AI hype. There is nothing else. Once it pops, USA will finally go into stagflation. 2008 was nothing compared to what is coming. If you have any savings, DO NOT KEEP CASH. Go and buy precious metals.",https://news.ycombinator.com/item?id=47045852,2025-03-01,2025,-1
74
+ 2025-03-05,HackerNews,productivity,neutral,"I think even if AI can help developer productivity, there are a lot of other elements to software delivery. A hard question to answer is what is the ROI if an organization is spending $10k/month on AI tools but project deliveries are mostly the same as before?",https://news.ycombinator.com/item?id=47038067,2025-03-01,2025,0
75
+ 2025-03-05,HackerNews,productivity,bullish,The ROI is from all of the junior developers you don't have to hire to do the grunt work. I work in consulting and I would have had to scope projects with myself as the lead and at least a couple of juniors. Now I can do it all myself.,https://news.ycombinator.com/item?id=47043178,2025-03-01,2025,1
76
+ 2025-03-05,HackerNews,hype,neutral,"AI is here to stay but not in the way big corporations dream of it. When the AI bubble pops, things will stabilize and adapt to real usage with a different business model. Prices for RAM, GPUs, SSDs will normalize a lot and more people will move towards local models. All of us AI realists are currently being treated like technophobes.",https://news.ycombinator.com/item?id=47035015,2025-03-01,2025,0
77
+ 2025-03-05,HackerNews,hype,neutral,"When the dot com bubble popped, the internet and websites did not go away. When the mortgage crisis happened, mortgages certainly didn't go away. When the AI/LLM bubble pops, LLMs will still exist and be used. They just won't be hyped and pushed everywhere.",https://news.ycombinator.com/item?id=47034958,2025-03-01,2025,0
78
+ 2025-03-05,HackerNews,hype,bullish,We have a bunch of compute built out that we can use to build actually good products and services again :),https://news.ycombinator.com/item?id=47042460,2025-03-01,2025,1
79
+ 2025-03-05,HackerNews,productivity,neutral,"In 2000, I lived in Atlanta with 4 years of experience. 2000 wasn't bad outside of Silicon Valley. The AI bubble busting means absolutely nothing to me as far as career prospects. If the bubble does burst, compute gets cheaper because of excessive capacity.",https://news.ycombinator.com/item?id=47040627,2025-03-01,2025,0
80
+ 2025-03-05,HackerNews,investment,neutral,"The 'AI bubble burst' framing is too simplistic. Bubbles don't erase technology. They erase mispriced capital. A post-bubble AI world probably looks less magical and less overfunded, but more disciplined. Fewer demos. More boring enterprise contracts.",https://news.ycombinator.com/item?id=47044531,2025-03-01,2025,0
81
+ 2025-03-05,HackerNews,hype,neutral,"Clearly we all get on the next train, robotics or quantum.",https://news.ycombinator.com/item?id=47039915,2025-03-01,2025,0
82
+ 2025-03-05,HackerNews,investment,bullish,I wouldn't find it hard to personally justify $200/month or $300/month for the single best LLM tool available to me. Right now I have $100/month spread out over a few different tools and it's a bargain.,https://news.ycombinator.com/item?id=47039857,2025-03-01,2025,1
83
+ 2025-03-05,HackerNews,productivity,bullish,"I think right now I have Claude, ChatGPT, MidJourney, and Perplexity. Perplexity has replaced Google searches for me. ChatGPT/Claude is what I use for generating code. Claude I think is better at writing than ChatGPT. MidJourney is $10 and I like it best for image generation.",https://news.ycombinator.com/item?id=47041117,2025-03-01,2025,1
84
+ 2025-08-28,HackerNews,hype,bullish,"Work for a major research lab. So much headroom, so much left on the table with every project, so many obvious directions to go to tackle major problems. These last 3 years have been chaotic sprints. Insane progress that somehow just gets continually denigrated by this community. There is hype and bullshit and stupid money, but 'it's a bubble' is absurd to me.",https://news.ycombinator.com/item?id=45041854,2025-08-01,2025,1
85
+ 2025-08-28,HackerNews,productivity,bullish,"I work as a ML researcher in a small startup researching, developing and training large models on a daily basis. I see the improvements done in my field every day. It feels as if people who talk about AI being a bubble are not familiar with AI which is not LLMs, and the amazing advancements it already did in drug discovery, ASR, media generation.",https://news.ycombinator.com/item?id=45042153,2025-08-01,2025,1
86
+ 2025-08-28,HackerNews,productivity,bullish,"Claude Code and ChatGPT brought me back to the early 2010s golden age when indies could be a one-man army. Not only code, but also for localizations, marketing. Not to mention that they unblock me and have basically fixed a large part of my ADHD issues because I can easily kickstart whatever task.",https://news.ycombinator.com/item?id=45042312,2025-08-01,2025,1
87
+ 2025-08-28,HackerNews,investment,bearish,"Companies like Windsurf, Cursor, many, they are all currently building the package for Wallstreet with literally no care that it will pull in retail investment en masse. This is going to be a messed up rug pull for regular investors in a few years.",https://news.ycombinator.com/item?id=45042754,2025-08-01,2025,-1
88
+ 2025-08-28,HackerNews,hype,neutral,"The thing to remember about the HN crowd is it can be a bit cynical. The concern is that for all the enthusiasm, generative AI's hard problems still seem unsolved, the output quality is seeing diminishing returns, and actually applying it outside language settings has been challenging.",https://news.ycombinator.com/item?id=45042006,2025-08-01,2025,0
89
+ 2025-08-28,HackerNews,hype,neutral,The way I've been thinking about this is that there is The Tech and The Business. The Tech is amazing and improving all the time. But The Business is the bubble part. Those companies went out of business but the fiber was still there and still useful.,https://news.ycombinator.com/item?id=45042845,2025-08-01,2025,0
90
+ 2025-08-28,HackerNews,investment,bearish,"AI-first app companies that actually go public, massive influx of investment from retail as the basket of 'AI' is just too much to pass up. This basket is no longer a collection of top tier titans but led by resellers and wrappers. Perplexity going public would be one of my biggest Red Flag moments.",https://news.ycombinator.com/item?id=45042443,2025-08-01,2025,-1
91
+ 2025-08-28,HackerNews,investment,neutral,"One difference between this and the dot-com bubble is that it is much, much harder to go public now. Arguably a more likely end is that the VCs turn off the tap, which will kill most companies in the space within a year, with fairly limited contagion to the broader markets.",https://news.ycombinator.com/item?id=45052134,2025-08-01,2025,0
92
+ 2025-08-28,HackerNews,skepticism,bearish,"There is no question LLMs are truly useful in some areas, and the LLM bubble will inevitably burst. Both can be simultaneously true. I see no serious arguments scaling up LLMs with increasingly massive data centers will actually reach anything like breakthrough to AGI.",https://news.ycombinator.com/item?id=45041223,2025-08-01,2025,-1
93
+ 2025-08-28,HackerNews,hype,neutral,We even have prior art. Web 1.0 and e-Commerce were truly useful and the bubble also burst. Railroads and radio also good examples!,https://news.ycombinator.com/item?id=45041254,2025-08-01,2025,0
94
+ 2025-08-28,HackerNews,investment,neutral,"Unlike that time, some money is actually being made. Total combined investments of over 500 billion and revenues of about 30 billion, 10 bil of which was payments to cloud providers, so actually 20 billion in revenues. That's not nothing.",https://news.ycombinator.com/item?id=45041497,2025-08-01,2025,0
95
+ 2025-08-28,HackerNews,investment,neutral,"The root of the bubble is not that the value will never be realized, but that many of this crop of companies will almost certainly not be able to survive long enough to be the ones to realize it. Folks draw comparison to the dot com bubble because it was quite similar.",https://news.ycombinator.com/item?id=45045200,2025-08-01,2025,0
96
+ 2025-08-28,HackerNews,investment,neutral,"I'm a little skeptical of a full on 2008-style 'burst'. I imagine it'll be closer to a slow deflation as these companies need to turn a profit. Fundamentally, serving a model via API is profitable and inference costs come down drastically over time.",https://news.ycombinator.com/item?id=45042052,2025-08-01,2025,0
97
+ 2025-08-28,HackerNews,investment,neutral,Bubbles are a core part of the innovation process. New tech being useful doesn't imply a lack of bubbles. See 'Technological Revolutions and Financial Capital' by Carlota Perez.,https://news.ycombinator.com/item?id=45041514,2025-08-01,2025,0
98
+ 2025-08-28,HackerNews,investment,bearish,By summer 2026 I think a lot of execs will be getting antsy if they can't defend their investments.,https://news.ycombinator.com/item?id=45041625,2025-08-01,2025,-1
99
+ 2025-10-17,SemiWiki,investment,bearish,"I've been thinking about the large company money exchanges going on between OpenAI, Microsoft, Nvidia, AMD, and others.. and I think there's a case that the bubble has already bust, we just don't see it reflected in stock pricing or corporate profits just yet. Nvidia is effectively giving OpenAI a 'rebate' on their GPUs by seeding them money.",https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93585,2025-10-01,2025,-1
100
+ 2025-10-17,SemiWiki,hype,bullish,"I was at a Rotary meeting / dinner in Nairobi, Kenya last week. I was at the end of the table talking to 3 business folks there - all three were about 30 years old, had AI subscriptions, and claimed it was essential for work and home. I'm seeing AI picking up steam in a lot of unusual places.",https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93589,2025-10-01,2025,1
101
+ 2025-10-17,SemiWiki,investment,neutral,"It will burst eventually, but it's not happening anytime soon. The amount of angel investment money available to these companies is huge - they can last years without needing to generate profits.",https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93592,2025-10-01,2025,0
102
+ 2025-10-17,SemiWiki,investment,neutral,"We won't know if the AI bubble has burst except in the rear view mirror. I am very cautious about making any investments in AI related stocks right now, but I don't believe the bubble is done inflating. Especially when the fed is cutting and not raising rates.",https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93595,2025-10-01,2025,0
103
+ 2025-10-17,SemiWiki,hype,bullish,Basically anyone who speaks English as a second language is finding AI critical for business and interaction with the English speaking world. AI is really good at filling in the blanks and giving you voice that presents like a native speaker.,https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93596,2025-10-01,2025,1
104
+ 2025-10-17,SemiWiki,hype,bullish,"If you look at the AI bubble as both infrastructure build and inference support the bubble will be years in the making. The AI bubble is not just semiconductors, it will be across many industries and will literally change how we live our lives. Much like the internet and smartphones did.",https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93598,2025-10-01,2025,1
105
+ 2025-10-17,SemiWiki,investment,bearish,The problem is the keystones of this inevitable bubble are private startups who don't have the same transparency requirements as public companies.,https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/post-93600,2025-10-01,2025,-1
106
+ 2025-10-17,SemiWiki,investment,neutral,"'Unlike the telecom bubble, where demand was speculative & customers burned cash, this merry-go-round has paying riders.' Yes this is a bubble, but no it's not ready to pop. My guess is we will top in the 2027-2028 timeframe.",https://semiwiki.com/forum/threads/discussion-has-the-ai-bubble-already-burst.23841/,2025-10-01,2025,0
107
+ 2025-10-31,Reddit,investment,bearish,"Meh, what could go wrong?...",https://www.moneydigest.com/2012317/what-could-go-wrong-reddit-reacts-to-ai-bubble/,2025-10-01,2025,-1
108
+ 2025-10-31,Reddit,investment,bearish,There's a handful of huge companies all throwing money in gambling that their AI model will win and corner the market.,https://www.moneydigest.com/2012317/what-could-go-wrong-reddit-reacts-to-ai-bubble/,2025-10-01,2025,-1
109
+ 2025-10-31,Reddit,investment,bearish,"Hyperscalers are hiding their losses in special purpose vehicles too, just like Enron!",https://www.moneydigest.com/2012317/what-could-go-wrong-reddit-reacts-to-ai-bubble/,2025-10-01,2025,-1
110
+ 2025-10-31,Reddit,skepticism,bearish,Bubble go pop. Everyone suffers.,https://www.moneydigest.com/2012317/what-could-go-wrong-reddit-reacts-to-ai-bubble/,2025-10-01,2025,-1
111
+ 2025-10-31,Reddit,skepticism,bearish,Yep. We'll be bailing them out again.,https://www.moneydigest.com/2012317/what-could-go-wrong-reddit-reacts-to-ai-bubble/,2025-10-01,2025,-1
112
+ 2025-11-29,HackerNews,skepticism,bearish,"If they succeed with agentic reasoning models (we are absolutely not there yet) then I think meritocracy will be replaced with assetocracy. The better the model, the more expensive it will be. I don't worry about it myself, but I do worry for my kids. I'm not even sure what to teach them anymore.",https://news.ycombinator.com/item?id=46086410,2025-11-01,2025,-1
113
+ 2025-08-22,HackerNews,productivity,bullish,"gpt5-mini is insane for its price, in agentic coding. I've had great sessions with it, at 0.x$ per session. It can do things that claude 3.5/3.7 couldn't do ~6 months ago, at 10-15x the price. Whatever RL they did is working wonders.",https://news.ycombinator.com/item?id=44964548,2025-08-01,2025,1
114
+ 2024-07-12,Newsletter,investment,bearish,"In 2023, AI firms spent $50 billion on Nvidia chips but they only generated $3 billion in revenue. It's a mismatch that is really undermining and is going to bring the crash.",https://www.theaioptimist.com/p/the-ai-bubble-burst-wakes-up,2024-07-01,2024,-1
115
+ 2024-07-12,Newsletter,hype,bearish,"David Cahn, an analyst with Sequoia Capital, believes that AI companies will have to earn about $600 billion per year to pay for the infrastructure, such as data centers, and revenue is in single-digit billions. This is a huge gap.",https://www.theaioptimist.com/p/the-ai-bubble-burst-wakes-up,2024-07-01,2024,-1
116
+ 2024-07-12,Newsletter,hype,bearish,"The founder and CEO of Baidu, the biggest search engine in China, says that their country has too many large language models and too few practical applications.",https://www.theaioptimist.com/p/the-ai-bubble-burst-wakes-up,2024-07-01,2024,-1
117
+ 2025-03-06,CACM,investment,neutral,"A Bank of America survey from November revealed concerns about companies 'overinvesting', with 45% of investors saying they view an AI bubble as the top risk for the economy and markets.",https://cacm.acm.org/news/is-the-ai-bubble-about-to-pop/,2025-03-01,2025,0
118
+ 2025-03-06,CACM,investment,neutral,"We're in the gray area right now. The chances of a big, dramatic burst with things crashing down? I'm not really seeing that now. More realistically, there may be a couple of mini corrections.",https://cacm.acm.org/news/is-the-ai-bubble-about-to-pop/,2025-03-01,2025,0
119
+ 2025-03-06,CACM,investment,bearish,"It's not a good idea in the sense that we're doing what we do in all of these capital bubbles, whether it's railroads or fiber, overbuilding in anticipation of projections of future demand based on recent trends.",https://cacm.acm.org/news/is-the-ai-bubble-about-to-pop/,2025-03-01,2025,-1
120
+ 2025-03-06,CACM,investment,neutral,"I'm not sure we're in an AI bubble, given that major tech companies like Nvidia are seeing 'real revenues' through the sale of chips. But 'what other things in humanity are we forgetting to build,' such as roads, hospitals, medical supplies, and food.",https://cacm.acm.org/news/is-the-ai-bubble-about-to-pop/,2025-03-01,2025,0
121
+ 2025-09-12,Blog,hype,bullish,"The debate is about whether this tech is going to transform business, the economy, and society. It comes down to whether you think that belief—and all the investment going in behind it—is a bubble. And that, as a false belief, it will pop.",https://danielmiessler.com/blog/no-ai-is-not-a-bubble,2025-09-01,2025,1
122
+ 2024-11-09,Blog,skepticism,bearish,"LLMs have reached a point of diminishing returns. Sky high valuations of companies like OpenAI and Microsoft are largely based on the notion that LLMs will, with continued scaling, become artificial general intelligence. That's just a fantasy. There is no principled solution to hallucinations.",https://garymarcus.substack.com/p/confirmed-llms-have-indeed-reached,2024-11-01,2024,-1
123
+ 2024-11-09,Blog,skepticism,bearish,"LLMs will not disappear even if improvements diminish, but the economics will likely never make sense: additional training is expensive. And everyone is landing in more or less the same place, which leaves nobody with a moat. LLMs will become a commodity; price wars will keep revenue low.",https://garymarcus.substack.com/p/confirmed-llms-have-indeed-reached,2024-11-01,2024,-1
124
+ 2024-11-09,Blog,skepticism,bearish,"Gary, I heard you list 10 great reasons for skepticism on the BBC. The interviewer gave equal time to an investor who shared hype and zero counter evidence. Meanwhile, the hype has every business searching for use cases, most of which are pretty meh. An AI Assistant is just a form, built with trial and error.",https://garymarcus.substack.com/p/confirmed-llms-have-indeed-reached,2024-11-01,2024,-1
125
+ 2024-11-13,HackerNews,hype,neutral,"But that's fine, LLMs as-is are amazing without being AGI. Perhaps not to those who invested based on promises of rapid eternal growth ending with AGI.",https://news.ycombinator.com/item?id=42097774,2024-11-01,2024,0
126
+ 2024-11-20,TechCrunch,hype,neutral,"AI scaling laws, the methods and expectations that labs have used to increase the capabilities of their models for the last five years, are now showing signs of diminishing returns, according to several AI investors, founders, and CEOs.",https://techcrunch.com/2024/11/20/ai-scaling-laws-are-showing-diminishing-returns-forcing-ai-labs-to-change-course/,2024-11-01,2024,0
127
+ 2024-11-20,TechCrunch,hype,neutral,"If you just put in more compute, you put in more data, you make the model bigger — there are diminishing returns. In order to keep the scaling laws going, we also need new ideas. When you've read a million reviews on Yelp, maybe the next reviews don't give you that much.",https://techcrunch.com/2024/11/20/ai-scaling-laws-are-showing-diminishing-returns-forcing-ai-labs-to-change-course/,2024-11-01,2024,0
128
+ 2025-12-09,Blog,hype,bullish,"Very few people use VR, even a decade after it started getting hyped. But AI is being adopted more rapidly than any technology in history. As far back as a year ago, 40% of people were already using AI at work. Humans just know when a technology works.",https://www.noahpinion.blog/p/the-ai-bust-scenario-that-no-one,2025-12-01,2025,1
129
+ 2025-12-09,Blog,investment,neutral,"'Some parts of AI are probably in a bubble,' Google DeepMind CEO Demis Hassabis told Axios. 'I think it would be a mistake to dismiss it as snake oil,' OpenAI Chairman Bret Taylor said. Taylor acknowledged that there 'probably is a bubble,' but said businesses, ideas and technologies endure even after bubbles pop.",https://www.noahpinion.blog/p/the-ai-bust-scenario-that-no-one,2025-12-01,2025,0
130
+ 2023-02-24,InvestorPlace,hype,neutral,"One user pointedly states that 'we are in a time of fools-gold rushes, and NVDA is selling shovels.' Whether AI turns out to be the 'technology of the future' or mostly useless is up in the air.",https://investorplace.com/2023/02/why-nvidia-nvda-stock-is-dominating-r-wallstreetbets-right-now/,2023-02-01,2023,0
131
+ 2025-08-25,HackerNews,hype,bearish,Too many people talk about a bubble almost wish-casting that such a thing will make this all go away.,https://news.ycombinator.com/item?id=44986576,2025-08-01,2025,-1
132
+ 2025-08-25,HackerNews,hype,neutral,So let's bet on sunken cost fallacy? That didn't work out during the dotcom era.,https://news.ycombinator.com/item?id=44986576,2025-08-01,2025,0
133
+ 2024-06-25,HackerNews,investment,neutral,"I do expect the current trajectory of generative models will eventually be incredibly important just like the internet was and is, but it seems there's a lot of high expectations of how useful it can be in the near future with fuzzy ideas around business models like in the dot com era.",https://news.ycombinator.com/item?id=40739431,2024-06-01,2024,0
134
+ 2025-10-10,HackerNews,investment,bearish,"AI is here to stay, but the valuations are likely way, way too high. OpenAI may survive, but their investors may not. Nvidia's stock will likely tumble, which will bring down the S&P 500.",https://news.ycombinator.com/item?id=45530497,2025-10-01,2025,-1
135
+ 2022-03-20,HackerNews,skepticism,bearish,"Is there a single example in AI, or even technology as a whole, where simply continuing to apply one technique has led to compounding growth? There is no similar curve for AI. It's not hard to see that compounding this process will rapidly hit diminishing returns quickly in terms of time, power consumption, cost of hardware.",https://news.ycombinator.com/item?id=30735550,2022-03-01,2022,-1
136
+ 2025-01-20,HackerNews,hype,neutral,TLDR: Google tries to push Gemini (and requires money to turn it off) and therefore nobody wants any kind of AI for any reason. This despite that ChatGPT $200 is too successful meaning people use it too much and the price should be higher.,https://news.ycombinator.com/item?id=42771634,2025-01-01,2025,0
137
+ 2025-01-22,HackerNews,productivity,bearish,"Indeed, reviewing notoriously takes just as much effort as producing the artifact under review!",https://news.ycombinator.com/item?id=42789214,2025-01-01,2025,-1
138
+ 2025-01-20,HackerNews,hype,neutral,"It just doesn't seem like those applications matter enough to normal people to actually pay anyone for them. I don't think people really want to pay for anything, which is why everything is ad supported. I don't think you can say people don't want AI just because they don't want to pay 20/month.",https://news.ycombinator.com/item?id=42770943,2025-01-01,2025,0
139
+ 2025-01-20,HackerNews,hype,bearish,"The piece is about Google Workspace, a paid service. Gemini apparently had low uptake from this already-self-selecting group of customers who are indeed willing to pay for stuff. Rather than going back to the drawing board, they increased everyone's base subscription and made Gemini 'free'.",https://news.ycombinator.com/item?id=42771142,2025-01-01,2025,-1
140
+ 2025-01-20,HackerNews,skepticism,bearish,People don't like subscriptions.,https://news.ycombinator.com/item?id=42771003,2025-01-01,2025,-1
141
+ 2025-01-20,HackerNews,hype,bearish,But do people want AI that's rigged to constantly recommend Shopping Like A Billionaire at Temu? Because that's the alternative if people won't pay.,https://news.ycombinator.com/item?id=42771011,2025-01-01,2025,-1
142
+ 2025-01-20,HackerNews,hype,neutral,"It's not bursting, not right now at least. But I concede it might burst as the internet bubble did 20 years ago. It will then continue. It won't have as much of an impact as internet did imho, but on that point I can be very wrong.",https://news.ycombinator.com/item?id=42771356,2025-01-01,2025,0
143
+ 2025-01-21,HackerNews,productivity,bullish,"No decently coded chatbot is going to respond with an incorrect restaurant menu or opening time. You'd call a function to return the menu from a database. At worst, the function fails, but it's not going to hallucinate dishes.",https://news.ycombinator.com/item?id=42775281,2025-01-01,2025,1
144
+ 2025-01-20,HackerNews,hype,neutral,"This AI development stalemate is more layered. Big companies set such broad targets in a race to catch up with OpenAI that they lose focus on real use cases. The loudest voices, those good at navigating internal politics, push their own ambition over actual customer needs or technical practicality. It's still a great time for small AI startups.",https://news.ycombinator.com/item?id=42771311,2025-01-01,2025,0
145
+ 2025-03-05,HackerNews,investment,neutral,Do you have figures supporting that? Because so far everything I've seen points to current inference subscriptions being wildly unprofitable.,https://news.ycombinator.com/item?id=47089095,2025-03-01,2025,0
146
+ 2025-03-05,HackerNews,hype,neutral,"I can go on Gemini, Claude, Mistral, and even ChatGPT for free even through a VPN, so they are definitely not profitable for me as they are not getting anything from me.",https://news.ycombinator.com/item?id=47040185,2025-03-01,2025,0
147
+ 2025-03-05,HackerNews,investment,neutral,They're getting data from you which is quite valuable.,https://news.ycombinator.com/item?id=47041651,2025-03-01,2025,0
148
+ 2025-03-05,HackerNews,productivity,neutral,"General investment advice is to put like 90% or more into ETFs, index funds or low-fee mutual funds. If someone went all-in on AI companies and a stock market bubble bursts, you're in the 'find out' phase.",https://news.ycombinator.com/item?id=44686042,2025-03-01,2025,0
149
+ 2025-06-23,HackerNews,investment,neutral,How likely do you think it is that this comes to an end soon? IMHO current valuations are ridiculous indicating we have reached the peak.,https://news.ycombinator.com/item?id=44325986,2025-06-01,2025,0
150
+ 2025-06-23,HackerNews,investment,bullish,Wouldn't call that a bubble. Sure some companies may run out of fuel but the Hyperscalers got a huge warchest to deploy for decades.,https://news.ycombinator.com/item?id=44325986,2025-06-01,2025,1
ai_bubble_monthly.csv ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ month,bullish,neutral,bearish,avg_score
2
+ 2022-03,0,0,1,-1.0
3
+ 2023-02,0,1,0,0.0
4
+ 2024-01,0,1,0,0.0
5
+ 2024-04,4,1,9,-0.35714285714285715
6
+ 2024-06,0,1,0,0.0
7
+ 2024-07,0,0,3,-1.0
8
+ 2024-11,6,6,4,0.125
9
+ 2024-12,1,0,0,1.0
10
+ 2025-01,2,8,18,-0.5714285714285714
11
+ 2025-03,5,17,5,0.0
12
+ 2025-06,1,1,0,0.5
13
+ 2025-07,6,5,7,-0.05555555555555555
14
+ 2025-08,4,9,5,-0.05555555555555555
15
+ 2025-09,1,0,0,1.0
16
+ 2025-10,3,3,8,-0.35714285714285715
17
+ 2025-11,0,0,1,-1.0
18
+ 2025-12,1,1,0,0.5
ai_bubble_sentiment_expanded.xlsx ADDED
Binary file (28.7 kB). View file
 
app.py ADDED
@@ -0,0 +1,1383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ╔══════════════════════════════════════════════════════════════════════╗
2
+ # ║ BubbleBusters — AI Bubble Sentiment Analytics ║
3
+ # ║ RX12 Group Project · ESCP Europe ║
4
+ # ║ app.py — Three-notebook pipeline + live dashboard ║
5
+ # ╚══════════════════════════════════════════════════════════════════════╝
6
+
7
+ import os
8
+ import re
9
+ import json
10
+ import time
11
+ import traceback
12
+ import sys
13
+ import subprocess
14
+ from pathlib import Path
15
+ from typing import Dict, Any, List, Tuple, Optional
16
+
17
+ import pandas as pd
18
+ import gradio as gr
19
+ import papermill as pm
20
+ import plotly.express as px
21
+ import plotly.graph_objects as go
22
+ from plotly.subplots import make_subplots
23
+
24
+ # ── Optional dependencies ───────────────────────────────────────────────
25
+ try:
26
+ import yfinance as yf
27
+ YFINANCE_AVAILABLE = True
28
+ except ImportError:
29
+ YFINANCE_AVAILABLE = False
30
+
31
+ try:
32
+ from huggingface_hub import InferenceClient
33
+ except Exception:
34
+ InferenceClient = None
35
+
36
+
37
+ # ══════════════════════════════════════════════════════════════════════
38
+ # CONFIG
39
+ # ══════════════════════════════════════════════════════════════════════
40
+
41
+ BASE_DIR = Path(__file__).resolve().parent
42
+
43
+ NB1 = os.environ.get("NB1", "datacreation_bubblebusters.ipynb").strip()
44
+ NB2 = os.environ.get("NB2", "pythonanalysis_bubblebusters.ipynb").strip()
45
+ NB3 = os.environ.get("NB3", "ranalysis_bubblebusters.ipynb").strip()
46
+
47
+ RUNS_DIR = BASE_DIR / "runs"
48
+ ART_DIR = BASE_DIR / "artifacts"
49
+ PY_FIG_DIR = ART_DIR / "py" / "figures"
50
+ PY_TAB_DIR = ART_DIR / "py" / "tables"
51
+ R_FIG_DIR = ART_DIR / "r" / "figures"
52
+ R_TAB_DIR = ART_DIR / "r" / "tables"
53
+
54
+ PAPERMILL_TIMEOUT = int(os.environ.get("PAPERMILL_TIMEOUT", "1800"))
55
+ MAX_PREVIEW_ROWS = int(os.environ.get("MAX_FILE_PREVIEW_ROWS", "50"))
56
+
57
+ HF_API_KEY = os.environ.get("HF_API_KEY", "").strip()
58
+ MODEL_NAME = os.environ.get("MODEL_NAME", "deepseek-ai/DeepSeek-R1").strip()
59
+ HF_PROVIDER = os.environ.get("HF_PROVIDER", "novita").strip()
60
+
61
+ # Colour palette
62
+ ESCP_PURPLE = "#00d2be"
63
+ BULLISH = "#2ec4a0" # deep mint-teal
64
+ NEUTRAL = "#5e8fef" # medium periwinkle-blue
65
+ BEARISH = "#e8537a" # deep blush-rose
66
+ AMBER = "#e8a230" # rich amber
67
+
68
+ # LLM client
69
+ LLM_ENABLED = bool(HF_API_KEY) and InferenceClient is not None
70
+ llm_client = (
71
+ InferenceClient(provider=HF_PROVIDER, api_key=HF_API_KEY)
72
+ if LLM_ENABLED else None
73
+ )
74
+
75
+ # AI-related tickers shown in the prices section
76
+ AI_TICKERS_DEFAULT = "NVDA, MSFT, GOOGL, META, AMD"
77
+ AI_PRESET_MEGA = "NVDA, MSFT, GOOGL, META, AMZN, AAPL"
78
+ AI_PRESET_SEMI = "NVDA, AMD, TSM, INTC, QCOM, SMCI"
79
+ AI_PRESET_PURE = "AI, PLTR, SOUN, PATH, BBAI, GFAI"
80
+
81
+
82
+ # ══════════════════════════════════════════════════════════════════════
83
+ # KERNEL SETUP (for papermill)
84
+ # ══════════════════════════════════════════════════════════════════════
85
+
86
+ def ensure_python_kernelspec() -> str:
87
+ from jupyter_client.kernelspec import KernelSpecManager
88
+ ksm = KernelSpecManager()
89
+ specs = ksm.find_kernel_specs()
90
+ if not specs:
91
+ try:
92
+ import ipykernel # noqa: F401
93
+ except Exception as e:
94
+ raise RuntimeError(
95
+ "ipykernel is not installed. "
96
+ "Add 'ipykernel' to requirements.txt and rebuild the Space.\n"
97
+ f"Original error: {e}"
98
+ )
99
+ subprocess.check_call([
100
+ sys.executable, "-m", "ipykernel", "install",
101
+ "--user", "--name", "python3", "--display-name", "Python 3 (Space)"
102
+ ])
103
+ specs = ksm.find_kernel_specs()
104
+
105
+ if "python3" in specs:
106
+ return "python3"
107
+ for name in specs:
108
+ if "python" in name.lower():
109
+ return name
110
+ raise RuntimeError(f"No Python kernel found. Available: {list(specs.keys())}")
111
+
112
+
113
+ try:
114
+ PY_KERNEL = ensure_python_kernelspec()
115
+ KERNEL_INIT_ERROR = ""
116
+ except Exception as e:
117
+ PY_KERNEL = None
118
+ KERNEL_INIT_ERROR = str(e)
119
+
120
+
121
+ # ══════════════════════════════════════════════════════════════════════
122
+ # HELPERS
123
+ # ══════════════════════════════════════════════════════════════════════
124
+
125
+ def ensure_dirs():
126
+ for p in [RUNS_DIR, ART_DIR, PY_FIG_DIR, PY_TAB_DIR, R_FIG_DIR, R_TAB_DIR]:
127
+ p.mkdir(parents=True, exist_ok=True)
128
+
129
+
130
+ def stamp():
131
+ return time.strftime("%Y%m%d-%H%M%S")
132
+
133
+
134
+ def _ls(dir_path: Path, exts: Tuple[str, ...]) -> List[str]:
135
+ if not dir_path.is_dir():
136
+ return []
137
+ return sorted(p.name for p in dir_path.iterdir()
138
+ if p.is_file() and p.suffix.lower() in exts)
139
+
140
+
141
+ def _read_csv(path: Path) -> pd.DataFrame:
142
+ return pd.read_csv(path, nrows=MAX_PREVIEW_ROWS)
143
+
144
+
145
+ def _read_json(path: Path):
146
+ with path.open(encoding="utf-8") as f:
147
+ return json.load(f)
148
+
149
+
150
+ def artifacts_index() -> Dict[str, Any]:
151
+ return {
152
+ "python": {
153
+ "figures": _ls(PY_FIG_DIR, (".png", ".jpg", ".jpeg")),
154
+ "tables": _ls(PY_TAB_DIR, (".csv", ".json")),
155
+ },
156
+ "r": {
157
+ "figures": _ls(R_FIG_DIR, (".png", ".jpg", ".jpeg")),
158
+ "tables": _ls(R_TAB_DIR, (".csv", ".json")),
159
+ },
160
+ }
161
+
162
+
163
+ # ══════════════════════════════════════════════════════════════════════
164
+ # PIPELINE STATUS
165
+ # ══════════════════════════════════════════════════════════════════════
166
+
167
+ def get_pipeline_status() -> Dict[str, Any]:
168
+ clean_csv = BASE_DIR / "ai_bubble_clean.csv"
169
+ monthly_csv = BASE_DIR / "ai_bubble_monthly.csv"
170
+ data_ok = clean_csv.exists() and monthly_csv.exists()
171
+
172
+ py_figs = _ls(PY_FIG_DIR, (".png",))
173
+ py_tabs = _ls(PY_TAB_DIR, (".csv", ".json"))
174
+ py_ok = len(py_figs) >= 5 and len(py_tabs) >= 4
175
+
176
+ r_figs = _ls(R_FIG_DIR, (".png",))
177
+ r_tabs = _ls(R_TAB_DIR, (".csv", ".json"))
178
+ r_ok = len(r_figs) >= 3 and len(r_tabs) >= 2
179
+
180
+ return {
181
+ "data": {
182
+ "ok": data_ok,
183
+ "detail": (
184
+ f"ai_bubble_clean.csv: {'✅' if clean_csv.exists() else '❌'} | "
185
+ f"ai_bubble_monthly.csv: {'✅' if monthly_csv.exists() else '❌'}"
186
+ ),
187
+ },
188
+ "python": {
189
+ "ok": py_ok,
190
+ "detail": f"{len(py_figs)} figures · {len(py_tabs)} tables",
191
+ },
192
+ "r": {
193
+ "ok": r_ok,
194
+ "detail": f"{len(r_figs)} figures · {len(r_tabs)} tables",
195
+ },
196
+ }
197
+
198
+
199
+ def render_status_html() -> str:
200
+ status = get_pipeline_status()
201
+
202
+ def badge(ok: bool, label: str, detail: str, icon: str) -> str:
203
+ colour = "#3dcba8" if ok else "#ff6b8a"
204
+ bg = "rgba(61,203,168,.10)" if ok else "rgba(255,107,138,.08)"
205
+ border = "rgba(61,203,168,.30)" if ok else "rgba(255,107,138,.25)"
206
+ pill = "READY" if ok else "PENDING"
207
+ return f"""
208
+ <div style="display:flex;align-items:flex-start;gap:12px;
209
+ padding:13px 15px;background:{bg};
210
+ border:1.5px solid {border};
211
+ border-radius:16px;margin-bottom:8px;">
212
+ <div style="font-size:20px;line-height:1;margin-top:2px;flex-shrink:0">{icon}</div>
213
+ <div style="flex:1;min-width:0;">
214
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:3px;">
215
+ <span style="font-family:'Nunito',sans-serif;font-weight:800;
216
+ color:#2d1f4e;font-size:13px;">{label}</span>
217
+ <span style="margin-left:auto;background:{colour};color:#fff;
218
+ border-radius:50px;padding:2px 10px;
219
+ font-family:'Nunito',sans-serif;
220
+ font-size:10px;font-weight:800;letter-spacing:.8px;
221
+ flex-shrink:0;">{pill}</span>
222
+ </div>
223
+ <div style="color:#9d8fc4;font-size:11.5px;font-family:'Nunito',sans-serif;
224
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
225
+ {detail}
226
+ </div>
227
+ </div>
228
+ </div>"""
229
+
230
+ html = """
231
+ <div style="background:rgba(255,255,255,.65);backdrop-filter:blur(16px);
232
+ border-radius:20px;padding:18px;
233
+ border:1.5px solid rgba(255,255,255,.7);
234
+ box-shadow:0 8px 32px rgba(124,92,191,.10);">
235
+ <div style="font-family:'Nunito',sans-serif;color:#a48de8;
236
+ font-weight:900;font-size:10.5px;text-transform:uppercase;
237
+ letter-spacing:2.5px;margin-bottom:14px;
238
+ display:flex;align-items:center;gap:10px;">
239
+ <span>🔍 Pipeline Status</span>
240
+ <div style="flex:1;height:1.5px;
241
+ background:linear-gradient(90deg,rgba(164,141,232,.5),transparent);
242
+ border-radius:2px;"></div>
243
+ </div>
244
+ """
245
+ html += badge(status["data"]["ok"], "Data Creation", status["data"]["detail"], "📦")
246
+ html += badge(status["python"]["ok"], "Python Analysis", status["python"]["detail"], "🐍")
247
+ html += badge(status["r"]["ok"], "R Analysis", status["r"]["detail"], "📊")
248
+ html += "</div>"
249
+ return html
250
+
251
+
252
+ # ══════════════════════════════════════════════════════════════════════
253
+ # PIPELINE RUNNERS
254
+ # ══════════════════════════════════════════════════════════════════════
255
+
256
+ def run_notebook(nb_name: str, kernel_name: str) -> str:
257
+ ensure_dirs()
258
+ nb_in = BASE_DIR / nb_name
259
+ if not nb_in.exists():
260
+ return f"ERROR: {nb_name} not found at {nb_in}"
261
+ nb_out = RUNS_DIR / f"run_{stamp()}_{nb_name}"
262
+ pm.execute_notebook(
263
+ input_path=str(nb_in),
264
+ output_path=str(nb_out),
265
+ cwd=str(BASE_DIR),
266
+ log_output=True,
267
+ progress_bar=False,
268
+ request_save_on_cell_execute=True,
269
+ execution_timeout=PAPERMILL_TIMEOUT,
270
+ kernel_name=kernel_name,
271
+ )
272
+ return f"✅ Executed {nb_name}"
273
+
274
+
275
+ def run_datacreation() -> str:
276
+ try:
277
+ if not PY_KERNEL:
278
+ return f"❌ Kernel unavailable:\n{KERNEL_INIT_ERROR}"
279
+ return run_notebook(NB1, kernel_name=PY_KERNEL)
280
+ except Exception as e:
281
+ return f"❌ FAILED: {e}\n\n{traceback.format_exc()[-2000:]}"
282
+
283
+
284
+ def run_pythonanalysis() -> str:
285
+ try:
286
+ if not PY_KERNEL:
287
+ return f"❌ Kernel unavailable:\n{KERNEL_INIT_ERROR}"
288
+ return run_notebook(NB2, kernel_name=PY_KERNEL)
289
+ except Exception as e:
290
+ return f"❌ FAILED: {e}\n\n{traceback.format_exc()[-2000:]}"
291
+
292
+
293
+ def run_r() -> str:
294
+ """Run the R analysis notebook via papermill + IRkernel."""
295
+ try:
296
+ # Check IRkernel is registered
297
+ from jupyter_client.kernelspec import KernelSpecManager
298
+ specs = KernelSpecManager().find_kernel_specs()
299
+ if "ir" not in specs:
300
+ return (
301
+ "❌ IRkernel not found in this environment.\n\n"
302
+ "If you are running locally, install it with:\n"
303
+ " Rscript -e \"install.packages('IRkernel')\"\n"
304
+ " Rscript -e \"IRkernel::installspec()\"\n\n"
305
+ "On the Hugging Face Space (Docker), this is pre-installed — "
306
+ "if you see this message, try rebuilding the Space."
307
+ )
308
+ return run_notebook(NB3, kernel_name="ir")
309
+ except Exception as e:
310
+ return f"❌ FAILED: {e}\n\n{traceback.format_exc()[-2000:]}"
311
+
312
+
313
+ def run_full_pipeline() -> str:
314
+ logs = []
315
+ for label, fn in [
316
+ ("📦 STEP 1/3 — Data Creation", run_datacreation),
317
+ ("🐍 STEP 2a/3 — Python Analysis", run_pythonanalysis),
318
+ ("📊 STEP 2b/3 — R Analysis", run_r),
319
+ ]:
320
+ logs.append(f"\n{'─'*52}\n{label}\n{'─'*52}")
321
+ logs.append(fn())
322
+ return "\n".join(logs)
323
+
324
+
325
+ # ══════════════════════════════════════════════════════════════════════
326
+ # ASSET PRICES
327
+ # ══════════════════════════════════════════════════════════════════════
328
+
329
+ def fetch_asset_prices(
330
+ tickers_str: str,
331
+ period: str = "6mo",
332
+ ) -> Tuple[go.Figure, str]:
333
+ """Fetch prices via yfinance and return normalised Plotly chart + summary."""
334
+
335
+ def _empty(msg: str) -> Tuple[go.Figure, str]:
336
+ fig = go.Figure()
337
+ fig.update_layout(
338
+ title=msg,
339
+ template="plotly_white",
340
+ paper_bgcolor="rgba(247,244,255,0.85)",
341
+ plot_bgcolor="rgba(255,255,255,0.95)",
342
+ height=420,
343
+ )
344
+ return fig, msg
345
+
346
+ if not YFINANCE_AVAILABLE:
347
+ return _empty("⚠️ yfinance not installed — add it to requirements.txt")
348
+
349
+ tickers = [t.strip().upper() for t in tickers_str.split(",") if t.strip()]
350
+ if not tickers:
351
+ return _empty("Please enter at least one ticker symbol.")
352
+
353
+ try:
354
+ raw = yf.download(tickers, period=period, auto_adjust=True, progress=False)
355
+
356
+ if raw.empty:
357
+ return _empty("No price data returned. Check ticker symbols.")
358
+
359
+ # Flatten: single ticker → single column
360
+ if len(tickers) == 1:
361
+ close = raw[["Close"]].rename(columns={"Close": tickers[0]})
362
+ else:
363
+ close = raw["Close"]
364
+
365
+ # Normalise to base 100
366
+ norm = close / close.iloc[0] * 100
367
+
368
+ palette = [
369
+ "#7c5cbf", "#2ec4a0", "#e8537a", "#e8a230", "#5e8fef",
370
+ "#c45ea8", "#3dbacc", "#a0522d", "#6aaa3a", "#d46060",
371
+ "#4a7fc1", "#8e6abf",
372
+ ]
373
+
374
+ fig = go.Figure()
375
+ for i, col in enumerate(norm.columns):
376
+ fig.add_trace(go.Scatter(
377
+ x=norm.index, y=norm[col].round(2),
378
+ name=str(col),
379
+ mode="lines",
380
+ line=dict(color=palette[i % len(palette)], width=2),
381
+ hovertemplate=(
382
+ f"<b>{col}</b><br>%{{x|%d %b %Y}}<br>"
383
+ "Index: %{y:.1f}<extra></extra>"
384
+ ),
385
+ ))
386
+
387
+ fig.add_hline(
388
+ y=100, line_dash="dot",
389
+ line_color="rgba(124,92,191,0.4)",
390
+ annotation_text="Base (100)",
391
+ annotation_position="bottom right",
392
+ )
393
+
394
+ fig.update_layout(
395
+ title=dict(
396
+ text="AI-Related Asset Prices — Normalised (base = 100 at start of period)",
397
+ font=dict(size=15, color="#4b2d8a", family="Syne, sans-serif"),
398
+ ),
399
+ template="plotly_white",
400
+ paper_bgcolor="rgba(247,244,255,0.85)",
401
+ plot_bgcolor="rgba(255,255,255,0.95)",
402
+ font=dict(color="#2d1f4e", family="Lato, sans-serif"),
403
+ height=460,
404
+ margin=dict(l=60, r=20, t=70, b=70),
405
+ legend=dict(
406
+ orientation="h",
407
+ yanchor="bottom", y=-0.22,
408
+ xanchor="center", x=0.5,
409
+ bgcolor="rgba(255,255,255,0.92)",
410
+ bordercolor="rgba(124,92,191,0.35)",
411
+ borderwidth=1,
412
+ ),
413
+ hovermode="x unified",
414
+ )
415
+ fig.update_xaxes(gridcolor="rgba(124,92,191,0.18)", showgrid=True)
416
+ fig.update_yaxes(gridcolor="rgba(124,92,191,0.18)", showgrid=True, title="Index (base 100)")
417
+
418
+ # Summary markdown
419
+ latest = close.iloc[-1]
420
+ first = close.iloc[0]
421
+ rows = []
422
+ for t in close.columns:
423
+ try:
424
+ chg = ((float(latest[t]) - float(first[t])) / float(first[t])) * 100
425
+ sign = "+" if chg >= 0 else ""
426
+ col = BULLISH if chg >= 0 else BEARISH
427
+ rows.append(
428
+ f"| **{t}** | ${float(latest[t]):.2f} "
429
+ f"| <span style='color:{col}'>{sign}{chg:.1f}%</span> |"
430
+ )
431
+ except Exception:
432
+ pass
433
+
434
+ summary = (
435
+ "| Ticker | Latest Price | Period Return |\n"
436
+ "|--------|:------------:|:-------------:|\n"
437
+ + "\n".join(rows)
438
+ ) if rows else "*(no data)*"
439
+
440
+ return fig, summary
441
+
442
+ except Exception as e:
443
+ return _empty(f"Error fetching prices: {e}")
444
+
445
+
446
+ # ══════════════════════════════════════════════════════════════════════
447
+ # SENTIMENT CHARTS (interactive Plotly)
448
+ # ══════════════════════════════════════════════════════════════════════
449
+
450
+ def _dark_layout(**kwargs) -> dict:
451
+ defaults = dict(
452
+ template="plotly_white",
453
+ paper_bgcolor="rgba(247,244,255,0.85)",
454
+ plot_bgcolor="rgba(255,255,255,0.95)",
455
+ font=dict(family="Lato, sans-serif", color="#2d1f4e", size=12),
456
+ margin=dict(l=60, r=20, t=70, b=70),
457
+ legend=dict(
458
+ orientation="h",
459
+ yanchor="bottom", y=1.02,
460
+ xanchor="right", x=1,
461
+ bgcolor="rgba(255,255,255,0.92)",
462
+ bordercolor="rgba(124,92,191,0.35)",
463
+ borderwidth=1,
464
+ ),
465
+ title=dict(font=dict(family="Syne, sans-serif", size=15, color="#4b2d8a")),
466
+ )
467
+ defaults.update(kwargs)
468
+ return defaults
469
+
470
+
471
+ def _grid_axes(fig: go.Figure, **kwargs):
472
+ fig.update_xaxes(gridcolor="rgba(124,92,191,0.18)", showgrid=True, **kwargs)
473
+ fig.update_yaxes(gridcolor="rgba(124,92,191,0.18)", showgrid=True)
474
+ return fig
475
+
476
+
477
+ def _empty_chart(title: str) -> go.Figure:
478
+ fig = go.Figure()
479
+ fig.update_layout(
480
+ title=title,
481
+ template="plotly_white",
482
+ paper_bgcolor="rgba(247,244,255,0.85)",
483
+ plot_bgcolor="rgba(255,255,255,0.95)",
484
+ font=dict(family="Lato, sans-serif", color="#2d1f4e"),
485
+ height=420,
486
+ annotations=[dict(
487
+ text="Run the pipeline to generate data",
488
+ x=0.5, y=0.5, xref="paper", yref="paper",
489
+ showarrow=False,
490
+ font=dict(size=14, color="rgba(124,92,191,0.5)",
491
+ family="Syne, sans-serif"),
492
+ )],
493
+ )
494
+ return fig
495
+
496
+
497
+ # ── KPI Cards ──────────────────────────────────────────────────────────
498
+
499
+ def load_kpis() -> Dict[str, Any]:
500
+ for candidate in [PY_TAB_DIR / "kpis.json"]:
501
+ if candidate.exists():
502
+ try:
503
+ return _read_json(candidate)
504
+ except Exception:
505
+ pass
506
+ return {}
507
+
508
+
509
+ def render_kpi_cards() -> str:
510
+ kpis = load_kpis()
511
+ if not kpis:
512
+ return (
513
+ '<div style="background:rgba(255,255,255,.65);backdrop-filter:blur(16px);'
514
+ 'border-radius:20px;padding:28px;text-align:center;'
515
+ 'border:1.5px solid rgba(255,255,255,.7);'
516
+ 'box-shadow:0 8px 32px rgba(124,92,191,.08);">'
517
+ '<div style="font-size:36px;margin-bottom:10px;">🫧</div>'
518
+ '<div style="font-family:\'Nunito\',sans-serif;color:#a48de8;font-size:14px;'
519
+ 'font-weight:800;margin-bottom:6px;">No data yet</div>'
520
+ '<div style="color:#9d8fc4;font-size:12px;font-family:\'Nunito\',sans-serif;">'
521
+ 'Run the Python analysis pipeline to populate these cards.</div>'
522
+ '</div>'
523
+ )
524
+
525
+ def card(icon, label, value, colour):
526
+ return f"""
527
+ <div style="background:rgba(255,255,255,.72);backdrop-filter:blur(16px);
528
+ border-radius:20px;padding:18px 14px 16px;text-align:center;
529
+ border:1.5px solid rgba(255,255,255,.8);
530
+ box-shadow:0 4px 16px rgba(124,92,191,.08);
531
+ border-top:3px solid {colour};
532
+ position:relative;overflow:hidden;">
533
+ <div style="font-size:26px;margin-bottom:7px;line-height:1;">{icon}</div>
534
+ <div style="font-family:'Nunito',sans-serif;color:#9d8fc4;
535
+ font-size:9.5px;text-transform:uppercase;
536
+ letter-spacing:1.8px;margin-bottom:7px;font-weight:800;">{label}</div>
537
+ <div style="font-family:'Syne',sans-serif;color:#2d1f4e;
538
+ font-size:16px;font-weight:800;letter-spacing:-.3px;">{value}</div>
539
+ </div>"""
540
+
541
+ cards = [
542
+ ("💬", "Comments", f"{kpis.get('total_comments','—'):,}", "#a48de8"),
543
+ ("📅", "Date Range", kpis.get("date_range","—"), "#7aa6f8"),
544
+ ("🌐", "Platforms", str(kpis.get("n_platforms","—")), "#6ee7c7"),
545
+ ("🏷️", "Topics", str(kpis.get("n_topics","—")), "#3dcba8"),
546
+ ("🐂", "Bullish", f"{kpis.get('pct_bullish','—')}%", "#3dcba8"),
547
+ ("🐻", "Bearish", f"{kpis.get('pct_bearish','—')}%", "#ff6b8a"),
548
+ ("⚠️", "Bubble Risk", f"{kpis.get('latest_bubble_risk','—')}", "#ffb347"),
549
+ ("🔬", "Chi² p-value", f"{kpis.get('chi2_p_value','—')}", "#8fa8f8"),
550
+ ]
551
+
552
+ html = (
553
+ '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));'
554
+ 'gap:12px;margin-bottom:24px;">'
555
+ )
556
+ for icon, label, value, colour in cards:
557
+ html += card(icon, label, value, colour)
558
+ html += "</div>"
559
+ return html
560
+
561
+
562
+ # ── Overview chart (monthly sentiment over time) ────────────────────
563
+
564
+ def build_overview_chart() -> go.Figure:
565
+ path = PY_TAB_DIR / "monthly_sentiment.csv"
566
+ if not path.exists():
567
+ return _empty_chart("Sentiment Over Time — data not yet available")
568
+
569
+ df = pd.read_csv(path)
570
+ df["month"] = pd.to_datetime(df["month"])
571
+
572
+ fig = make_subplots(
573
+ rows=2, cols=1, shared_xaxes=True,
574
+ subplot_titles=(
575
+ "Monthly Comment Volume by Sentiment",
576
+ "3-Month Rolling Average Sentiment Score",
577
+ ),
578
+ vertical_spacing=0.14,
579
+ row_heights=[0.62, 0.38],
580
+ )
581
+
582
+ for sentiment, colour in [("bullish", BULLISH), ("neutral", NEUTRAL), ("bearish", BEARISH)]:
583
+ if sentiment not in df.columns:
584
+ continue
585
+ r, g, b = int(colour[1:3], 16), int(colour[3:5], 16), int(colour[5:7], 16)
586
+ fig.add_trace(go.Scatter(
587
+ x=df["month"], y=df[sentiment],
588
+ name=sentiment.title(),
589
+ mode="lines",
590
+ stackgroup="one",
591
+ line=dict(color=colour, width=0.5),
592
+ fillcolor=f"rgba({r},{g},{b},0.7)",
593
+ hovertemplate=f"<b>{sentiment.title()}</b>: %{{y}}<extra></extra>",
594
+ ), row=1, col=1)
595
+
596
+ if "avg_score" in df.columns:
597
+ rolling = df["avg_score"].rolling(3, min_periods=1).mean()
598
+ fig.add_trace(go.Scatter(
599
+ x=df["month"], y=rolling.round(3),
600
+ name="3-mo avg score",
601
+ mode="lines",
602
+ line=dict(color="#7c5cbf", width=2.5),
603
+ hovertemplate="Score: %{y:.2f}<extra></extra>",
604
+ ), row=2, col=1)
605
+ fig.add_hline(
606
+ y=0, line_dash="dot",
607
+ line_color="rgba(124,92,191,0.35)",
608
+ row=2, col=1,
609
+ )
610
+
611
+ fig.update_layout(**_dark_layout(height=580, hovermode="x unified"))
612
+ _grid_axes(fig)
613
+ return fig
614
+
615
+
616
+ # ── Topic breakdown chart ──────────────────────────────────────────
617
+
618
+ def build_topic_chart() -> go.Figure:
619
+ path = PY_TAB_DIR / "sentiment_by_topic.csv"
620
+ if not path.exists():
621
+ return _empty_chart("Sentiment by Topic — data not yet available")
622
+
623
+ df = pd.read_csv(path)
624
+ if "Topic" not in df.columns:
625
+ return _empty_chart("Unexpected CSV format for sentiment_by_topic.csv")
626
+
627
+ cols = [c for c in ["bullish", "neutral", "bearish"] if c in df.columns]
628
+ totals = df[cols].sum(axis=1).replace(0, 1)
629
+
630
+ fig = go.Figure()
631
+ for sentiment, colour in [("bullish", BULLISH), ("neutral", NEUTRAL), ("bearish", BEARISH)]:
632
+ if sentiment not in df.columns:
633
+ continue
634
+ pct = (df[sentiment] / totals * 100).round(1)
635
+ fig.add_trace(go.Bar(
636
+ name=sentiment.title(),
637
+ x=df["Topic"], y=pct,
638
+ marker_color=colour,
639
+ hovertemplate=f"<b>{sentiment.title()}</b><br>%{{x}}: %{{y:.1f}}%<extra></extra>",
640
+ ))
641
+
642
+ fig.update_layout(
643
+ **_dark_layout(
644
+ barmode="stack",
645
+ title="Sentiment Distribution by Topic (%)",
646
+ height=420,
647
+ yaxis_title="% of Comments",
648
+ )
649
+ )
650
+ _grid_axes(fig)
651
+ return fig
652
+
653
+
654
+ # ── Platform breakdown chart ───────────────────────────────────────
655
+
656
+ def build_platform_chart() -> go.Figure:
657
+ path = PY_TAB_DIR / "sentiment_by_platform.csv"
658
+ if not path.exists():
659
+ return _empty_chart("Sentiment by Platform — data not yet available")
660
+
661
+ df = pd.read_csv(path)
662
+ if "Platform" not in df.columns:
663
+ return _empty_chart("Unexpected CSV format for sentiment_by_platform.csv")
664
+
665
+ cols = [c for c in ["bullish", "neutral", "bearish"] if c in df.columns]
666
+ totals = df[cols].sum(axis=1).replace(0, 1)
667
+
668
+ fig = go.Figure()
669
+ for sentiment, colour in [("bullish", BULLISH), ("neutral", NEUTRAL), ("bearish", BEARISH)]:
670
+ if sentiment not in df.columns:
671
+ continue
672
+ pct = (df[sentiment] / totals * 100).round(1)
673
+ fig.add_trace(go.Bar(
674
+ name=sentiment.title(),
675
+ x=df["Platform"], y=pct,
676
+ marker_color=colour,
677
+ hovertemplate=f"<b>{sentiment.title()}</b><br>%{{x}}: %{{y:.1f}}%<extra></extra>",
678
+ ))
679
+
680
+ fig.update_layout(
681
+ **_dark_layout(
682
+ barmode="stack",
683
+ title="Sentiment Distribution by Platform (%)",
684
+ height=420,
685
+ yaxis_title="% of Comments",
686
+ )
687
+ )
688
+ _grid_axes(fig)
689
+ return fig
690
+
691
+
692
+ # ── Bubble risk chart ──────────────────────────────────────────────
693
+
694
+ def build_bubble_risk_chart() -> go.Figure:
695
+ path = PY_TAB_DIR / "bubble_risk_score.csv"
696
+ if not path.exists():
697
+ return _empty_chart("Bubble Risk Score — data not yet available")
698
+
699
+ df = pd.read_csv(path)
700
+ if "month" not in df.columns or "bubble_risk_score" not in df.columns:
701
+ return _empty_chart("Unexpected CSV format for bubble_risk_score.csv")
702
+
703
+ df["month"] = pd.to_datetime(df["month"])
704
+ score = df["bubble_risk_score"]
705
+
706
+ fig = go.Figure()
707
+
708
+ # Shaded area: bullish zone (score < 0.5)
709
+ fig.add_trace(go.Scatter(
710
+ x=df["month"], y=score.clip(upper=0.5),
711
+ mode="none",
712
+ fill="tozeroy",
713
+ fillcolor=f"rgba({int(BULLISH[1:3],16)},{int(BULLISH[3:5],16)},{int(BULLISH[5:7],16)},0.15)",
714
+ name="Bullish zone",
715
+ showlegend=False,
716
+ hoverinfo="skip",
717
+ ))
718
+
719
+ # Shaded area: bearish zone (score > 0.5)
720
+ base = pd.Series([0.5] * len(df), index=df.index)
721
+ fig.add_trace(go.Scatter(
722
+ x=df["month"], y=score.clip(lower=0.5),
723
+ mode="none",
724
+ fill="tonexty",
725
+ fillcolor=f"rgba({int(BEARISH[1:3],16)},{int(BEARISH[3:5],16)},{int(BEARISH[5:7],16)},0.15)",
726
+ name="Bearish zone",
727
+ showlegend=False,
728
+ hoverinfo="skip",
729
+ ))
730
+
731
+ # Main line
732
+ fig.add_trace(go.Scatter(
733
+ x=df["month"], y=score.round(3),
734
+ name="Bubble Risk Score",
735
+ mode="lines+markers",
736
+ line=dict(color="#7c5cbf", width=2.5),
737
+ marker=dict(size=5),
738
+ hovertemplate="Risk: %{y:.3f}<extra></extra>",
739
+ ))
740
+
741
+ fig.add_hline(
742
+ y=0.5, line_dash="dot",
743
+ line_color="rgba(124,92,191,0.5)",
744
+ annotation_text="Neutral threshold",
745
+ annotation_position="top right",
746
+ annotation_font_color="#7c5cbf",
747
+ )
748
+
749
+ fig.update_layout(
750
+ **_dark_layout(
751
+ title="AI Bubble Risk Score (0 = all bullish · 1 = all bearish)",
752
+ height=420,
753
+ hovermode="x unified",
754
+ yaxis=dict(range=[0, 1], title="Risk Score"),
755
+ )
756
+ )
757
+ _grid_axes(fig)
758
+ return fig
759
+
760
+
761
+ # ── Yearly chart ───────────────────────────────────────────────────
762
+
763
+ def build_yearly_chart() -> go.Figure:
764
+ path = PY_TAB_DIR / "yearly_sentiment.csv"
765
+ if not path.exists():
766
+ return _empty_chart("Yearly Sentiment — data not yet available")
767
+
768
+ df = pd.read_csv(path)
769
+ year_col = [c for c in ["Year", "year", "Year_num"] if c in df.columns]
770
+ if not year_col:
771
+ return _empty_chart("No year column found")
772
+ year_col = year_col[0]
773
+
774
+ cols = [c for c in ["bullish", "neutral", "bearish"] if c in df.columns]
775
+ totals = df[cols].sum(axis=1).replace(0, 1)
776
+
777
+ fig = go.Figure()
778
+ for sentiment, colour in [("bullish", BULLISH), ("neutral", NEUTRAL), ("bearish", BEARISH)]:
779
+ if sentiment not in df.columns:
780
+ continue
781
+ pct = (df[sentiment] / totals * 100).round(1)
782
+ fig.add_trace(go.Bar(
783
+ name=sentiment.title(),
784
+ x=df[year_col].astype(str), y=pct,
785
+ marker_color=colour,
786
+ hovertemplate=f"<b>{sentiment.title()}</b><br>%{{x}}: %{{y:.1f}}%<extra></extra>",
787
+ ))
788
+
789
+ fig.update_layout(
790
+ **_dark_layout(
791
+ barmode="stack",
792
+ title="Sentiment Share by Year (%)",
793
+ height=400,
794
+ yaxis_title="% of Comments",
795
+ )
796
+ )
797
+ _grid_axes(fig)
798
+ return fig
799
+
800
+
801
+ # ── Static R figures ───────────────────────────────────────────────
802
+
803
+ def _r_fig(name: str) -> Optional[str]:
804
+ p = R_FIG_DIR / name
805
+ return str(p) if p.exists() else None
806
+
807
+
808
+ # ── Full sentiment refresh ─────────────────────────────────────────
809
+
810
+ def refresh_sentiment():
811
+ return (
812
+ render_kpi_cards(),
813
+ build_overview_chart(),
814
+ build_topic_chart(),
815
+ build_platform_chart(),
816
+ build_bubble_risk_chart(),
817
+ build_yearly_chart(),
818
+ _r_fig("r01_monthly_sentiment_trend.png"),
819
+ _r_fig("r02_rolling_sentiment_score.png"),
820
+ _r_fig("r03_chi_square_residuals.png"),
821
+ _r_fig("r04_regression_coefficients.png"),
822
+ _r_fig("r05_yearly_grouped_bars.png"),
823
+ )
824
+
825
+
826
+ # ══════════════════════════════════════════════════════════════════════
827
+ # AI CHAT
828
+ # ══════════════════════════════════════════════════════════════════════
829
+
830
+ DASHBOARD_SYSTEM = """You are a sharp, concise analytics assistant for **BubbleBusters**
831
+ — an AI Bubble Sentiment Analytics dashboard built for ESCP Europe (RX12).
832
+
833
+ The dataset contains online comments about whether AI is a "bubble", scraped from
834
+ platforms like HackerNews, Twitter/X, and Reddit. Each comment is classified as:
835
+ - **bullish** (AI is real / valuable / here to stay)
836
+ - **neutral** (balanced / uncertain)
837
+ - **bearish** (AI is overhyped / a bubble / will crash)
838
+
839
+ Topics covered: hype, investment, productivity, skepticism.
840
+
841
+ AVAILABLE ARTIFACTS:
842
+ {artifacts_json}
843
+
844
+ KEY METRICS:
845
+ {kpis_json}
846
+
847
+ INSTRUCTIONS:
848
+ 1. Answer in 2-4 concise sentences.
849
+ 2. At the END of every response, output exactly one JSON block specifying what chart to show:
850
+ ```json
851
+ {{"show": "chart", "chart_type": "overview"}}
852
+ ```
853
+ chart_type must be one of: "overview", "topic", "platform", "risk", "yearly", "none"
854
+
855
+ ROUTING RULES:
856
+ - Trends over time / monthly / rolling → "overview"
857
+ - Topics / hype / investment / skepticism / productivity → "topic"
858
+ - Platforms / HackerNews / Twitter / Reddit → "platform"
859
+ - Bubble risk / danger / fear score → "risk"
860
+ - Year-over-year / annual → "yearly"
861
+ - General / unclear → "none"
862
+ """
863
+
864
+ _JSON_RE = re.compile(r"```json\s*(\{.*?\})\s*```", re.DOTALL)
865
+
866
+
867
+ def _parse_directive(text: str) -> Dict[str, str]:
868
+ m = _JSON_RE.search(text)
869
+ if m:
870
+ try:
871
+ return json.loads(m.group(1))
872
+ except Exception:
873
+ pass
874
+ return {"show": "none"}
875
+
876
+
877
+ def _clean(text: str) -> str:
878
+ return _JSON_RE.sub("", text).strip()
879
+
880
+
881
+ def _keyword_chat(msg: str, idx: Dict, kpis: Dict) -> Tuple[str, Dict]:
882
+ has_data = any(
883
+ idx[s]["figures"] or idx[s]["tables"] for s in ("python", "r")
884
+ )
885
+ if not has_data:
886
+ return (
887
+ "No analysis data found yet. Please run the pipeline first (⚙️ Pipeline tab).",
888
+ {"show": "none"},
889
+ )
890
+
891
+ ml = msg.lower()
892
+ kpi_line = ""
893
+ if kpis:
894
+ total = kpis.get("total_comments", 0)
895
+ kpi_line = (
896
+ f" The dataset contains **{total:,}** comments"
897
+ f" spanning {kpis.get('date_range', 'various dates')}."
898
+ )
899
+
900
+ if any(w in ml for w in ["risk", "bubble risk", "danger", "score"]):
901
+ return (
902
+ f"Here's the AI Bubble Risk Score over time.{kpi_line}",
903
+ {"show": "chart", "chart_type": "risk"},
904
+ )
905
+ if any(w in ml for w in ["year", "annual", "over year"]):
906
+ return (
907
+ f"Here's the year-over-year sentiment breakdown.{kpi_line}",
908
+ {"show": "chart", "chart_type": "yearly"},
909
+ )
910
+ if any(w in ml for w in ["topic", "hype", "investment", "productivity", "skepticism"]):
911
+ mb = kpis.get("most_bearish_topic", "")
912
+ mbu = kpis.get("most_bullish_topic", "")
913
+ extra = f" The most bearish topic is **{mb}** and the most bullish is **{mbu}**." if mb else ""
914
+ return (
915
+ f"Here's sentiment broken down by topic.{extra}{kpi_line}",
916
+ {"show": "chart", "chart_type": "topic"},
917
+ )
918
+ if any(w in ml for w in ["platform", "hackernews", "twitter", "reddit", "source"]):
919
+ dom = kpis.get("dominant_platform", "")
920
+ extra = f" The dominant platform is **{dom}**." if dom else ""
921
+ return (
922
+ f"Here's sentiment broken down by platform.{extra}{kpi_line}",
923
+ {"show": "chart", "chart_type": "platform"},
924
+ )
925
+ if any(w in ml for w in ["trend", "time", "monthly", "over time", "evolution", "sentiment"]):
926
+ risk = kpis.get("latest_bubble_risk", "")
927
+ extra = f" The latest 3-month bubble risk score is **{risk}**." if risk else ""
928
+ return (
929
+ f"Here are sentiment trends over time.{extra}{kpi_line}",
930
+ {"show": "chart", "chart_type": "overview"},
931
+ )
932
+
933
+ bearish = kpis.get("pct_bearish", "?")
934
+ bullish = kpis.get("pct_bullish", "?")
935
+ neutral = kpis.get("pct_neutral", "?")
936
+ return (
937
+ f"Overall: **{bullish}%** bullish · **{neutral}%** neutral · **{bearish}%** bearish.{kpi_line}\n\n"
938
+ "Try: *'Show sentiment trends'*, *'Which topics are most bearish?'*, "
939
+ "*'Compare platforms'*, *'What's the bubble risk?'*",
940
+ {"show": "none"},
941
+ )
942
+
943
+
944
+ def _directive_to_chart(directive: Dict) -> Optional[go.Figure]:
945
+ ct = directive.get("chart_type", "none")
946
+ if directive.get("show") != "chart" or ct == "none":
947
+ return None
948
+ return {
949
+ "overview": build_overview_chart,
950
+ "topic": build_topic_chart,
951
+ "platform": build_platform_chart,
952
+ "risk": build_bubble_risk_chart,
953
+ "yearly": build_yearly_chart,
954
+ }.get(ct, lambda: None)()
955
+
956
+
957
+ def ai_chat(user_msg: str, history: list):
958
+ if not user_msg or not user_msg.strip():
959
+ return history, "", None
960
+
961
+ idx = artifacts_index()
962
+ kpis = load_kpis()
963
+
964
+ if not LLM_ENABLED:
965
+ reply, directive = _keyword_chat(user_msg, idx, kpis)
966
+ else:
967
+ system = DASHBOARD_SYSTEM.format(
968
+ artifacts_json=json.dumps(idx, indent=2),
969
+ kpis_json=json.dumps(kpis, indent=2) if kpis else "(no KPIs — run pipeline first)",
970
+ )
971
+ msgs = [{"role": "system", "content": system}]
972
+ for entry in (history or [])[-6:]:
973
+ if isinstance(entry, dict) and "role" in entry:
974
+ msgs.append(entry)
975
+ msgs.append({"role": "user", "content": user_msg})
976
+
977
+ try:
978
+ r = llm_client.chat_completion(
979
+ model=MODEL_NAME,
980
+ messages=msgs,
981
+ temperature=0.3,
982
+ max_tokens=600,
983
+ stream=False,
984
+ )
985
+ raw = (
986
+ r["choices"][0]["message"]["content"]
987
+ if isinstance(r, dict)
988
+ else r.choices[0].message.content
989
+ )
990
+ directive = _parse_directive(raw)
991
+ reply = _clean(raw)
992
+ except Exception as e:
993
+ fallback_reply, directive = _keyword_chat(user_msg, idx, kpis)
994
+ reply = f"*(LLM error: {e})*\n\n{fallback_reply}"
995
+
996
+ chart_out = _directive_to_chart(directive)
997
+
998
+ new_history = list(history or []) + [
999
+ {"role": "user", "content": user_msg},
1000
+ {"role": "assistant", "content": reply},
1001
+ ]
1002
+ return new_history, "", chart_out
1003
+
1004
+
1005
+ # ══════════════════════════════════════════════════════════════════════
1006
+ # CSS — Bloomberg-terminal-inspired dark theme
1007
+ # ══════════════════════════════════════════════════════════════════════
1008
+
1009
+ CSS = (BASE_DIR / "style.css").read_text(encoding="utf-8")
1010
+
1011
+
1012
+
1013
+ # ══════════════════════════════════════════════════════════════════════
1014
+ # GRADIO APP
1015
+ # ══════════════════════════════════════════════════════════════════════
1016
+
1017
+ ensure_dirs()
1018
+
1019
+ with gr.Blocks(title="BubbleBusters — AI Bubble Analytics") as demo:
1020
+
1021
+ # ── Master header ──────────────────────────────────────────────
1022
+ gr.HTML("""
1023
+ <div id="bb-header">
1024
+
1025
+ <!-- Floating soap bubbles (CSS-only) -->
1026
+ <div aria-hidden="true" style="position:absolute;inset:0;pointer-events:none;overflow:hidden;">
1027
+
1028
+ <!-- Big iridescent bubble left -->
1029
+ <div style="position:absolute;width:72px;height:72px;border-radius:50%;
1030
+ left:4%;top:10%;
1031
+ background: radial-gradient(circle at 35% 30%,
1032
+ rgba(255,255,255,.7) 0%,
1033
+ rgba(197,180,240,.3) 35%,
1034
+ rgba(110,231,199,.15) 65%,
1035
+ rgba(168,216,240,.08) 100%);
1036
+ border: 1.5px solid rgba(255,255,255,.8);
1037
+ box-shadow: inset 0 0 16px rgba(197,180,240,.4),
1038
+ 0 4px 20px rgba(197,180,240,.2);
1039
+ animation: floatBubble 6.5s ease-in-out infinite;"></div>
1040
+
1041
+ <!-- Small bubble top-left cluster -->
1042
+ <div style="position:absolute;width:32px;height:32px;border-radius:50%;
1043
+ left:9%;top:55%;
1044
+ background: radial-gradient(circle at 35% 30%,
1045
+ rgba(255,255,255,.65) 0%, rgba(255,179,200,.25) 50%, transparent 100%);
1046
+ border: 1.5px solid rgba(255,255,255,.75);
1047
+ animation: floatBubble 5.1s ease-in-out infinite 0.8s;"></div>
1048
+
1049
+ <div style="position:absolute;width:18px;height:18px;border-radius:50%;
1050
+ left:13%;top:75%;
1051
+ background: radial-gradient(circle at 35% 30%,
1052
+ rgba(255,255,255,.6) 0%, rgba(110,231,199,.3) 60%, transparent 100%);
1053
+ border: 1px solid rgba(255,255,255,.7);
1054
+ animation: floatBubble 4.4s ease-in-out infinite 1.4s;"></div>
1055
+
1056
+ <!-- Right side bubbles -->
1057
+ <div style="position:absolute;width:56px;height:56px;border-radius:50%;
1058
+ right:5%;top:8%;
1059
+ background: radial-gradient(circle at 35% 30%,
1060
+ rgba(255,255,255,.65) 0%, rgba(168,216,240,.3) 50%, transparent 100%);
1061
+ border: 1.5px solid rgba(255,255,255,.75);
1062
+ box-shadow: inset 0 0 12px rgba(168,216,240,.3),
1063
+ 0 4px 16px rgba(168,216,240,.15);
1064
+ animation: floatBubble 7.2s ease-in-out infinite 1.9s;"></div>
1065
+
1066
+ <div style="position:absolute;width:24px;height:24px;border-radius:50%;
1067
+ right:11%;top:60%;
1068
+ background: radial-gradient(circle at 35% 30%,
1069
+ rgba(255,255,255,.6) 0%, rgba(143,168,248,.25) 60%, transparent 100%);
1070
+ border: 1px solid rgba(255,255,255,.65);
1071
+ animation: floatBubble 5.8s ease-in-out infinite 0.5s;"></div>
1072
+
1073
+ <!-- Centre accent bubble -->
1074
+ <div style="position:absolute;width:14px;height:14px;border-radius:50%;
1075
+ left:50%;top:15%;
1076
+ background: rgba(255,255,255,.55);
1077
+ border: 1px solid rgba(255,255,255,.7);
1078
+ animation: floatBubble 4.0s ease-in-out infinite 2.2s;"></div>
1079
+ </div>
1080
+
1081
+ <!-- Content -->
1082
+ <div style="position:relative;z-index:1;display:flex;align-items:center;
1083
+ gap:22px;flex-wrap:wrap;">
1084
+
1085
+ <!-- Logo bubble -->
1086
+ <div style="width:72px;height:72px;border-radius:50%;flex-shrink:0;
1087
+ background: radial-gradient(circle at 35% 30%,
1088
+ rgba(255,255,255,.85) 0%,
1089
+ rgba(197,180,240,.45) 40%,
1090
+ rgba(110,231,199,.25) 75%,
1091
+ transparent 100%);
1092
+ border: 2px solid rgba(255,255,255,.8);
1093
+ display:flex;align-items:center;justify-content:center;
1094
+ font-size:34px;
1095
+ box-shadow: inset 0 0 24px rgba(197,180,240,.4),
1096
+ 0 8px 28px rgba(124,92,191,.2),
1097
+ 0 2px 8px rgba(124,92,191,.1);
1098
+ animation: floatBubble 5.5s ease-in-out infinite, iridescent 8s linear infinite;">
1099
+ 🫧
1100
+ </div>
1101
+
1102
+ <!-- Title block -->
1103
+ <div>
1104
+ <h1 style="margin:0;font-family:'Syne',sans-serif;font-size:34px;
1105
+ font-weight:800;letter-spacing:-1px;line-height:1;
1106
+ background: linear-gradient(125deg,
1107
+ #7c5cbf 0%, #a48de8 35%, #6ee7c7 65%, #7c5cbf 100%);
1108
+ background-size: 300% auto;
1109
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;
1110
+ animation: shimmerSlide 5s linear infinite;">
1111
+ BubbleBusters
1112
+ </h1>
1113
+ <p style="margin:8px 0 0;color:#9d8fc4;
1114
+ font-family:'Nunito',sans-serif;font-size:13.5px;
1115
+ letter-spacing:.4px;font-weight:600;">
1116
+ AI Bubble Sentiment Analytics &nbsp;·&nbsp; RX12 &nbsp;·&nbsp; ESCP Europe
1117
+ </p>
1118
+ </div>
1119
+
1120
+ <!-- Right pill badges -->
1121
+ <div style="margin-left:auto;display:flex;flex-direction:column;gap:7px;align-items:flex-end;">
1122
+ <div style="background:linear-gradient(135deg,rgba(197,180,240,.35),rgba(110,231,199,.25));
1123
+ border:1.5px solid rgba(255,255,255,.65);border-radius:50px;
1124
+ padding:5px 14px;font-family:'Nunito',sans-serif;
1125
+ font-size:11px;font-weight:800;color:#6b4fa8;letter-spacing:.5px;">
1126
+ 🎓 Group Project
1127
+ </div>
1128
+ <div style="background:rgba(255,255,255,.5);border:1.5px solid rgba(255,255,255,.65);
1129
+ border-radius:50px;padding:5px 14px;
1130
+ font-family:'Nunito',sans-serif;font-size:11px;
1131
+ font-weight:700;color:#9d8fc4;letter-spacing:.3px;">
1132
+ 🐍 Python &nbsp;·&nbsp; 📊 R &nbsp;·&nbsp; 🤖 AI
1133
+ </div>
1134
+ </div>
1135
+
1136
+ </div>
1137
+ </div>
1138
+ """)
1139
+
1140
+ with gr.Tabs():
1141
+
1142
+ # ══════════════════════════════════════════════════════════
1143
+ # TAB 1 — PIPELINE
1144
+ # ══════════════════════════════════════════════════════════
1145
+ with gr.Tab("⚙️ Pipeline"):
1146
+
1147
+ with gr.Row(equal_height=False):
1148
+
1149
+ # Status column
1150
+ with gr.Column(scale=1, min_width=280):
1151
+ gr.HTML('<div class="section-label">System Status</div>')
1152
+ status_html = gr.HTML(value=render_status_html)
1153
+ refresh_status_btn = gr.Button(
1154
+ "🔄 Refresh", elem_classes=["btn-secondary"]
1155
+ )
1156
+
1157
+ # Runner column
1158
+ with gr.Column(scale=2):
1159
+ gr.HTML('<div class="section-label">Run Pipeline</div>')
1160
+
1161
+ if PY_KERNEL:
1162
+ gr.HTML(
1163
+ f'<div style="background:rgba(34,232,120,.07);'
1164
+ f'border:1px solid rgba(34,232,120,.25);'
1165
+ f'border-radius:10px;padding:10px 16px;font-size:12px;'
1166
+ f'color:#22e878;margin-bottom:14px;'
1167
+ f'font-family:\'Lato\',sans-serif;">'
1168
+ f'✦ Notebook kernel ready &nbsp;·&nbsp; '
1169
+ f'<code style="font-family:\'JetBrains Mono\',monospace;">'
1170
+ f'{PY_KERNEL}</code></div>'
1171
+ )
1172
+ else:
1173
+ gr.HTML(
1174
+ f'<div style="background:rgba(255,87,112,.07);'
1175
+ f'border:1px solid rgba(255,87,112,.25);'
1176
+ f'border-radius:10px;padding:10px 16px;font-size:12px;'
1177
+ f'color:#ff5770;margin-bottom:14px;'
1178
+ f'font-family:\'Lato\',sans-serif;">'
1179
+ f'✖ Kernel unavailable — add '
1180
+ f'<code style="font-family:\'JetBrains Mono\',monospace;">'
1181
+ f'ipykernel</code> to requirements.txt<br>'
1182
+ f'<span style="color:rgba(240,237,230,.30);font-size:11px;">'
1183
+ f'{KERNEL_INIT_ERROR[:180]}</span>'
1184
+ f'</div>'
1185
+ )
1186
+
1187
+ with gr.Row():
1188
+ btn_nb1 = gr.Button("📦 Step 1: Data", elem_classes=["btn-secondary"])
1189
+ btn_nb2 = gr.Button("🐍 Step 2a: Python", elem_classes=["btn-secondary"])
1190
+ btn_r = gr.Button("📊 Step 2b: R", elem_classes=["btn-secondary"])
1191
+
1192
+ btn_all = gr.Button("🚀 Run Full Pipeline", elem_classes=["btn-primary"])
1193
+
1194
+ run_log = gr.Textbox(
1195
+ label="Execution Log",
1196
+ lines=18, max_lines=18,
1197
+ interactive=False,
1198
+ elem_id="pipeline-log",
1199
+ autoscroll=True,
1200
+ )
1201
+
1202
+ refresh_status_btn.click(fn=render_status_html, outputs=status_html)
1203
+ btn_nb1.click(fn=run_datacreation, outputs=run_log)
1204
+ btn_nb2.click(fn=run_pythonanalysis, outputs=run_log)
1205
+ btn_r.click(fn=run_r, outputs=run_log)
1206
+ btn_all.click(fn=run_full_pipeline, outputs=run_log)
1207
+
1208
+ # ══════════════════════════════════════════════════════════
1209
+ # TAB 2 — ASSET PRICES
1210
+ # ══════════════════════════════════════════════════════════
1211
+ with gr.Tab("📈 Asset Prices"):
1212
+ gr.HTML(
1213
+ '<div style="color:#5a5a7a;font-size:13px;margin-bottom:16px;">'
1214
+ 'Track AI-related stocks in real time. Select tickers and a period '
1215
+ 'to compare normalised performance (base = 100).</div>'
1216
+ )
1217
+
1218
+ with gr.Row():
1219
+ with gr.Column(scale=3):
1220
+ ticker_box = gr.Textbox(
1221
+ label="Tickers (comma-separated)",
1222
+ value=AI_TICKERS_DEFAULT,
1223
+ placeholder="e.g. NVDA, MSFT, GOOGL, META",
1224
+ )
1225
+ with gr.Column(scale=1):
1226
+ period_radio = gr.Radio(
1227
+ choices=["1mo", "3mo", "6mo", "1y", "2y", "5y"],
1228
+ value="6mo",
1229
+ label="Period",
1230
+ )
1231
+ with gr.Column(scale=1):
1232
+ fetch_btn = gr.Button("📡 Fetch Prices", elem_classes=["btn-primary"])
1233
+
1234
+ gr.HTML('<div style="color:#3a3a5a;font-size:11px;margin:6px 0 4px;">Quick presets:</div>')
1235
+ with gr.Row():
1236
+ preset_mega = gr.Button("🏦 Mega-Cap AI", elem_classes=["btn-secondary"])
1237
+ preset_semi = gr.Button("🔧 Semiconductors", elem_classes=["btn-secondary"])
1238
+ preset_pure = gr.Button("🤖 Pure-Play AI", elem_classes=["btn-secondary"])
1239
+
1240
+ with gr.Row(equal_height=False):
1241
+ with gr.Column(scale=3):
1242
+ price_chart = gr.Plot(label="", container=False)
1243
+ with gr.Column(scale=1, min_width=220):
1244
+ price_summary = gr.Markdown()
1245
+
1246
+ fetch_btn.click(
1247
+ fn=lambda t, p: fetch_asset_prices(t, p),
1248
+ inputs=[ticker_box, period_radio],
1249
+ outputs=[price_chart, price_summary],
1250
+ )
1251
+ preset_mega.click(
1252
+ fn=lambda p: fetch_asset_prices(AI_PRESET_MEGA, p),
1253
+ inputs=period_radio, outputs=[price_chart, price_summary],
1254
+ )
1255
+ preset_semi.click(
1256
+ fn=lambda p: fetch_asset_prices(AI_PRESET_SEMI, p),
1257
+ inputs=period_radio, outputs=[price_chart, price_summary],
1258
+ )
1259
+ preset_pure.click(
1260
+ fn=lambda p: fetch_asset_prices(AI_PRESET_PURE, p),
1261
+ inputs=period_radio, outputs=[price_chart, price_summary],
1262
+ )
1263
+
1264
+ # ══════════════════════════════════════════════════════════
1265
+ # TAB 3 — SENTIMENT ANALYSIS
1266
+ # ══════════════════════════════════════════════════════════
1267
+ with gr.Tab("🎭 Sentiment Analysis"):
1268
+ gr.HTML(
1269
+ '<div style="color:#5a5a7a;font-size:13px;margin-bottom:14px;">'
1270
+ 'Interactive charts and R figures from the full analysis pipeline. '
1271
+ 'Run the pipeline first if charts are empty.</div>'
1272
+ )
1273
+
1274
+ with gr.Row():
1275
+ refresh_sent_btn = gr.Button("🔄 Refresh All Charts", elem_classes=["btn-secondary"])
1276
+
1277
+ # KPI cards
1278
+ kpi_html_comp = gr.HTML(value=render_kpi_cards)
1279
+
1280
+ # Main interactive charts
1281
+ overview_chart_comp = gr.Plot(label="Sentiment Over Time", container=False)
1282
+
1283
+ with gr.Row():
1284
+ with gr.Column():
1285
+ topic_chart_comp = gr.Plot(label="By Topic", container=False)
1286
+ with gr.Column():
1287
+ platform_chart_comp = gr.Plot(label="By Platform", container=False)
1288
+
1289
+ with gr.Row():
1290
+ with gr.Column():
1291
+ risk_chart_comp = gr.Plot(label="Bubble Risk Score", container=False)
1292
+ with gr.Column():
1293
+ yearly_chart_comp = gr.Plot(label="Year-over-Year", container=False)
1294
+
1295
+ # R figures (static) inside accordion
1296
+ with gr.Accordion("📊 R Analysis Figures (static)", open=False):
1297
+ gr.HTML(
1298
+ '<div style="color:#5a5a7a;font-size:12px;margin-bottom:10px;">'
1299
+ 'Generated by ranalysis_bubblebusters.ipynb (run locally).'
1300
+ '</div>'
1301
+ )
1302
+ with gr.Row():
1303
+ r1 = gr.Image(label="R01 · Monthly Trend", show_label=True)
1304
+ r2 = gr.Image(label="R02 · Rolling Score", show_label=True)
1305
+ with gr.Row():
1306
+ r3 = gr.Image(label="R03 · Chi-Square Residuals", show_label=True)
1307
+ r4 = gr.Image(label="R04 · Regression Coefficients", show_label=True)
1308
+ with gr.Row():
1309
+ r5 = gr.Image(label="R05 · Yearly Grouped Bars", show_label=True)
1310
+
1311
+ SENT_OUTPUTS = [
1312
+ kpi_html_comp,
1313
+ overview_chart_comp, topic_chart_comp,
1314
+ platform_chart_comp, risk_chart_comp, yearly_chart_comp,
1315
+ r1, r2, r3, r4, r5,
1316
+ ]
1317
+
1318
+ refresh_sent_btn.click(fn=refresh_sentiment, outputs=SENT_OUTPUTS)
1319
+
1320
+ # ══════════════════════════════════════════════════════════
1321
+ # TAB 4 — AI CHAT
1322
+ # ══════════════════════════════════════════════════════════
1323
+ with gr.Tab("🤖 AI Chat"):
1324
+ llm_badge = (
1325
+ f'<span style="color:#22e878;font-family:\'Syne\',sans-serif;font-weight:700;">'
1326
+ f'✦ LLM active — {MODEL_NAME}</span>'
1327
+ if LLM_ENABLED else
1328
+ f'<span style="color:#ffb830;font-family:\'Syne\',sans-serif;font-weight:700;">'
1329
+ f'◈ Keyword mode — set <code>HF_API_KEY</code> secret for full AI support</span>'
1330
+ )
1331
+ gr.HTML(
1332
+ f'<div style="color:#5a5a7a;font-size:13px;margin-bottom:14px;">'
1333
+ f'Ask questions about the AI bubble data. {llm_badge}</div>'
1334
+ )
1335
+
1336
+ with gr.Row(equal_height=True):
1337
+ with gr.Column(scale=1):
1338
+ chatbot = gr.Chatbot(
1339
+ label="Conversation",
1340
+ height=430
1341
+ )
1342
+ user_msg = gr.Textbox(
1343
+ label="Ask about the data",
1344
+ placeholder=(
1345
+ "e.g. Show me sentiment trends · "
1346
+ "Which topics are most bearish? · "
1347
+ "What's the current bubble risk?"
1348
+ ),
1349
+ lines=1,
1350
+ )
1351
+ gr.Examples(
1352
+ examples=[
1353
+ "Show me sentiment trends over time",
1354
+ "Which topics are most bearish about AI?",
1355
+ "Compare sentiment across platforms",
1356
+ "What is the latest bubble risk score?",
1357
+ "Is sentiment getting more bullish or bearish recently?",
1358
+ "Give me an overview of the dataset",
1359
+ ],
1360
+ inputs=user_msg,
1361
+ )
1362
+
1363
+ with gr.Column(scale=1):
1364
+ chat_chart = gr.Plot(label="Data Visualisation", container=False)
1365
+
1366
+ user_msg.submit(
1367
+ fn=ai_chat,
1368
+ inputs=[user_msg, chatbot],
1369
+ outputs=[chatbot, user_msg, chat_chart],
1370
+ )
1371
+
1372
+ # On page load, populate sentiment charts if data is available
1373
+ demo.load(fn=refresh_sentiment, outputs=SENT_OUTPUTS)
1374
+
1375
+
1376
+ demo.launch(
1377
+ allowed_paths=[str(BASE_DIR)],
1378
+ css=CSS,
1379
+ theme=gr.themes.Base(
1380
+ primary_hue=gr.themes.colors.teal,
1381
+ neutral_hue=gr.themes.colors.slate,
1382
+ ),
1383
+ )
datacreation_bubblebusters.ipynb ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 5,
4
+ "metadata": {
5
+ "kernelspec": {
6
+ "display_name": "Python 3",
7
+ "language": "python",
8
+ "name": "python3"
9
+ },
10
+ "language_info": {
11
+ "name": "python"
12
+ }
13
+ },
14
+ "cells": [
15
+ {
16
+ "cell_type": "markdown",
17
+ "metadata": {},
18
+ "source": "# 🤖 **Data Collection, Creation, Storage, and Processing**"
19
+ },
20
+ {
21
+ "cell_type": "markdown",
22
+ "metadata": {},
23
+ "source": "## **1.** 📦 Install required packages"
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": null,
28
+ "metadata": {},
29
+ "outputs": [],
30
+ "source": "!pip install pandas openpyxl numpy matplotlib seaborn"
31
+ },
32
+ {
33
+ "cell_type": "markdown",
34
+ "metadata": {},
35
+ "source": "## **2.** 📥 Load the AI Bubble Sentiment dataset"
36
+ },
37
+ {
38
+ "cell_type": "markdown",
39
+ "metadata": {},
40
+ "source": "### *a. Initial setup*\nLoad the Excel file `ai_bubble_sentiment_expanded.xlsx` which must be in the working directory.\n- **Google Colab**: upload it first using the Files panel on the left (folder icon → upload)\n- **HuggingFace Space**: commit it to the repo root alongside `app.py`\n"
41
+ },
42
+ {
43
+ "cell_type": "code",
44
+ "execution_count": null,
45
+ "metadata": {},
46
+ "outputs": [],
47
+ "source": "import pandas as pd\nimport numpy as np\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n"
48
+ },
49
+ {
50
+ "cell_type": "markdown",
51
+ "metadata": {},
52
+ "source": "### *b. Load the Excel file into a DataFrame df_raw*"
53
+ },
54
+ {
55
+ "cell_type": "code",
56
+ "execution_count": null,
57
+ "metadata": {},
58
+ "outputs": [],
59
+ "source": "df_raw = pd.read_excel(\"ai_bubble_sentiment_expanded.xlsx\",\n sheet_name=\"AI Bubble Comments\")\ndf_raw.columns = [\"Date\", \"Platform\", \"Topic\", \"Sentiment\", \"Comment\", \"Source_Link\"]\ndf_raw.head()\n"
60
+ },
61
+ {
62
+ "cell_type": "markdown",
63
+ "metadata": {},
64
+ "source": "### *c. View the shape and column types*"
65
+ },
66
+ {
67
+ "cell_type": "code",
68
+ "execution_count": null,
69
+ "metadata": {},
70
+ "outputs": [],
71
+ "source": "print(df_raw.shape)\nprint(df_raw.dtypes)"
72
+ },
73
+ {
74
+ "cell_type": "markdown",
75
+ "metadata": {},
76
+ "source": "## **3.** 🧹 Clean and process the dataset"
77
+ },
78
+ {
79
+ "cell_type": "markdown",
80
+ "metadata": {},
81
+ "source": "### *a. Parse dates and drop missing values*"
82
+ },
83
+ {
84
+ "cell_type": "code",
85
+ "execution_count": null,
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": "df_raw[\"Date\"] = pd.to_datetime(df_raw[\"Date\"], errors=\"coerce\")\ndf_raw = df_raw.dropna(subset=[\"Date\", \"Sentiment\"]).copy()\nprint(f\"Records after cleaning: {len(df_raw)}\")\nprint(f\"Date range: {df_raw['Date'].min().date()} → {df_raw['Date'].max().date()}\")\n"
89
+ },
90
+ {
91
+ "cell_type": "markdown",
92
+ "metadata": {},
93
+ "source": "### *b. Add derived columns useful for analysis*"
94
+ },
95
+ {
96
+ "cell_type": "code",
97
+ "execution_count": null,
98
+ "metadata": {},
99
+ "outputs": [],
100
+ "source": "df_raw[\"YearMonth\"] = df_raw[\"Date\"].dt.to_period(\"M\").dt.to_timestamp()\ndf_raw[\"Year\"] = df_raw[\"Date\"].dt.year\ndf_raw[\"SentScore\"] = df_raw[\"Sentiment\"].map({\"bullish\": 1, \"neutral\": 0, \"bearish\": -1})\ndf_raw.head()\n"
101
+ },
102
+ {
103
+ "cell_type": "markdown",
104
+ "metadata": {},
105
+ "source": "### *c. Quality check: missing values, duplicates, value counts*"
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": null,
110
+ "metadata": {},
111
+ "outputs": [],
112
+ "source": "print(\"\\n--- Missing values ---\")\nprint(df_raw.isnull().sum())\nprint(f\"\\n--- Duplicate rows: {df_raw.duplicated().sum()} ---\")\nprint(\"\\n--- Sentiment counts ---\")\nprint(df_raw[\"Sentiment\"].value_counts())\nprint(\"\\n--- Topic counts ---\")\nprint(df_raw[\"Topic\"].value_counts())\nprint(\"\\n--- Platform counts ---\")\nprint(df_raw[\"Platform\"].value_counts())\n"
113
+ },
114
+ {
115
+ "cell_type": "markdown",
116
+ "metadata": {},
117
+ "source": "## **4.** 💾 Save cleaned dataset as CSV"
118
+ },
119
+ {
120
+ "cell_type": "markdown",
121
+ "metadata": {},
122
+ "source": "### *a. Save as ai_bubble_clean.csv (used by pythonanalysis and ranalysis notebooks)*"
123
+ },
124
+ {
125
+ "cell_type": "code",
126
+ "execution_count": null,
127
+ "metadata": {},
128
+ "outputs": [],
129
+ "source": "df_raw.to_csv(\"ai_bubble_clean.csv\", index=False)\nprint(\"✅ Saved: ai_bubble_clean.csv\")\nprint(df_raw.head())\n"
130
+ },
131
+ {
132
+ "cell_type": "markdown",
133
+ "metadata": {},
134
+ "source": "### *b. Create a monthly aggregated summary and save it*"
135
+ },
136
+ {
137
+ "cell_type": "code",
138
+ "execution_count": null,
139
+ "metadata": {},
140
+ "outputs": [],
141
+ "source": "monthly_summary = (\n df_raw.groupby([\"YearMonth\",\"Sentiment\"])\n .size()\n .unstack(fill_value=0)\n .reindex(columns=[\"bullish\",\"neutral\",\"bearish\"], fill_value=0)\n)\nmonthly_summary[\"avg_score\"] = (\n df_raw.groupby(\"YearMonth\")[\"SentScore\"].mean()\n)\nmonthly_summary.index = monthly_summary.index.strftime(\"%Y-%m\")\nmonthly_summary = monthly_summary.reset_index()\nmonthly_summary.columns = [\"month\",\"bullish\",\"neutral\",\"bearish\",\"avg_score\"]\nmonthly_summary.to_csv(\"ai_bubble_monthly.csv\", index=False)\nprint(\"✅ Saved: ai_bubble_monthly.csv\")\nmonthly_summary.head()\n"
142
+ },
143
+ {
144
+ "cell_type": "markdown",
145
+ "metadata": {},
146
+ "source": "### *c. ✋🏻🛑⛔️ View the first few lines of the final cleaned dataset*"
147
+ },
148
+ {
149
+ "cell_type": "code",
150
+ "execution_count": null,
151
+ "metadata": {},
152
+ "outputs": [],
153
+ "source": "df_raw.head()"
154
+ }
155
+ ]
156
+ }
pythonanalysis_bubblebusters.ipynb ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 5,
4
+ "metadata": {
5
+ "kernelspec": {
6
+ "display_name": "Python 3",
7
+ "language": "python",
8
+ "name": "python3"
9
+ },
10
+ "language_info": {
11
+ "name": "python"
12
+ }
13
+ },
14
+ "cells": [
15
+ {
16
+ "cell_type": "markdown",
17
+ "metadata": {},
18
+ "source": "# **🤖 Data Analysis & Visualization**"
19
+ },
20
+ {
21
+ "cell_type": "markdown",
22
+ "metadata": {},
23
+ "source": "## **1.** 📦 Install required packages"
24
+ },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": null,
28
+ "metadata": {},
29
+ "outputs": [],
30
+ "source": "!pip install pandas matplotlib seaborn numpy scipy"
31
+ },
32
+ {
33
+ "cell_type": "markdown",
34
+ "metadata": {},
35
+ "source": "## **2.** ✅️ Load & inspect the dataset"
36
+ },
37
+ {
38
+ "cell_type": "markdown",
39
+ "metadata": {},
40
+ "source": "### *a. Initial setup*"
41
+ },
42
+ {
43
+ "cell_type": "code",
44
+ "execution_count": null,
45
+ "metadata": {},
46
+ "outputs": [],
47
+ "source": "import pandas as pd\nimport numpy as np\nimport json, re, warnings\nfrom collections import Counter\nfrom pathlib import Path\nwarnings.filterwarnings(\"ignore\")\n"
48
+ },
49
+ {
50
+ "cell_type": "markdown",
51
+ "metadata": {},
52
+ "source": "### *b. ✋🏻🛑⛔️ Create the df dataframe from the ai_bubble_clean.csv file*"
53
+ },
54
+ {
55
+ "cell_type": "code",
56
+ "execution_count": null,
57
+ "metadata": {},
58
+ "outputs": [],
59
+ "source": "df = pd.read_csv(\"ai_bubble_clean.csv\")\ndf[\"Date\"] = pd.to_datetime(df[\"Date\"])\ndf[\"YearMonth\"] = pd.to_datetime(df[\"YearMonth\"])"
60
+ },
61
+ {
62
+ "cell_type": "markdown",
63
+ "metadata": {},
64
+ "source": "### *c. ✋🏻🛑⛔️ Visualize the first few lines of df*"
65
+ },
66
+ {
67
+ "cell_type": "code",
68
+ "execution_count": null,
69
+ "metadata": {},
70
+ "outputs": [],
71
+ "source": "df.head()"
72
+ },
73
+ {
74
+ "cell_type": "markdown",
75
+ "metadata": {},
76
+ "source": "### *d. Run a quality check on the dataset*"
77
+ },
78
+ {
79
+ "cell_type": "code",
80
+ "execution_count": null,
81
+ "metadata": {},
82
+ "outputs": [],
83
+ "source": "def quality_check(df, name=\"DataFrame\"):\n print(f\"\\n🔍 Quality Check Report for: {name}\")\n print(\"=\" * (25 + len(name)))\n print(f\"\\n📏 Shape: {df.shape}\")\n print(\"\\n🔠 Column Types:\")\n print(df.dtypes)\n print(\"\\n❓ Missing Values:\")\n print(df.isnull().sum())\n print(f\"\\n📋 Duplicate Rows: {df.duplicated().sum()}\")\n print(\"\\n📊 Summary Statistics:\")\n display(df.describe(include=\"all\").transpose())\n print(\"\\n👀 Sample Rows:\")\n display(df.sample(min(5, len(df))))\n\nquality_check(df, \"AI Bubble Sentiment Dataset\")\n"
84
+ },
85
+ {
86
+ "cell_type": "markdown",
87
+ "metadata": {},
88
+ "source": "## **3.** 📊 Set up output folders and plot style"
89
+ },
90
+ {
91
+ "cell_type": "markdown",
92
+ "metadata": {},
93
+ "source": "### *a. Create artifact folders for the Hugging Face dashboard*"
94
+ },
95
+ {
96
+ "cell_type": "code",
97
+ "execution_count": null,
98
+ "metadata": {},
99
+ "outputs": [],
100
+ "source": "import matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\nimport matplotlib.dates as mdates\nfrom matplotlib.ticker import MaxNLocator\nimport seaborn as sns\n\nART_DIR = Path(\"artifacts\")\nPY_FIG = ART_DIR / \"py\" / \"figures\"\nPY_TAB = ART_DIR / \"py\" / \"tables\"\n\nfor p in [PY_FIG, PY_TAB]:\n p.mkdir(parents=True, exist_ok=True)\n\nPALETTE = {\"bullish\": \"#2ecc71\", \"neutral\": \"#3498db\", \"bearish\": \"#e74c3c\"}\nESCP_PURPLE = \"#2e0052\"\nsns.set_theme(style=\"whitegrid\", font_scale=1.1)\n\nprint(\"✅ Output folders:\")\nprint(\" -\", PY_FIG.resolve())\nprint(\" -\", PY_TAB.resolve())\n"
101
+ },
102
+ {
103
+ "cell_type": "markdown",
104
+ "metadata": {},
105
+ "source": "## **4.** 🧭 Overall sentiment distribution"
106
+ },
107
+ {
108
+ "cell_type": "markdown",
109
+ "metadata": {},
110
+ "source": "### *a. Compute sentiment, platform and topic counts*"
111
+ },
112
+ {
113
+ "cell_type": "code",
114
+ "execution_count": null,
115
+ "metadata": {},
116
+ "outputs": [],
117
+ "source": "sent_counts = df[\"Sentiment\"].value_counts().reindex([\"bullish\",\"neutral\",\"bearish\"])\nplat_counts = df[\"Platform\"].value_counts().head(6)\ntopic_counts = df[\"Topic\"].value_counts()\nprint(sent_counts)\n"
118
+ },
119
+ {
120
+ "cell_type": "markdown",
121
+ "metadata": {},
122
+ "source": "### *b. Plot the 3-panel overview figure*"
123
+ },
124
+ {
125
+ "cell_type": "code",
126
+ "execution_count": null,
127
+ "metadata": {},
128
+ "outputs": [],
129
+ "source": "fig, axes = plt.subplots(1, 3, figsize=(16, 5))\nfig.suptitle(\"AI Bubble Sentiment — Dataset Overview\", fontsize=15,\n fontweight=\"bold\", color=ESCP_PURPLE)\n\n# Pie — sentiment\naxes[0].pie(sent_counts,\n labels=[f\"{s.title()}\\n{n}\" for s, n in zip(sent_counts.index, sent_counts)],\n colors=[PALETTE[s] for s in sent_counts.index],\n autopct=\"%1.1f%%\", startangle=90,\n wedgeprops={\"edgecolor\":\"white\",\"linewidth\":2})\naxes[0].set_title(\"Sentiment Distribution\", fontweight=\"bold\")\n\n# Bar — platform\naxes[1].barh(plat_counts.index[::-1], plat_counts.values[::-1],\n color=ESCP_PURPLE, alpha=0.8)\naxes[1].set_title(\"Comments by Platform\", fontweight=\"bold\")\naxes[1].set_xlabel(\"Number of Comments\")\nfor i, v in enumerate(plat_counts.values[::-1]):\n axes[1].text(v + 0.3, i, str(v), va=\"center\", fontsize=9)\n\n# Bar — topic\naxes[2].bar(topic_counts.index, topic_counts.values,\n color=[\"#9b59b6\",\"#3498db\",\"#e67e22\",\"#1abc9c\"],\n edgecolor=\"white\", linewidth=1.5)\naxes[2].set_title(\"Comments by Topic\", fontweight=\"bold\")\naxes[2].set_ylabel(\"Count\"); axes[2].set_xlabel(\"Topic\")\nfor i, v in enumerate(topic_counts.values):\n axes[2].text(i, v + 0.3, str(v), ha=\"center\", fontsize=9)\n\nplt.tight_layout()\nplt.savefig(PY_FIG / \"01_overview_distributions.png\", dpi=150)\nplt.show()\n"
130
+ },
131
+ {
132
+ "cell_type": "markdown",
133
+ "metadata": {},
134
+ "source": "## **5.** 📈 Sentiment over time"
135
+ },
136
+ {
137
+ "cell_type": "markdown",
138
+ "metadata": {},
139
+ "source": "### *a. Aggregate monthly comment counts per sentiment*"
140
+ },
141
+ {
142
+ "cell_type": "code",
143
+ "execution_count": null,
144
+ "metadata": {},
145
+ "outputs": [],
146
+ "source": "monthly = (df.groupby([\"YearMonth\",\"Sentiment\"]).size()\n .unstack(fill_value=0)\n .reindex(columns=[\"bullish\",\"neutral\",\"bearish\"], fill_value=0))\nmonthly.index = pd.to_datetime(monthly.index)\n\nmonthly_score = df.groupby(\"YearMonth\")[\"SentScore\"].mean()\nmonthly_score.index = pd.to_datetime(monthly_score.index)\nrolling_score = monthly_score.rolling(3, min_periods=1).mean()\n\nmonthly_out = monthly.copy()\nmonthly_out[\"avg_score\"] = monthly_score\nmonthly_out.index = monthly_out.index.strftime(\"%Y-%m\")\nmonthly_out.reset_index(inplace=True)\nmonthly_out.columns = [\"month\",\"bullish\",\"neutral\",\"bearish\",\"avg_score\"]\nmonthly_out.to_csv(PY_TAB / \"monthly_sentiment.csv\", index=False)\nprint(monthly_out.head())\n"
147
+ },
148
+ {
149
+ "cell_type": "markdown",
150
+ "metadata": {},
151
+ "source": "### *b. Plot stacked area + rolling sentiment score*"
152
+ },
153
+ {
154
+ "cell_type": "code",
155
+ "execution_count": null,
156
+ "metadata": {},
157
+ "outputs": [],
158
+ "source": "fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 9), sharex=True)\nfig.suptitle(\"AI Bubble Sentiment Over Time\", fontsize=15,\n fontweight=\"bold\", color=ESCP_PURPLE)\n\nax1.stackplot(monthly.index,\n monthly[\"bullish\"], monthly[\"neutral\"], monthly[\"bearish\"],\n labels=[\"Bullish\",\"Neutral\",\"Bearish\"],\n colors=[PALETTE[\"bullish\"],PALETTE[\"neutral\"],PALETTE[\"bearish\"]], alpha=0.75)\nax1.set_ylabel(\"Number of Comments\")\nax1.set_title(\"Monthly Comment Volume by Sentiment\", fontweight=\"bold\")\nax1.legend(loc=\"upper left\", framealpha=0.8)\nax1.yaxis.set_major_locator(MaxNLocator(integer=True))\n\nax2.axhline(0, color=\"black\", lw=0.8, ls=\"--\", alpha=0.6)\nax2.fill_between(rolling_score.index, rolling_score, 0,\n where=(rolling_score >= 0), interpolate=True,\n color=PALETTE[\"bullish\"], alpha=0.4, label=\"Bullish zone\")\nax2.fill_between(rolling_score.index, rolling_score, 0,\n where=(rolling_score < 0), interpolate=True,\n color=PALETTE[\"bearish\"], alpha=0.4, label=\"Bearish zone\")\nax2.plot(rolling_score.index, rolling_score, color=\"black\", lw=1.5, label=\"3-month avg\")\nax2.set_ylabel(\"Avg Sentiment Score\\n(+1=bullish, -1=bearish)\")\nax2.set_xlabel(\"Date\")\nax2.set_title(\"Rolling Average Sentiment Score (3-month window)\", fontweight=\"bold\")\nax2.legend(loc=\"upper left\", framealpha=0.8)\nax2.xaxis.set_major_formatter(mdates.DateFormatter(\"%b '%y\"))\nax2.xaxis.set_major_locator(mdates.MonthLocator(interval=4))\nplt.setp(ax2.xaxis.get_majorticklabels(), rotation=45, ha=\"right\")\n\nplt.tight_layout()\nplt.savefig(PY_FIG / \"02_sentiment_over_time.png\", dpi=150)\nplt.show()\n"
159
+ },
160
+ {
161
+ "cell_type": "markdown",
162
+ "metadata": {},
163
+ "source": "## **6.** 🔥 Sentiment breakdown by topic"
164
+ },
165
+ {
166
+ "cell_type": "markdown",
167
+ "metadata": {},
168
+ "source": "### *a. ✋🏻🛑⛔️ Compute cross-tabulation of Topic vs Sentiment*"
169
+ },
170
+ {
171
+ "cell_type": "code",
172
+ "execution_count": null,
173
+ "metadata": {},
174
+ "outputs": [],
175
+ "source": "cross = pd.crosstab(df[\"Topic\"], df[\"Sentiment\"])[[\"bullish\",\"neutral\",\"bearish\"]]\ncross_pct = cross.div(cross.sum(axis=1), axis=0) * 100\ncross.reset_index().to_csv(PY_TAB / \"sentiment_by_topic.csv\", index=False)\nprint(cross)\n"
176
+ },
177
+ {
178
+ "cell_type": "markdown",
179
+ "metadata": {},
180
+ "source": "### *b. Plot absolute counts and % heatmaps side by side*"
181
+ },
182
+ {
183
+ "cell_type": "code",
184
+ "execution_count": null,
185
+ "metadata": {},
186
+ "outputs": [],
187
+ "source": "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\nfig.suptitle(\"Sentiment vs. Topic\", fontsize=15, fontweight=\"bold\", color=ESCP_PURPLE)\n\nsns.heatmap(cross, annot=True, fmt=\"d\", cmap=\"YlOrRd\",\n linewidths=0.5, linecolor=\"white\", cbar_kws={\"label\":\"Count\"}, ax=ax1)\nax1.set_title(\"Absolute Comment Counts\", fontweight=\"bold\")\n\nsns.heatmap(cross_pct, annot=True, fmt=\".1f\", cmap=\"RdYlGn\",\n linewidths=0.5, linecolor=\"white\",\n cbar_kws={\"label\":\"% within Topic\"}, ax=ax2, vmin=0, vmax=60)\nax2.set_title(\"% of Comments per Topic\", fontweight=\"bold\")\n\nplt.tight_layout()\nplt.savefig(PY_FIG / \"03_sentiment_by_topic.png\", dpi=150)\nplt.show()\n"
188
+ },
189
+ {
190
+ "cell_type": "markdown",
191
+ "metadata": {},
192
+ "source": "## **7.** 🌐 Sentiment breakdown by platform"
193
+ },
194
+ {
195
+ "cell_type": "markdown",
196
+ "metadata": {},
197
+ "source": "### *a. Compute platform cross-tabulation*"
198
+ },
199
+ {
200
+ "cell_type": "code",
201
+ "execution_count": null,
202
+ "metadata": {},
203
+ "outputs": [],
204
+ "source": "top_platforms = df[\"Platform\"].value_counts().head(6).index\ndf_plat = df[df[\"Platform\"].isin(top_platforms)]\nplat_cross = pd.crosstab(df_plat[\"Platform\"], df_plat[\"Sentiment\"])[[\"bullish\",\"neutral\",\"bearish\"]]\nplat_pct = plat_cross.div(plat_cross.sum(axis=1), axis=0) * 100\nplat_cross.reset_index().to_csv(PY_TAB / \"sentiment_by_platform.csv\", index=False)\nprint(plat_pct)\n"
205
+ },
206
+ {
207
+ "cell_type": "markdown",
208
+ "metadata": {},
209
+ "source": "### *b. Plot stacked bar chart by platform*"
210
+ },
211
+ {
212
+ "cell_type": "code",
213
+ "execution_count": null,
214
+ "metadata": {},
215
+ "outputs": [],
216
+ "source": "fig, ax = plt.subplots(figsize=(12, 6))\nplat_pct.plot(kind=\"bar\", stacked=True, ax=ax,\n color=[PALETTE[\"bullish\"],PALETTE[\"neutral\"],PALETTE[\"bearish\"]],\n edgecolor=\"white\", linewidth=0.8)\nax.set_title(\"Sentiment Distribution by Platform (%)\", fontsize=14,\n fontweight=\"bold\", color=ESCP_PURPLE)\nax.set_xlabel(\"Platform\"); ax.set_ylabel(\"% of Comments\")\nax.set_xticklabels(ax.get_xticklabels(), rotation=30, ha=\"right\")\nax.legend(title=\"Sentiment\", bbox_to_anchor=(1.01, 1), loc=\"upper left\")\nax.axhline(50, color=\"black\", ls=\"--\", alpha=0.4, lw=0.9)\nplt.tight_layout()\nplt.savefig(PY_FIG / \"04_sentiment_by_platform.png\", dpi=150)\nplt.show()\n"
217
+ },
218
+ {
219
+ "cell_type": "markdown",
220
+ "metadata": {},
221
+ "source": "## **8.** 📅 Yearly sentiment shift"
222
+ },
223
+ {
224
+ "cell_type": "markdown",
225
+ "metadata": {},
226
+ "source": "### *a. Aggregate by year*"
227
+ },
228
+ {
229
+ "cell_type": "code",
230
+ "execution_count": null,
231
+ "metadata": {},
232
+ "outputs": [],
233
+ "source": "yearly = pd.crosstab(df[\"Year\"], df[\"Sentiment\"])[[\"bullish\",\"neutral\",\"bearish\"]]\nyearly_pct = yearly.div(yearly.sum(axis=1), axis=0) * 100\nyearly.reset_index().to_csv(PY_TAB / \"yearly_sentiment.csv\", index=False)\nprint(yearly_pct)\n"
234
+ },
235
+ {
236
+ "cell_type": "markdown",
237
+ "metadata": {},
238
+ "source": "### *b. ✋🏻🛑⛔️ Plot volume and share grouped bars side by side*"
239
+ },
240
+ {
241
+ "cell_type": "code",
242
+ "execution_count": null,
243
+ "metadata": {},
244
+ "outputs": [],
245
+ "source": "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))\nfig.suptitle(\"How Sentiment Has Shifted Year-over-Year\", fontsize=14,\n fontweight=\"bold\", color=ESCP_PURPLE)\n\nyearly.plot(kind=\"bar\", ax=ax1,\n color=[PALETTE[\"bullish\"],PALETTE[\"neutral\"],PALETTE[\"bearish\"]], edgecolor=\"white\")\nax1.set_title(\"Comment Volume by Year\", fontweight=\"bold\")\nax1.set_xticklabels(yearly.index, rotation=0); ax1.legend(title=\"Sentiment\")\n\nyearly_pct.plot(kind=\"bar\", stacked=True, ax=ax2,\n color=[PALETTE[\"bullish\"],PALETTE[\"neutral\"],PALETTE[\"bearish\"]], edgecolor=\"white\")\nax2.set_title(\"Sentiment Share by Year (%)\", fontweight=\"bold\")\nax2.set_xticklabels(yearly_pct.index, rotation=0)\nax2.legend(title=\"Sentiment\", bbox_to_anchor=(1.01, 1))\nplt.tight_layout()\nplt.savefig(PY_FIG / \"05_yearly_sentiment_shift.png\", dpi=150)\nplt.show()\n"
246
+ },
247
+ {
248
+ "cell_type": "markdown",
249
+ "metadata": {},
250
+ "source": "## **9.** 🔤 Word frequency by sentiment"
251
+ },
252
+ {
253
+ "cell_type": "markdown",
254
+ "metadata": {},
255
+ "source": "### *a. Define stopwords and top_words function*"
256
+ },
257
+ {
258
+ "cell_type": "code",
259
+ "execution_count": null,
260
+ "metadata": {},
261
+ "outputs": [],
262
+ "source": "STOPWORDS = {\n \"the\",\"a\",\"an\",\"is\",\"it\",\"in\",\"of\",\"and\",\"to\",\"for\",\"on\",\"are\",\"that\",\n \"this\",\"with\",\"as\",\"but\",\"not\",\"be\",\"at\",\"by\",\"or\",\"from\",\"have\",\"has\",\n \"will\",\"was\",\"were\",\"been\",\"they\",\"their\",\"we\",\"our\",\"i\",\"you\",\"he\",\"she\",\n \"its\",\"so\",\"if\",\"than\",\"more\",\"just\",\"can\",\"about\",\"what\",\"which\",\"would\",\n \"also\",\"there\",\"these\",\"those\",\"all\",\"some\",\"any\",\"up\",\"how\",\"very\",\"much\",\n \"when\",\"who\",\"one\",\"my\",\"do\",\"had\",\"get\",\"out\",\"even\",\"into\",\"like\",\"no\",\n \"after\",\"them\",\"your\",\"such\",\"because\",\"am\",\"over\",\"does\",\"make\",\"only\",\"really\"\n}\n\ndef top_words(texts, n=20):\n words = []\n for t in texts:\n words.extend(re.findall(r\"\\b[a-z]{3,}\\b\", str(t).lower()))\n return Counter(w for w in words if w not in STOPWORDS).most_common(n)\n"
263
+ },
264
+ {
265
+ "cell_type": "markdown",
266
+ "metadata": {},
267
+ "source": "### *b. ✋🏻🛑⛔️ Plot top 20 words for each sentiment label*"
268
+ },
269
+ {
270
+ "cell_type": "code",
271
+ "execution_count": null,
272
+ "metadata": {},
273
+ "outputs": [],
274
+ "source": "fig, axes = plt.subplots(1, 3, figsize=(18, 7))\nfig.suptitle(\"Top Words by Sentiment\", fontsize=14, fontweight=\"bold\", color=ESCP_PURPLE)\n\nfor ax, sent in zip(axes, [\"bullish\",\"neutral\",\"bearish\"]):\n pairs = top_words(df[df[\"Sentiment\"] == sent][\"Comment\"])\n words, freqs = zip(*pairs)\n ax.barh(list(words)[::-1], list(freqs)[::-1],\n color=PALETTE[sent], alpha=0.85, edgecolor=\"white\")\n ax.set_title(f\"{sent.title()} Comments\", fontweight=\"bold\", color=PALETTE[sent])\n ax.set_xlabel(\"Frequency\")\n\nplt.tight_layout()\nplt.savefig(PY_FIG / \"06_word_frequency_by_sentiment.png\", dpi=150)\nplt.show()\n"
275
+ },
276
+ {
277
+ "cell_type": "markdown",
278
+ "metadata": {},
279
+ "source": "## **10.** 📐 AI Bubble Risk Score"
280
+ },
281
+ {
282
+ "cell_type": "markdown",
283
+ "metadata": {},
284
+ "source": "### *a. Define bubble_risk function — bearish share of (bullish+bearish) per month*"
285
+ },
286
+ {
287
+ "cell_type": "code",
288
+ "execution_count": null,
289
+ "metadata": {},
290
+ "outputs": [],
291
+ "source": "def bubble_risk(group):\n b = (group == \"bearish\").sum()\n u = (group == \"bullish\").sum()\n total = b + u\n return b / total if total > 0 else np.nan\n\nmonthly_risk = df.groupby(\"YearMonth\")[\"Sentiment\"].apply(bubble_risk)\nmonthly_risk.index = pd.to_datetime(monthly_risk.index)\nrolling_risk = monthly_risk.rolling(3, min_periods=1).mean()\n\npd.DataFrame({\n \"month\": rolling_risk.index.strftime(\"%Y-%m\"),\n \"bubble_risk_score\": rolling_risk.round(3).values\n}).to_csv(PY_TAB / \"bubble_risk_score.csv\", index=False)\nprint(rolling_risk.tail())\n"
292
+ },
293
+ {
294
+ "cell_type": "markdown",
295
+ "metadata": {},
296
+ "source": "### *b. Plot bubble risk score over time*"
297
+ },
298
+ {
299
+ "cell_type": "code",
300
+ "execution_count": null,
301
+ "metadata": {},
302
+ "outputs": [],
303
+ "source": "fig, ax = plt.subplots(figsize=(13, 5))\nax.fill_between(rolling_risk.index, rolling_risk, 0.5,\n where=(rolling_risk > 0.5), interpolate=True,\n color=\"#e74c3c\", alpha=0.3, label=\"Bearish-dominant\")\nax.fill_between(rolling_risk.index, rolling_risk, 0.5,\n where=(rolling_risk <= 0.5), interpolate=True,\n color=\"#2ecc71\", alpha=0.3, label=\"Bullish-dominant\")\nax.plot(rolling_risk.index, rolling_risk, color=\"#2c3e50\", lw=2,\n label=\"Risk score (3-mo avg)\")\nax.axhline(0.5, color=\"gray\", ls=\"--\", lw=1, alpha=0.7, label=\"Neutral threshold\")\nax.set_ylim(0, 1)\nax.set_ylabel(\"Bubble Risk Score\\n(0 = all bullish, 1 = all bearish)\")\nax.set_title(\"AI Bubble Risk Score Over Time\", fontsize=14,\n fontweight=\"bold\", color=ESCP_PURPLE)\nax.xaxis.set_major_formatter(mdates.DateFormatter(\"%b '%y\"))\nax.xaxis.set_major_locator(mdates.MonthLocator(interval=4))\nplt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha=\"right\")\nax.legend(framealpha=0.8)\nplt.tight_layout()\nplt.savefig(PY_FIG / \"07_bubble_risk_score.png\", dpi=150)\nplt.show()\n"
304
+ },
305
+ {
306
+ "cell_type": "markdown",
307
+ "metadata": {},
308
+ "source": "## **11.** 💾 Save Python outputs for the Hugging Face dashboard"
309
+ },
310
+ {
311
+ "cell_type": "markdown",
312
+ "metadata": {},
313
+ "source": "This section exports **HF-ready artifacts** into the folder structure the app expects:\n- `artifacts/py/figures/` — all chart images\n- `artifacts/py/tables/` — tables and KPI JSON\n"
314
+ },
315
+ {
316
+ "cell_type": "code",
317
+ "execution_count": null,
318
+ "metadata": {},
319
+ "outputs": [],
320
+ "source": "from scipy.stats import chi2_contingency\n\n# Chi-square test\nchi2_stat, p, dof, _ = chi2_contingency(cross.values)\npd.DataFrame({\n \"Test\": [\"Chi-Square (Sentiment vs Topic)\"],\n \"Chi2_Statistic\": [round(chi2_stat, 3)],\n \"p_value\": [round(p, 4)],\n \"Degrees_of_Freedom\": [dof],\n \"Significant_alpha_05\": [\"Yes\" if p < 0.05 else \"No\"],\n}).to_csv(PY_TAB / \"chi_square_result.csv\", index=False)\n\n# KPIs JSON\nlatest_3mo = df[df[\"Date\"] >= df[\"Date\"].max() - pd.DateOffset(months=3)]\nlatest_risk = (latest_3mo[\"Sentiment\"]==\"bearish\").sum() / max(\n (latest_3mo[\"Sentiment\"]==\"bearish\").sum() +\n (latest_3mo[\"Sentiment\"]==\"bullish\").sum(), 1)\n\nkpis = {\n \"total_comments\": int(len(df)),\n \"date_range\": f\"{df['Date'].min().strftime('%b %Y')} – {df['Date'].max().strftime('%b %Y')}\",\n \"n_platforms\": int(df[\"Platform\"].nunique()),\n \"n_topics\": int(df[\"Topic\"].nunique()),\n \"pct_bearish\": round((df[\"Sentiment\"]==\"bearish\").mean()*100, 1),\n \"pct_bullish\": round((df[\"Sentiment\"]==\"bullish\").mean()*100, 1),\n \"pct_neutral\": round((df[\"Sentiment\"]==\"neutral\").mean()*100, 1),\n \"avg_sentiment_score\": round(df[\"SentScore\"].mean(), 3),\n \"latest_bubble_risk\": round(float(latest_risk), 3),\n \"chi2_p_value\": round(p, 4),\n \"most_bearish_topic\": str(cross_pct[\"bearish\"].idxmax()),\n \"most_bullish_topic\": str(cross_pct[\"bullish\"].idxmax()),\n \"dominant_platform\": str(df[\"Platform\"].value_counts().index[0]),\n}\nwith open(PY_TAB / \"kpis.json\", \"w\") as f:\n json.dump(kpis, f, indent=2)\n\nprint(\"✅ All Python artifacts saved\")\nprint(f\" Figures : {len(list(PY_FIG.glob('*.png')))}\")\nprint(f\" Tables : {len(list(PY_TAB.glob('*.csv')) + list(PY_TAB.glob('*.json')))}\")\nfor k, v in kpis.items():\n print(f\" {k}: {v}\")\n"
321
+ },
322
+ {
323
+ "cell_type": "markdown",
324
+ "metadata": {},
325
+ "source": "✅ **Output for R notebook**: `ai_bubble_clean.csv` in the working directory (produced by `datacreation.ipynb`)."
326
+ },
327
+ {
328
+ "cell_type": "markdown",
329
+ "metadata": {},
330
+ "source": "## **12.** ⬇️ Download all Python artifacts"
331
+ },
332
+ {
333
+ "cell_type": "code",
334
+ "execution_count": null,
335
+ "metadata": {},
336
+ "outputs": [],
337
+ "source": "# ── Download all Python artifacts as a ZIP ────────────────────────────────────\nimport shutil, zipfile, os\nfrom pathlib import Path\n\nzip_path = \"python_analysis_artifacts.zip\"\n\nwith zipfile.ZipFile(zip_path, \"w\", zipfile.ZIP_DEFLATED) as zf:\n for p in Path(\"artifacts/py\").rglob(\"*\"):\n if p.is_file():\n zf.write(p, p.relative_to(\"artifacts/py\"))\n\nprint(\"📦 ZIP contents:\")\nwith zipfile.ZipFile(zip_path, \"r\") as zf:\n for name in sorted(zf.namelist()):\n print(f\" {name}\")\n\n# Colab: triggers a browser download\n# HuggingFace / local: the ZIP is saved next to the notebook\ntry:\n from google.colab import files\n files.download(zip_path)\n print(\"\\n✅ Download started!\")\nexcept ImportError:\n print(f\"\\n✅ Saved as: {Path(zip_path).resolve()}\")\n"
338
+ }
339
+ ]
340
+ }
ranalysis_bubblebusters.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=6.0.0
2
+ pandas>=2.0.0
3
+ numpy>=1.24.0
4
+ matplotlib>=3.7.0
5
+ seaborn>=0.13.0
6
+ statsmodels>=0.14.0
7
+ scikit-learn>=1.3.0
8
+ papermill>=2.5.0
9
+ nbformat>=5.9.0
10
+ ipykernel>=6.29.0
11
+ jupyter_client>=8.6.0
12
+ jupyter_core>=5.7.0
13
+ nbclient>=0.10.0
14
+ pillow>=10.0.0
15
+ requests>=2.31.0
16
+ beautifulsoup4>=4.12.0
17
+ vaderSentiment>=3.3.2
18
+ huggingface_hub>=0.20.0
19
+ textblob>=0.18.0
20
+ faker>=20.0.0
21
+ plotly>=5.0.0
22
+ yfinance>=0.2.0
23
+ openpyxl>=3.1.0
style.css ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Syne:wght@700;800&family=JetBrains+Mono:wght@400;500&display=swap');
2
+
3
+ /* ══════════════════════════════════════════════════════════════
4
+ BubbleBusters — Iridescent Soap Bubble Design System
5
+ Palette: Pearl White · Soft Lavender · Bubblegum · Mint
6
+ ══════════════════════════════════════════════════════════════ */
7
+ :root {
8
+ /* backgrounds */
9
+ --bg: #f7f4ff;
10
+ --bg-card: rgba(255,255,255,0.72);
11
+ --bg-card-2: rgba(255,255,255,0.55);
12
+ --bg-input: rgba(255,255,255,0.80);
13
+ --bg-dark: #1a0e2e;
14
+
15
+ /* palette */
16
+ --lavender: #c5b4f0;
17
+ --lavender-mid: #a48de8;
18
+ --violet: #7c5cbf;
19
+ --violet-deep: #4b2d8a;
20
+ --mint: #6ee7c7;
21
+ --mint-mid: #3dcba8;
22
+ --blush: #ffb3c8;
23
+ --peach: #ffd6a5;
24
+ --sky: #a8d8f0;
25
+ --periwinkle: #8fa8f8;
26
+
27
+ /* text */
28
+ --text: #2d1f4e;
29
+ --text-mid: #6b5b8e;
30
+ --text-muted: #9d8fc4;
31
+
32
+ /* semantic */
33
+ --green: #3dcba8;
34
+ --red: #ff6b8a;
35
+ --amber: #ffb347;
36
+ --blue: #7aa6f8;
37
+
38
+ /* glass */
39
+ --glass-border: rgba(255,255,255,0.65);
40
+ --glass-shadow: 0 8px 32px rgba(124,92,191,.12), 0 2px 8px rgba(124,92,191,.06);
41
+
42
+ --font-display: 'Syne', sans-serif;
43
+ --font-body: 'Nunito', sans-serif;
44
+ --font-mono: 'JetBrains Mono', monospace;
45
+ }
46
+
47
+ /* ── Base — aurora gradient background ── */
48
+ body, .gradio-container {
49
+ background:
50
+ radial-gradient(ellipse at 15% 0%, rgba(197,180,240,.45) 0%, transparent 55%),
51
+ radial-gradient(ellipse at 85% 10%, rgba(168,216,240,.40) 0%, transparent 50%),
52
+ radial-gradient(ellipse at 50% 90%, rgba(110,231,199,.30) 0%, transparent 60%),
53
+ radial-gradient(ellipse at 5% 80%, rgba(255,179,200,.25) 0%, transparent 45%),
54
+ #f0ecff !important;
55
+ color: var(--text) !important;
56
+ font-family: var(--font-body) !important;
57
+ min-height: 100vh;
58
+ }
59
+ .gradio-container { max-width: 1520px !important; }
60
+
61
+ /* ── Keyframes ── */
62
+ @keyframes floatBubble {
63
+ 0% { transform: translateY(0px) rotate(0deg) scale(1); opacity:.7; }
64
+ 33% { transform: translateY(-14px) rotate(3deg) scale(1.04); opacity:.9; }
65
+ 66% { transform: translateY(-8px) rotate(-2deg) scale(.97); opacity:.75; }
66
+ 100% { transform: translateY(0px) rotate(0deg) scale(1); opacity:.7; }
67
+ }
68
+ @keyframes iridescent {
69
+ 0% { filter: hue-rotate(0deg) brightness(1.0); }
70
+ 50% { filter: hue-rotate(30deg) brightness(1.08); }
71
+ 100% { filter: hue-rotate(0deg) brightness(1.0); }
72
+ }
73
+ @keyframes shimmerSlide {
74
+ 0% { background-position: -600px 0; }
75
+ 100% { background-position: 600px 0; }
76
+ }
77
+ @keyframes popIn {
78
+ 0% { opacity:0; transform:scale(.92) translateY(8px); }
79
+ 60% { opacity:1; transform:scale(1.02) translateY(-2px); }
80
+ 100% { opacity:1; transform:scale(1) translateY(0); }
81
+ }
82
+ @keyframes pulseDot {
83
+ 0%,100% { box-shadow: 0 0 0 0 rgba(61,203,168,.7); }
84
+ 50% { box-shadow: 0 0 0 7px rgba(61,203,168,.0); }
85
+ }
86
+
87
+ /* ── Header ── */
88
+ #bb-header {
89
+ position: relative;
90
+ overflow: hidden;
91
+ background: linear-gradient(135deg,
92
+ rgba(255,255,255,.82) 0%,
93
+ rgba(245,240,255,.88) 50%,
94
+ rgba(240,248,255,.82) 100%);
95
+ backdrop-filter: blur(24px);
96
+ -webkit-backdrop-filter: blur(24px);
97
+ border: 1.5px solid var(--glass-border);
98
+ border-radius: 28px;
99
+ padding: 28px 36px;
100
+ margin-bottom: 20px;
101
+ box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255,255,255,.9);
102
+ animation: popIn .55s cubic-bezier(.34,1.56,.64,1) both;
103
+ }
104
+ /* Top shimmer stripe */
105
+ #bb-header::before {
106
+ content: '';
107
+ position: absolute;
108
+ top: 0; left: 0; right: 0; height: 3px;
109
+ background: linear-gradient(90deg,
110
+ var(--lavender), var(--mint), var(--blush),
111
+ var(--sky), var(--lavender));
112
+ background-size: 300% 100%;
113
+ animation: shimmerSlide 4s linear infinite;
114
+ border-radius: 28px 28px 0 0;
115
+ }
116
+ /* Subtle dot grid */
117
+ #bb-header::after {
118
+ content: '';
119
+ position: absolute; inset: 0;
120
+ background-image: radial-gradient(circle, rgba(124,92,191,.08) 1px, transparent 1px);
121
+ background-size: 22px 22px;
122
+ pointer-events: none;
123
+ }
124
+
125
+ /* ── Tabs ── */
126
+ .tab-nav {
127
+ background: rgba(255,255,255,.55) !important;
128
+ backdrop-filter: blur(12px);
129
+ border: 1.5px solid var(--glass-border) !important;
130
+ border-radius: 18px !important;
131
+ padding: 5px 5px !important;
132
+ margin-bottom: 14px !important;
133
+ box-shadow: 0 4px 16px rgba(124,92,191,.08);
134
+ gap: 4px !important;
135
+ display: flex !important;
136
+ }
137
+ .tab-nav button {
138
+ background: transparent !important;
139
+ color: var(--text-mid) !important;
140
+ border: none !important;
141
+ border-radius: 13px !important;
142
+ font-family: var(--font-body) !important;
143
+ font-size: 13px !important;
144
+ font-weight: 700 !important;
145
+ padding: 10px 20px !important;
146
+ letter-spacing: .2px !important;
147
+ transition: all .22s ease !important;
148
+ flex: 1;
149
+ }
150
+ .tab-nav button.selected {
151
+ background: linear-gradient(135deg, var(--violet), var(--lavender-mid)) !important;
152
+ color: #fff !important;
153
+ box-shadow: 0 4px 14px rgba(124,92,191,.35) !important;
154
+ }
155
+ .tab-nav button:hover:not(.selected) {
156
+ background: rgba(197,180,240,.25) !important;
157
+ color: var(--violet) !important;
158
+ }
159
+
160
+ /* ── Buttons ── */
161
+ .btn-primary {
162
+ background: linear-gradient(135deg, var(--violet), var(--lavender-mid)) !important;
163
+ color: #fff !important;
164
+ border: none !important;
165
+ border-radius: 50px !important;
166
+ font-family: var(--font-body) !important;
167
+ font-weight: 800 !important;
168
+ font-size: 13px !important;
169
+ padding: 12px 30px !important;
170
+ letter-spacing: .3px !important;
171
+ box-shadow: 0 6px 20px rgba(124,92,191,.35) !important;
172
+ transition: box-shadow .25s, transform .18s !important;
173
+ }
174
+ .btn-primary:hover {
175
+ box-shadow: 0 8px 28px rgba(124,92,191,.55) !important;
176
+ transform: translateY(-2px) !important;
177
+ }
178
+ .btn-primary:active { transform: translateY(0) !important; }
179
+
180
+ .btn-secondary {
181
+ background: rgba(255,255,255,.7) !important;
182
+ color: var(--violet) !important;
183
+ border: 1.5px solid rgba(197,180,240,.6) !important;
184
+ border-radius: 50px !important;
185
+ font-family: var(--font-body) !important;
186
+ font-weight: 700 !important;
187
+ font-size: 12px !important;
188
+ padding: 9px 18px !important;
189
+ transition: all .2s !important;
190
+ backdrop-filter: blur(8px) !important;
191
+ }
192
+ .btn-secondary:hover {
193
+ background: rgba(197,180,240,.3) !important;
194
+ border-color: var(--lavender-mid) !important;
195
+ box-shadow: 0 4px 14px rgba(124,92,191,.2) !important;
196
+ transform: translateY(-1px) !important;
197
+ }
198
+
199
+ /* ── Inputs / textareas ── */
200
+ textarea, input[type="text"],
201
+ .gr-textbox textarea, .gr-input input {
202
+ background: var(--bg-input) !important;
203
+ color: var(--text) !important;
204
+ border: 1.5px solid rgba(197,180,240,.5) !important;
205
+ border-radius: 16px !important;
206
+ font-family: var(--font-body) !important;
207
+ font-size: 13.5px !important;
208
+ font-weight: 500 !important;
209
+ transition: border-color .2s, box-shadow .2s !important;
210
+ box-shadow: 0 2px 8px rgba(124,92,191,.06) !important;
211
+ }
212
+ textarea:focus, input[type="text"]:focus {
213
+ border-color: var(--lavender-mid) !important;
214
+ box-shadow: 0 0 0 4px rgba(197,180,240,.3), 0 2px 8px rgba(124,92,191,.1) !important;
215
+ outline: none !important;
216
+ }
217
+
218
+ /* ── Chatbot ── */
219
+ .message {
220
+ border-radius: 18px !important;
221
+ font-family: var(--font-body) !important;
222
+ font-size: 13.5px !important;
223
+ font-weight: 500 !important;
224
+ line-height: 1.65 !important;
225
+ }
226
+ .message.user {
227
+ background: linear-gradient(135deg,
228
+ rgba(197,180,240,.25), rgba(168,216,240,.20)) !important;
229
+ border: 1.5px solid rgba(197,180,240,.45) !important;
230
+ }
231
+ .message.assistant {
232
+ background: linear-gradient(135deg,
233
+ rgba(255,255,255,.85), rgba(245,240,255,.75)) !important;
234
+ border: 1.5px solid rgba(255,255,255,.7) !important;
235
+ box-shadow: 0 4px 16px rgba(124,92,191,.08) !important;
236
+ }
237
+
238
+ /* ── Pipeline log ── */
239
+ #pipeline-log textarea {
240
+ font-family: var(--font-mono) !important;
241
+ font-size: 11.5px !important;
242
+ background: #1a0e2e !important;
243
+ color: #c5b4f0 !important;
244
+ border: 1.5px solid rgba(197,180,240,.25) !important;
245
+ border-radius: 16px !important;
246
+ line-height: 1.75 !important;
247
+ box-shadow: inset 0 2px 12px rgba(0,0,0,.2) !important;
248
+ height: 340px !important;
249
+ min-height: 340px !important;
250
+ max-height: 340px !important;
251
+ overflow-y: scroll !important;
252
+ resize: vertical !important;
253
+ white-space: pre !important;
254
+ word-break: keep-all !important;
255
+ user-select: text !important;
256
+ -webkit-user-select: text !important;
257
+ }
258
+
259
+ /* ── Section labels ── */
260
+ .section-label {
261
+ font-family: var(--font-body);
262
+ color: var(--violet);
263
+ font-weight: 800;
264
+ font-size: 10.5px;
265
+ text-transform: uppercase;
266
+ letter-spacing: 2.5px;
267
+ margin-bottom: 12px;
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 10px;
271
+ }
272
+ .section-label::after {
273
+ content: '';
274
+ flex: 1;
275
+ height: 1.5px;
276
+ background: linear-gradient(90deg, rgba(197,180,240,.7), transparent);
277
+ border-radius: 2px;
278
+ }
279
+
280
+ /* ── Accordion ── */
281
+ .gr-accordion {
282
+ background: var(--bg-card) !important;
283
+ backdrop-filter: blur(16px) !important;
284
+ border: 1.5px solid var(--glass-border) !important;
285
+ border-radius: 18px !important;
286
+ box-shadow: var(--glass-shadow) !important;
287
+ }
288
+ .gr-accordion > div:first-child {
289
+ border-radius: 18px 18px 0 0 !important;
290
+ }
291
+
292
+ /* ── Radio / checkbox ── */
293
+ .gr-radio label, .gr-checkbox label {
294
+ color: var(--text-mid) !important;
295
+ font-family: var(--font-body) !important;
296
+ font-size: 12px !important;
297
+ font-weight: 600 !important;
298
+ }
299
+
300
+ /* ── Misc cards / panels ── */
301
+ .gr-form { background: transparent !important; }
302
+ label, .gr-form label {
303
+ color: var(--text-mid) !important;
304
+ font-family: var(--font-body) !important;
305
+ font-size: 12px !important;
306
+ font-weight: 600 !important;
307
+ }
308
+ .gr-box, .gr-panel {
309
+ background: var(--bg-card) !important;
310
+ backdrop-filter: blur(16px) !important;
311
+ border: 1.5px solid var(--glass-border) !important;
312
+ border-radius: 20px !important;
313
+ box-shadow: var(--glass-shadow) !important;
314
+ }
315
+
316
+ /* ── Scrollbars ── */
317
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
318
+ ::-webkit-scrollbar-track { background: transparent; }
319
+ ::-webkit-scrollbar-thumb {
320
+ background: linear-gradient(var(--lavender), var(--mint));
321
+ border-radius: 10px;
322
+ }
323
+
324
+ /* ── Plotly override: white backgrounds look wrong on aurora bg ── */
325
+ .js-plotly-plot .plotly .bg { fill: transparent !important; }