Faffio commited on
Commit
4d96bb5
Β·
1 Parent(s): a160341

Final Update

Browse files
app/api/__pycache__/main.cpython-312.pyc CHANGED
Binary files a/app/api/__pycache__/main.cpython-312.pyc and b/app/api/__pycache__/main.cpython-312.pyc differ
 
app/api/main.py CHANGED
@@ -1,52 +1,101 @@
1
  from fastapi import FastAPI, HTTPException
2
  from pydantic import BaseModel
3
- # Importiamo l'istanza del modello che abbiamo appena creato
 
 
 
 
 
4
  from app.model.loader import model_instance
 
5
 
6
  # 1. Inizializziamo l'app
7
  app = FastAPI(
8
  title="Reputation Monitor API",
9
- description="API per l'analisi del sentiment della reputazione aziendale",
10
  version="1.0.0"
11
  )
12
 
13
- # 2. Definiamo il "modello dei dati" in input (Data Validation)
14
- # Questo serve a garantire che chi chiama l'API mandi i dati giusti
15
- class SentimentRequest(BaseModel):
16
- text: str
17
- language: str = "it" # default italiano
 
 
 
 
 
 
18
 
19
- class SentimentResponse(BaseModel):
 
 
 
 
 
 
20
  sentiment: str
21
  confidence: float
22
 
23
- # 3. Endpoint di default (la nostra Home che consiglia reindirizzamento a docs)
24
- @app.get("/")
25
- def home():
26
- return {"message": "Reputation Monitor API is running. Go to /docs for Swagger UI."}
 
 
27
 
28
- # 4. Endpoint di Health Check (fondamentale per MLOps e Docker)
29
- # Serve a capire se il container Γ¨ vivo
30
  @app.get("/health")
31
  def health_check():
32
  # VERIFICA: Controlliamo se il modello Γ¨ stato caricato in memoria
33
  if model_instance.model is not None:
34
  return {"status": "ok", "model_loaded": True}
35
- else:
36
- # Se il modello non c'Γ¨, restituiamo errore 503 (Service Unavailable)
37
- raise HTTPException(status_code=503, detail="Model not loaded yet")
38
 
39
- # 5. Endpoint di Previsione (Dummy per ora)
40
- @app.post("/predict", response_model=SentimentResponse)
41
- def predict_sentiment(request: SentimentRequest):
 
 
 
 
 
 
 
 
42
  try:
43
- # Chiamiamo la funzione predict del nostro loader
44
- sentiment, confidence = model_instance.predict(request.text)
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  return {
47
- "sentiment": sentiment,
48
- "confidence": confidence
 
49
  }
 
50
  except Exception as e:
51
  # Se qualcosa va storto, restituiamo errore 500
52
  raise HTTPException(status_code=500, detail=str(e))
 
1
  from fastapi import FastAPI, HTTPException
2
  from pydantic import BaseModel
3
+ from typing import List
4
+ import csv
5
+ import os
6
+ from datetime import datetime
7
+
8
+ # Importiamo l'istanza del modello e l'istanza di GoogleNews per ricerca news
9
  from app.model.loader import model_instance
10
+ from app.services.news_client import news_instance # Assicurati che il file si chiami news_client.py
11
 
12
  # 1. Inizializziamo l'app
13
  app = FastAPI(
14
  title="Reputation Monitor API",
15
+ description="API per l'analisi del sentiment della reputazione aziendale sulla base di news estrapolate con Google di un azienda/soggetto dato in input",
16
  version="1.0.0"
17
  )
18
 
19
+ # --- MONITORAGGIO (Logging su CSV) ---
20
+ LOG_FILE = "reputation_logs.csv"
21
+ if not os.path.exists(LOG_FILE):
22
+ with open(LOG_FILE, mode='w', newline='', encoding='utf-8') as file:
23
+ writer = csv.writer(file)
24
+ writer.writerow(["timestamp", "query", "text", "sentiment", "confidence"])
25
+
26
+ def log_prediction(query, text, sentiment, confidence):
27
+ with open(LOG_FILE, mode='a', newline='', encoding='utf-8') as file:
28
+ writer = csv.writer(file)
29
+ writer.writerow([datetime.now(), query, text, sentiment, confidence])
30
 
31
+ # --- MODELLI DATI (Pydantic) ---
32
+ class AnalysisRequest(BaseModel):
33
+ query: str # Es. "Tesla"
34
+ limit: int = 5
35
+
36
+ class SingleResult(BaseModel):
37
+ text: str
38
  sentiment: str
39
  confidence: float
40
 
41
+ class AnalysisResponse(BaseModel):
42
+ query: str
43
+ results: List[SingleResult]
44
+ summary: dict # Es. {"positive": 3, "negative": 1}
45
+
46
+ # --- ENDPOINTS ---
47
 
 
 
48
  @app.get("/health")
49
  def health_check():
50
  # VERIFICA: Controlliamo se il modello Γ¨ stato caricato in memoria
51
  if model_instance.model is not None:
52
  return {"status": "ok", "model_loaded": True}
53
+ raise HTTPException(status_code=503, detail="Model not loaded")
 
 
54
 
55
+ @app.post("/analyze", response_model=AnalysisResponse)
56
+ def analyze_company(request: AnalysisRequest):
57
+ # --- DEBUG PRINT ---
58
+ print(f"πŸ”₯ API RECEIVED REQUEST -> Query: {request.query}, Limit: {request.limit}")
59
+ # -------------------
60
+ """
61
+ 1. Cerca news su Google
62
+ 2. Analizza il sentiment di ogni news
63
+ 3. Salva i log
64
+ 4. Restituisce il report completo
65
+ """
66
  try:
67
+ # 1. Scarica le News
68
+ news_texts = news_instance.search_news(request.query, limit=request.limit)
69
 
70
+ if not news_texts:
71
+ return {"query": request.query, "results": [], "summary": {}}
72
+
73
+ analyzed_results = []
74
+ sentiment_counts = {"positive": 0, "neutral": 0, "negative": 0}
75
+
76
+ # 2. Analizza ogni notizia col Modello
77
+ for text in news_texts:
78
+ sentiment, confidence = model_instance.predict(text)
79
+
80
+ # Aggiorna conteggi
81
+ sentiment_counts[sentiment] += 1
82
+
83
+ # Aggiungi alla lista
84
+ analyzed_results.append({
85
+ "text": text,
86
+ "sentiment": sentiment,
87
+ "confidence": confidence
88
+ })
89
+
90
+ # 3. Logga per il monitoraggio (Punto 2 dell'esercizio)
91
+ log_prediction(request.query, text, sentiment, confidence)
92
+
93
  return {
94
+ "query": request.query,
95
+ "results": analyzed_results,
96
+ "summary": sentiment_counts
97
  }
98
+
99
  except Exception as e:
100
  # Se qualcosa va storto, restituiamo errore 500
101
  raise HTTPException(status_code=500, detail=str(e))
app/services/__pycache__/news_client.cpython-312.pyc ADDED
Binary file (2.26 kB). View file
 
app/services/news_client.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from GoogleNews import GoogleNews
2
+
3
+ class NewsClient:
4
+ def __init__(self):
5
+ # Configura Google News (Lingua Inglese, ultimi 7 giorni)
6
+ self.googlenews = GoogleNews(lang='en', period='7d')
7
+ print("βœ… Google News Client initialized")
8
+
9
+ def search_news(self, query: str, limit: int = 10):
10
+ """
11
+ Cerca news iterando sulle pagine finchΓ© non raggiunge il 'limit'.
12
+ """
13
+ print(f"πŸ”Ž Searching News for: {query} (Target: {limit})...")
14
+ self.googlenews.clear()
15
+
16
+ # 1. Prima ricerca (Pagina 1)
17
+ self.googlenews.search(query)
18
+ all_results = self.googlenews.result()
19
+
20
+ # 2. Se non bastano, scarichiamo le pagine successive
21
+ page = 2
22
+ while len(all_results) < limit:
23
+ print(f" ... Fetching page {page} to reach limit ...")
24
+ self.googlenews.getpage(page)
25
+ new_results = self.googlenews.result()
26
+
27
+ # Se la pagina nuova non aggiunge nulla, ci fermiamo (fine notizie)
28
+ if len(new_results) == len(all_results):
29
+ break
30
+
31
+ all_results = new_results
32
+ page += 1
33
+
34
+ # Safety break: Non andiamo oltre pagina 5 per non bloccare tutto
35
+ if page > 5:
36
+ break
37
+
38
+ # 3. Estrazione e Pulizia
39
+ texts_found = []
40
+ # Prendiamo un po' piΓΉ risultati del necessario per compensare quelli vuoti
41
+ for news in all_results:
42
+ full_text = f"{news['title']}. {news['desc']}"
43
+
44
+ # Pulizia base: evitiamo stringhe vuote o troppo corte
45
+ if len(full_text) > 20:
46
+ texts_found.append(full_text)
47
+
48
+ # Se abbiamo raggiunto il numero richiesto, ci fermiamo
49
+ if len(texts_found) >= limit:
50
+ break
51
+
52
+ print(f"βœ… Found {len(texts_found)} news items for '{query}'")
53
+ return texts_found
54
+
55
+ news_instance = NewsClient()
reputation_logs.csv ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ timestamp,query,text,sentiment,confidence
2
+ 2025-12-11 23:33:12.555398,Elettromedia Spa,"Kia EV6 Owner Discovers Factory Wiring Error in Subwoofer, Fixes Weak Sound in Minutes. The EV6's weak sound system has frustrated owners, but reversing a few wires running to the Meridian subwoofer is all it takes to boost bass dramatically.",negative,0.6096141934394836
3
+ 2025-12-11 23:33:12.740590,Elettromedia Spa,"Automotive Audio Speakers Market to Grow by USD 9.02 Billion (2024-2028) with Cost-Effective Aftermarket Speakers Boosting the Market, Report on Market Evolution Powered by AI - Technavio. Report on how AI is driving market transformation - The global automotive audio speakers market size is estimated to grow by USD 9.02 billion from 2024-2028...",positive,0.6643907427787781
4
+ 2025-12-11 23:33:12.872823,Elettromedia Spa,"Lavoce Italiana Comes to North America. Italian transducer company Lavoce Italiana has established its first U.S. division, Elettromedia Corporation, based in Old Hickory, TN.",neutral,0.7901442050933838
5
+ 2025-12-11 23:33:13.005912,Elettromedia Spa,"Elettromedia Training at KnowledgeFest. Press Release: Irvine, CA – Elettromedia USA is hosting several training sessions at KnowledgeFest Las Vegas February 3rd-5th.",neutral,0.8425894975662231
6
+ 2025-12-11 23:40:24.248518,Ferrari,Ferrari crash: Luxury car had a history of speeding. Kolkata: Kolkata Police will seek help of engineers from Ferrari to study the reasons that led to one of their cars getting involved in an accident on.,negative,0.62410968542099
7
+ 2025-12-11 23:40:24.383238,Ferrari,"Adrian Newey or Max Verstappen: what would Ferrari’s former key figures choose?. Maurizio Arrivabene and Luigi Mazzola reveal whether Ferrari should choose Adrian Newey or Max Verstappen, highlighting why technical excellence is more...",neutral,0.9174836277961731
8
+ 2025-12-11 23:40:24.498802,Ferrari,60 Years Of The Dino: How Ferrari Turned A β€œJunior” Into A Legend. Ferrari's new feature on the Dino's 60th anniversary reveals how its first mid-engined V6 road car was hand-shaped into an icon that is now a blue-chip...,neutral,0.566238284111023
9
+ 2025-12-11 23:40:24.606439,Ferrari,Former Ferrari boss weighs in on departing engine specialists: what Maranello should worry about. Maurizio Arrivabene discusses the departure of key Ferrari engine specialists and the potential impact on the team's 2026 F1 season as rivals prepare for...,neutral,0.9085403680801392
10
+ 2025-12-11 23:40:24.708324,Ferrari,"Lewis Hamilton warned his F1 legacy is at risk after disastrous first season with Ferrari. Lewis Hamilton faces growing concerns about damaging his Formula 1 legacy after a difficult first season with Ferrari, as his ongoing struggles and lack of...",negative,0.7664820551872253
11
+ 2025-12-11 23:40:24.824364,Ferrari,"While confident he made the most of what Ferrari gave him this year, Leclerc hopes for a lot more from 2026. After Ferrari's promising form of 2024 faded in 2025, Charles Leclerc is staying cautious about the team's chances of a quick rebound under the incoming...",positive,0.6923292875289917
12
+ 2025-12-11 23:40:24.913603,Ferrari,"Bookies open betting on Lewis Hamilton retiring after nightmare season at Ferrari. When Lewis Hamilton announced he was joining Ferrari for the 2025 season, everything seemed to be lined up for a title charge.",neutral,0.7515650391578674
13
+ 2025-12-11 23:40:25.015110,Ferrari,"Representing Ferrari, Alba Larsen is living a ""dream come true"".. Every racing driver dreams of racing in the famous Ferrari red, and at just 16 years old, Alba Larsen is about to achieve this feat.",positive,0.9591248035430908
14
+ 2025-12-11 23:40:25.173862,Ferrari,"Ferrari: Fred Vasseur cautions Lewis Hamilton and Charles Leclerc about a key pitfall ahead of 2026 F1 campaign. Ferrari enter the 2026 Formula 1 season under huge pressure, with Lewis Hamilton and Charles Leclerc urged to push the team relentlessly rather than offer...",neutral,0.897258996963501
15
+ 2025-12-11 23:40:25.289483,Ferrari,"""Lewis Hamilton isn't finished"": Former Ferrari boss tries to knock 'common sense' into the team's driver decisions. Former Ferrari F1 team principal Maurizio Arrivabene has lent support to Lewis Hamilton after his nightmare first season with the Scuderia.",neutral,0.681147038936615
16
+ 2025-12-11 23:40:25.392379,Ferrari,"Lewis Hamilton’s dire first Ferrari season keeps drawing the same comparison to Michael Schumacher. Lewis Hamilton joining Ferrari sparked pandemonium in Maranello ahead of the 2025 F1 season, but bitter disappointment and even anger quickly replaced any...",negative,0.7126820683479309
17
+ 2025-12-11 23:40:25.496356,Ferrari,Lewis Hamilton urged to study Max Verstappen’s radio craft to help drive Ferrari forward. Lewis Hamilton is urged to adopt Max Verstappen's motivational style as Ferrari seek stronger leadership after a difficult 2025 F1 season.,neutral,0.6595488786697388
18
+ 2025-12-11 23:40:25.642155,Ferrari,Recon 2022 Ferrari Roma 3.9 Coupe SURROUND VIEW / CARBON / JBL / ELECTRIC SEAT / INCOMING STOCK. 2022 Ferrari Roma 3.9 Coupe SURROUND VIEW / CARBON / JBL / ELECTRIC SEAT / INCOMING STOCK. Find all the best used / second hand and new cars from trusted...,neutral,0.69819575548172
19
+ 2025-12-11 23:40:25.788479,Ferrari,"Ghost in white: a nearly-new, 986 HP Ferrari SF90 Stradale hits the market. This impeccably specced SF90 Stradale comes with a clean Carfax report and is now ready to find a new home. Ferrari SF90 Stradale.",positive,0.8663274645805359
20
+ 2025-12-11 23:40:25.889634,Ferrari,Lewis Hamilton gets response from Ferrari after joint complaints with Charles Leclerc. Lewis Hamilton and Charles Leclerc spent the Formula 1 season complaining about how uncompetitive their Ferrari cars were compared to their championship...,neutral,0.6231443285942078
21
+ 2025-12-11 23:40:26.000135,Ferrari,"214-Mile 2025 Ferrari 12Cilindri For Sale. Grab the keys to Ferrari's latest and greatest supercar! While most Ferraris have historically been recognized for their mid-engine configurations, Enzo.",positive,0.797694981098175
22
+ 2025-12-11 23:40:26.115016,Ferrari,"Dubai Police Force Commits Crime Against Sanity With Mansory Purosangue. Dubai's police force is all about putting on a show. Its fleet is legendary for its collection of supercars, hypercars, and even four-rotor flying...",neutral,0.5327199101448059
23
+ 2025-12-11 23:40:26.236435,Ferrari,"Carlos Sainz silences Williams β€˜doubts’ after Ferrari departure. Carlos Sainz admitted ""there were doubts"" from outside about his move to Williams in 2025, but he believes the team is on an upward trend.",neutral,0.8175623416900635
24
+ 2025-12-11 23:40:26.336150,Ferrari,"Europe cracks down on rule-breaking Ferrari, Rolls-Royce owners. Authorities in London and Vienna recently took these rich dweebs down a peg and let them know they can't just do whatever they want.",negative,0.7607141733169556
25
+ 2025-12-11 23:40:26.446514,Ferrari,Lewis Hamilton's Dad may have just set up Ferrari star's F1 retirement. Seven-time F1 world champion Lewis Hamilton may just have been given a retirement plan from his father Anthony Hamilton.,neutral,0.5159865617752075
26
+ 2025-12-11 23:40:44.616365,Elettromedia,Elettromedia USA Names New President. Rob Wempe to President of Mobile Audio for Advanced Marketing companies. Wempe was previously VP of Sales & Marketing at the company for fifteen years.,neutral,0.7228948473930359
27
+ 2025-12-11 23:40:44.727524,Elettromedia,"Elettromedia USA Parent Buys Biketronics. Advanced Marketing, the parent company of Elettromedia USA, American Hard Bag and Velocity brands is proud to announce the acquisition of Biketronics located...",positive,0.8176090717315674
28
+ 2025-12-11 23:40:44.818475,Elettromedia,"Elettromedia Elevated in MESA Role. Elettromedia, takes an elevated position as a vendor for the car audio Mobile Electronics Specialists of America buying group.",positive,0.6863150596618652
29
+ 2025-12-11 23:40:44.936782,Elettromedia,"Elettromedia Adds Automated DSP Tuning. Elettromedia Adds Automated DSP Tuning ... Elettromedia, maker of Hertz and Audison, is offering the next generation of its tuning software, bit Drive 2.0. It...",neutral,0.5924315452575684
30
+ 2025-12-11 23:40:45.039521,Elettromedia,"Elettromedia USA Owner Buys This Company. Elettromedia USA Owner Buys This Company ... Advanced Marketing, the US Distributor of Hertz, Audison and other brands, announced it is taking over the operations...",neutral,0.7651730179786682
31
+ 2025-12-11 23:40:45.137103,Elettromedia,"Former JL Exec Joins Hertz/Audison Marine. Elettromedia announced the appointment of Brian Power as Vice President of its Marine Division, which includes both the Hertz and Audison marine audio...",neutral,0.8856467008590698
32
+ 2025-12-11 23:40:45.252198,Elettromedia,"Elettromedia Names New Rep. Press Release (UNEDITED): IRVINE, CA– Elettromedia-USA, manufacturer of premium electronics and speaker systems for the automotive, marine, and motorsports...",neutral,0.9273980855941772
33
+ 2025-12-11 23:40:45.397305,Elettromedia,"Lavoce Italiana Comes to North America. Italian transducer company Lavoce Italiana has established its first U.S. division, Elettromedia Corporation, based in Old Hickory, TN.",neutral,0.7901442050933838
34
+ 2025-12-11 23:40:45.510152,Elettromedia,Audison Announces Wide Availability of Forza Amps. Press Release (UNEDITED): LAS VEGAS– Audison announces wide availability for award-winning Forza DSP amplifiers. β€œAfter a very successful launch of AF Forza...,positive,0.5248441100120544
35
+ 2025-12-11 23:40:45.602417,Elettromedia,"Elettromedia Training at KnowledgeFest. Press Release: Irvine, CA – Elettromedia USA is hosting several training sessions at KnowledgeFest Las Vegas February 3rd-5th.",neutral,0.8425894975662231
36
+ 2025-12-11 23:40:45.700735,Elettromedia,Elettromedia Names Reps of the Year 2021. Press Release: Elettromedia is proud to announce the following reps and rep firms that were named Elettromedia Reps of the Year for 2021.,positive,0.9222844839096069
37
+ 2025-12-11 23:40:45.802643,Elettromedia,"Elettromedia Hosts Reps in Italy. Press Release: Elettromedia will be conducting a sales rep training in Potenza Picena, Italy in March. Forty USA field reps will be trained on new products,...",neutral,0.8989458084106445
38
+ 2025-12-11 23:40:45.897368,Elettromedia,"Elettromedia Appoints New Executive. Effectively immediately, Pilgrim will work with dealers to supply marketing support and materials and help drive sales. He will also keep a hand technical...",neutral,0.7104781270027161
39
+ 2025-12-11 23:40:45.998556,Elettromedia,Advanced Marketing Exits Full Line 12 Volt Distribution. Advanced Marketing (AM) announced it is ceasing its full-line car audio wholesale distribution business. Instead it will devote its full efforts to growing...,neutral,0.7373328804969788
40
+ 2025-12-11 23:40:46.081800,Elettromedia,"Elettromedia Names New Rep. Elettromedia has appointed Current Marketing as its representative for the Arizona and southern Nevada territories, effective immediately.",neutral,0.9036652445793152
41
+ 2025-12-11 23:40:46.176913,Elettromedia,Elettromedia Promotes Wempe and Delgado. Elettromedia-USA has promoted Rob Wempe to Vice President of the company from his former post as Director of Sales and Marketing.,neutral,0.4994317293167114
42
+ 2025-12-11 23:40:46.264677,Elettromedia,"Larry Frederick Leaves Elettromedia. Prominent, veteran car audio product developer Larry Frederick has left Elettromedia and is entertaining other possibilities in car electronics.",neutral,0.5087681412696838
43
+ 2025-12-11 23:42:09.006472,Celaschi,Charleroi will pursue funding from restitution in Ha case. Councilman Larry Celaschi said the borough was harmed by the failure to pay taxes on illegal aliens.,negative,0.6671392917633057
44
+ 2025-12-11 23:42:09.109556,Celaschi,"Charleroi councilman claims glass plant will reopen, but other officials cannot confirm. The closed Corelle Brands plant in Charleroi, shown on Friday. After it closed in April, production was moved to Lancaster, Ohio.",neutral,0.85775226354599
45
+ 2025-12-11 23:42:09.233219,Celaschi,"Perryopolis man held for court in vehicular death case. John Delbert Celaschi Jr., 25, of Perryopolis, was held for court on all but one charge filed against him in Fayette County in relation to the vehicular...",neutral,0.715713381767273
46
+ 2025-12-11 23:42:09.340242,Celaschi,"Immigrants Rebuilt a Pennsylvania Town β€” Then Became Targets. Larry Celaschi summons me to look at his cell phone, which displays a photo of a truck. The picture, which someone shared with Celaschi, features the...",neutral,0.945743203163147
47
+ 2025-12-11 23:42:09.465843,Celaschi,"Andrew Celaschi Obituary (2025) - Carmichaels, PA - Observer-Reporter. Andrew Celaschi Obituary. Andrew Michael Celaschi, 25, died Tuesday, April 29, 2025, from injuries sustained when he was struck by a tree...",neutral,0.7802605032920837
48
+ 2025-12-11 23:42:09.576318,Celaschi,"Greene man killed Tuesday when tree falls on car. Andrew Celaschi, 25, was the passenger in a Toyota Corolla that was headed westbound on Jefferson Road in Franklin Township, about 150 feet north of the...",negative,0.5343117713928223
49
+ 2025-12-11 23:42:09.678246,Celaschi,"Perryopolis man charged with homicide by vehicle. A Perryopolis man faces homicide by vehicle while driving under the influence charges following a two-vehicle head-on crash Sept. 21, 2024.",negative,0.7082436680793762
50
+ 2025-12-11 23:42:09.781644,Celaschi,"Fayette County man charged with vehicular homicide in crash that killed Ringgold grad. John Celaschi, 25, was leaving a wedding when the truck he was driving allegedly crossed the center line on Brownsville Road in Jefferson Township, hitting a...",negative,0.6681841015815735
51
+ 2025-12-11 23:42:09.877497,Celaschi,"Perryopolis man charged with homicide by vehicle. A Perryopolis man faces homicide by vehicle while driving under the influence charges following a two-vehicle head-on accident on Sept. 21, 2024.",negative,0.695264458656311
52
+ 2025-12-11 23:42:09.992874,Celaschi,"Victor Celaschi. He is survived by his beloved wife of 67 years, Alberta E. Celaschi (nee: Miller); his children, Victoria Aring (Lee) of Avon, and Victor Celaschi (Marie) of...",neutral,0.6017436981201172
53
+ 2025-12-11 23:42:10.093366,Celaschi,"Bearcats grapplers pin down ’Dores. Bentworth dug deep to secure a hard-fought 42-26 victory over Frazier Wednesday night, using timely wins, clutch performances and key forfeits to pull away...",neutral,0.5737037062644958
54
+ 2025-12-11 23:42:10.183541,Celaschi,"Charleroi council president statement draws ire of fellow council member. Councilman Larry Celaschi expressed his displeasure, taking issue with a statement concerning the plant property and its possible availability.",negative,0.6969603896141052
55
+ 2025-12-11 23:42:10.289656,Celaschi,"Donora man killed in head-on collision in Fayette County. Quinte Lamar Brown, 26, of Donora, was traveling in the 800 block of Brownsville Road in Jefferson Township shortly before 11 pm when a vehicle being driven by...",neutral,0.5457555055618286
56
+ 2025-12-11 23:42:10.385365,Celaschi,"Donora man dies in Fayette County crash. Troopers said Quinte Lamar Brown, 26, was driving a Chevrolet Cobalt on Brownsville Road in Jefferson Township just before 11 pm when an oncoming Chevrolet...",neutral,0.588758647441864
57
+ 2025-12-11 23:42:10.478229,Celaschi,Efforts underway to save Charleroi plant. The company announced last week that it plans to shut down the facility by the end of the year. It's not over until it's over.,neutral,0.5509985089302063
58
+ 2025-12-11 23:42:10.611811,Celaschi,"Tracy Lynn Herrada Obituary - Wolverine Lake , MI (1962-2024). Tracy Lynn Herrada (Celaschi) OBITUARY. Beloved wife of 20 years to Randall Herrada. Loving mother of Michelle (Abraham) Handgis, Jennifer (Stan) Sebastian,...",neutral,0.5667412877082825
59
+ 2025-12-11 23:42:10.714440,Celaschi,"Linda Bailey Obituary (2023) - Carmichaels, PA - Observer-Reporter. Linda Joyce Bailey, 77, of Rices Landing, died Thursday, October 12, 2023, in Premier Care, Washington.",neutral,0.7752928137779236
60
+ 2025-12-11 23:42:10.829393,Celaschi,"Claire Elizabeth Kolt Obituary. In Loving Memory of Claire Elizabeth Kolt A Bright Light Taken Too Soon It is with heavy hearts that we mourn the loss of our beloved Claire, a...",neutral,0.47717931866645813
61
+ 2025-12-11 23:42:10.923217,Celaschi,"Clearview FCU's former CEO at helm of credit union in Chambersburg. Ron Celaschi, president and CEO at Clearview FCU for five years, joined Patriot FCU last month.",neutral,0.9147176742553711
62
+ 2025-12-11 23:42:11.007703,Celaschi,"Patriot Federal Credit Union in Pennsylvania names new CEO. Ronald Celaschi will succeed Brad Warner, who announced his intention to retire last year.",neutral,0.8869218826293945
requirements.txt CHANGED
@@ -1,19 +1,16 @@
1
- # --- Web API ---
2
  fastapi==0.109.0
3
  uvicorn[standard]==0.27.0
4
  pydantic==2.6.0
5
-
6
- # --- Machine Learning (Base) ---
7
- # Per ora mettiamo scikit-learn/pandas per testare,
8
- # poi aggiungeremo transformers/torch se usiamo RoBERTa
9
- pandas
10
- scikit-learn
11
- joblib
12
  torch
13
  transformers
14
  scipy
15
  numpy
16
-
 
 
 
 
 
17
  # --- Testing ---
18
  pytest
19
  httpx==0.27.0
 
 
1
  fastapi==0.109.0
2
  uvicorn[standard]==0.27.0
3
  pydantic==2.6.0
 
 
 
 
 
 
 
4
  torch
5
  transformers
6
  scipy
7
  numpy
8
+ # --- Scraping ---
9
+ GoogleNews
10
+ # --- Frontend ---
11
+ streamlit
12
+ requests
13
+ pandas
14
  # --- Testing ---
15
  pytest
16
  httpx==0.27.0
streamlit_app/app.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import pandas as pd
4
+ import time
5
+
6
+ # Configurazione Pagina
7
+ st.set_page_config(page_title="Reputation Monitor", page_icon="πŸ“Š", layout="wide")
8
+
9
+ st.title("πŸ“Š AI Reputation Monitor")
10
+ st.markdown("Monitor brand reputation using **Google News** and **RoBERTa AI**.")
11
+
12
+ # URL dell'API (Se siamo in Docker usa localhost, altrimenti l'URL dello Space)
13
+ # In Codespaces locale usa questo:
14
+ API_URL = "http://localhost:8000"
15
+
16
+ # --- SIDEBAR ---
17
+ with st.sidebar:
18
+ st.header("βš™οΈ Configuration")
19
+ target_company = st.text_input("Company/Brand to monitor:", value="Ferrari")
20
+ num_news = st.slider("Number of news to analyze:", 1, 20, 5)
21
+ analyze_btn = st.button("πŸš€ Analyze Reputation")
22
+ st.divider()
23
+ st.info("System Status: Online 🟒")
24
+
25
+ # --- MAIN LOGIC ---
26
+ if analyze_btn:
27
+ if target_company:
28
+ with st.spinner(f"πŸ” Searching news for '{target_company}' and analyzing sentiment..."):
29
+ try:
30
+ # Chiamata all'API
31
+ payload = {"query": target_company, "limit": num_news}
32
+ response = requests.post(f"{API_URL}/analyze", json=payload)
33
+
34
+ if response.status_code == 200:
35
+ data = response.json()
36
+ results = data['results']
37
+ summary = data['summary']
38
+
39
+ # 1. METRICHE (KPI)
40
+ col1, col2, col3 = st.columns(3)
41
+ col1.metric("Positive News", summary.get('positive', 0), delta_color="normal")
42
+ col2.metric("Negative News", summary.get('negative', 0), delta_color="inverse")
43
+ col3.metric("Neutral News", summary.get('neutral', 0), delta_color="off")
44
+
45
+ # 2. GRAFICI
46
+ st.subheader("Sentiment Distribution")
47
+ chart_data = pd.DataFrame({
48
+ "Sentiment": list(summary.keys()),
49
+ "Count": list(summary.values())
50
+ })
51
+ st.bar_chart(chart_data, x="Sentiment", y="Count", color="Sentiment")
52
+
53
+ # 3. DETTAGLI (Tabella)
54
+ st.subheader("Latest News Analyzed")
55
+ for item in results:
56
+ color = "green" if item['sentiment'] == "positive" else "red" if item['sentiment'] == "negative" else "gray"
57
+ with st.expander(f":{color}[{item['sentiment'].upper()}] - {item['text'][:80]}..."):
58
+ st.write(f"**Full Text:** {item['text']}")
59
+ st.write(f"**Confidence:** {item['confidence']:.2%}")
60
+
61
+ else:
62
+ st.error(f"Error {response.status_code}: {response.text}")
63
+ except Exception as e:
64
+ st.error(f"Connection Error: {e}. Is the API running?")
65
+ else:
66
+ st.warning("Please enter a company name.")
67
+
68
+ # --- MONITORING TAB (Punto 2 dell'esercizio) ---
69
+ st.divider()
70
+ st.header("πŸ“ˆ Continuous Monitoring Logs")
71
+ if st.button("Refresh Logs"):
72
+ try:
73
+ # Leggiamo il CSV generato dall'API
74
+ # Nota: Funziona perchΓ© in Codespaces condividiamo il file system.
75
+ # In produzione servirebbe un endpoint API dedicato /get_logs
76
+ if requests.get(f"{API_URL}/health").status_code == 200:
77
+ # Trucco: leggiamo il file locale (se siamo in locale)
78
+ try:
79
+ df_logs = pd.read_csv("reputation_logs.csv")
80
+ st.dataframe(df_logs.sort_values(by="timestamp", ascending=False).head(50))
81
+ except:
82
+ st.warning("Logs not accessible directly (Are you in Docker?)")
83
+ except:
84
+ st.warning("API not reachable")
test_scraper.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from app.services.news_client import news_instance
2
+
3
+ azienda = "Elettromedia S.P.A."
4
+ notizie = news_instance.search_news(azienda, limit=5)
5
+
6
+ print(f"\n--- Ultime notizie su {azienda} ---")
7
+ for i, testo in enumerate(notizie):
8
+ print(f"[{i+1}] {testo}")
tests/test_api.py CHANGED
@@ -30,6 +30,24 @@ def test_prediction_negative():
30
  assert response.status_code == 200
31
  assert response.json()["sentiment"] == "negative"
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  """
35
  scrivi sul terminale: pytest Serve per lanciare il test
 
30
  assert response.status_code == 200
31
  assert response.json()["sentiment"] == "negative"
32
 
33
+ def test_analyze_endpoint():
34
+ """
35
+ Verifica che l'endpoint complesso /analyze risponda correttamente.
36
+ Nota: Questo test effettua una chiamata reale a Google News.
37
+ """
38
+ payload = {"query": "Apple", "limit": 1}
39
+ response = client.post("/analyze", json=payload)
40
+
41
+ assert response.status_code == 200
42
+ data = response.json()
43
+
44
+ # Verifichiamo la struttura della risposta
45
+ assert "query" in data
46
+ assert "results" in data
47
+ assert "summary" in data
48
+ # Verifichiamo che summary abbia i conteggi
49
+ assert "positive" in data["summary"]
50
+
51
 
52
  """
53
  scrivi sul terminale: pytest Serve per lanciare il test