Spaces:
Running
Running
Delete Data_Fetching_and_Rendering.py
Browse files- Data_Fetching_and_Rendering.py +0 -213
Data_Fetching_and_Rendering.py
DELETED
@@ -1,213 +0,0 @@
|
|
1 |
-
import json
|
2 |
-
import requests
|
3 |
-
import html
|
4 |
-
from datetime import datetime
|
5 |
-
from collections import defaultdict
|
6 |
-
from transformers import pipeline
|
7 |
-
|
8 |
-
from sessions import create_session
|
9 |
-
from error_handling import display_error
|
10 |
-
from posts_categorization import batch_summarize_and_classify
|
11 |
-
|
12 |
-
import logging
|
13 |
-
|
14 |
-
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
15 |
-
|
16 |
-
API_V2_BASE = 'https://api.linkedin.com/v2'
|
17 |
-
API_REST_BASE = "https://api.linkedin.com/rest"
|
18 |
-
|
19 |
-
# Load sentiment model
|
20 |
-
sentiment_pipeline = pipeline("text-classification", model="tabularisai/multilingual-sentiment-analysis")
|
21 |
-
|
22 |
-
def fetch_org_urn(comm_client_id, comm_token_dict):
|
23 |
-
if not comm_token_dict or 'access_token' not in comm_token_dict:
|
24 |
-
raise ValueError("Marketing token is missing or invalid.")
|
25 |
-
|
26 |
-
session = create_session(comm_client_id, token=comm_token_dict)
|
27 |
-
url = (
|
28 |
-
f"{API_V2_BASE}/organizationalEntityAcls"
|
29 |
-
"?q=roleAssignee&role=ADMINISTRATOR&state=APPROVED"
|
30 |
-
"&projection=(elements*(*,organizationalTarget~(id,localizedName)))"
|
31 |
-
)
|
32 |
-
|
33 |
-
try:
|
34 |
-
response = session.get(url)
|
35 |
-
response.raise_for_status()
|
36 |
-
except requests.exceptions.RequestException as e:
|
37 |
-
status = getattr(e.response, 'status_code', 'N/A')
|
38 |
-
try:
|
39 |
-
details = e.response.json()
|
40 |
-
except Exception:
|
41 |
-
details = str(e)
|
42 |
-
raise ValueError(f"Failed to fetch Organization details (Status: {status}): {details}") from e
|
43 |
-
|
44 |
-
elements = response.json().get('elements')
|
45 |
-
if not elements:
|
46 |
-
raise ValueError("No organizations found with ADMINISTRATOR role.")
|
47 |
-
|
48 |
-
org = elements[0]
|
49 |
-
org_urn = org.get('organizationalTarget')
|
50 |
-
org_name = org.get(next((k for k in org if k.endswith('organizationalTarget~')), {}), {}).get('localizedName')
|
51 |
-
|
52 |
-
if not org_urn or not org_urn.startswith("urn:li:organization:"):
|
53 |
-
raise ValueError("Invalid Organization URN.")
|
54 |
-
if not org_name:
|
55 |
-
org_id = org_urn.split(":")[-1]
|
56 |
-
org_name = f"Organization ({org_id})"
|
57 |
-
|
58 |
-
return org_urn, org_name
|
59 |
-
|
60 |
-
def fetch_comments(comm_client_id, token_dict, post_urns, stats_map):
|
61 |
-
from requests_oauthlib import OAuth2Session
|
62 |
-
linkedin = OAuth2Session(comm_client_id, token=token_dict)
|
63 |
-
linkedin.headers.update({'LinkedIn-Version': "202502"})
|
64 |
-
|
65 |
-
all_comments = {}
|
66 |
-
for post_urn in post_urns:
|
67 |
-
if stats_map.get(post_urn, {}).get('commentCount', 0) == 0:
|
68 |
-
continue
|
69 |
-
|
70 |
-
try:
|
71 |
-
url = f"{API_REST_BASE}/socialActions/{post_urn}/comments"
|
72 |
-
response = linkedin.get(url)
|
73 |
-
if response.status_code == 200:
|
74 |
-
elements = response.json().get('elements', [])
|
75 |
-
all_comments[post_urn] = [c.get('message', {}).get('text') for c in elements if c.get('message')]
|
76 |
-
else:
|
77 |
-
all_comments[post_urn] = []
|
78 |
-
except Exception:
|
79 |
-
all_comments[post_urn] = []
|
80 |
-
|
81 |
-
return all_comments
|
82 |
-
|
83 |
-
def analyze_sentiment(comments_data):
|
84 |
-
results = {}
|
85 |
-
for post_urn, comments in comments_data.items():
|
86 |
-
sentiment_counts = defaultdict(int)
|
87 |
-
total = 0
|
88 |
-
|
89 |
-
for comment in comments:
|
90 |
-
if not comment:
|
91 |
-
continue
|
92 |
-
try:
|
93 |
-
result = sentiment_pipeline(comment)
|
94 |
-
label = result[0]['label'].upper()
|
95 |
-
if label in ['POSITIVE', 'VERY POSITIVE']:
|
96 |
-
sentiment_counts['Positive 👍'] += 1
|
97 |
-
elif label in ['NEGATIVE', 'VERY NEGATIVE']:
|
98 |
-
sentiment_counts['Negative 👎'] += 1
|
99 |
-
elif label == 'NEUTRAL':
|
100 |
-
sentiment_counts['Neutral 😐'] += 1
|
101 |
-
else:
|
102 |
-
sentiment_counts['Unknown'] += 1
|
103 |
-
total += 1
|
104 |
-
except:
|
105 |
-
sentiment_counts['Error'] += 1
|
106 |
-
|
107 |
-
dominant = max(sentiment_counts, key=sentiment_counts.get, default='Neutral 😐')
|
108 |
-
percentage = round((sentiment_counts[dominant] / total) * 100, 1) if total else 0.0
|
109 |
-
results[post_urn] = {"sentiment": dominant, "percentage": percentage}
|
110 |
-
|
111 |
-
return results
|
112 |
-
|
113 |
-
def fetch_posts_and_stats(comm_client_id, community_token, count=10):
|
114 |
-
token_dict = community_token if isinstance(community_token, dict) else {'access_token': community_token, 'token_type': 'Bearer'}
|
115 |
-
session = create_session(comm_client_id, token=token_dict)
|
116 |
-
|
117 |
-
#org_urn, org_name = fetch_org_urn(comm_client_id, token_dict)
|
118 |
-
org_urn, org_name = "urn:li:organization:19010008", "GRLS"
|
119 |
-
posts_url = f"{API_REST_BASE}/posts?author={org_urn}&q=author&count={count}&sortBy=LAST_MODIFIED"
|
120 |
-
|
121 |
-
try:
|
122 |
-
resp = session.get(posts_url)
|
123 |
-
resp.raise_for_status()
|
124 |
-
raw_posts = resp.json().get("elements", [])
|
125 |
-
except requests.exceptions.RequestException as e:
|
126 |
-
status = getattr(e.response, 'status_code', 'N/A')
|
127 |
-
raise ValueError(f"Failed to fetch posts (Status: {status})") from e
|
128 |
-
|
129 |
-
if not raw_posts:
|
130 |
-
return [], org_name, {}
|
131 |
-
|
132 |
-
post_urns = [p["id"] for p in raw_posts if ":share:" in p["id"] or ":ugcPost:" in p["id"]]
|
133 |
-
stats_map = {}
|
134 |
-
|
135 |
-
post_texts = [{"text": p["commentary"] or p.get("specificContent", {}).get("com.linkedin.ugc.ShareContent", {}).get("shareCommentaryV2", {}).get("text", "")} for p in raw_posts]
|
136 |
-
structured_results = batch_summarize_and_classify(post_texts)
|
137 |
-
|
138 |
-
for i in range(0, len(post_urns), 20):
|
139 |
-
batch = post_urns[i:i+20]
|
140 |
-
params = {'q': 'organizationalEntity', 'organizationalEntity': org_urn}
|
141 |
-
for idx, urn in enumerate(batch):
|
142 |
-
key = f"shares[{idx}]" if ":share:" in urn else f"ugcPosts[{idx}]"
|
143 |
-
params[key] = urn
|
144 |
-
try:
|
145 |
-
stat_resp = session.get(f"{API_REST_BASE}/organizationalEntityShareStatistics", params=params)
|
146 |
-
stat_resp.raise_for_status()
|
147 |
-
for stat in stat_resp.json().get("elements", []):
|
148 |
-
urn = stat.get("share") or stat.get("ugcPost")
|
149 |
-
if urn:
|
150 |
-
stats_map[urn] = stat.get("totalShareStatistics", {})
|
151 |
-
except:
|
152 |
-
continue
|
153 |
-
|
154 |
-
comments = fetch_comments(comm_client_id, token_dict, post_urns, stats_map)
|
155 |
-
sentiments = analyze_sentiment(comments)
|
156 |
-
|
157 |
-
posts = []
|
158 |
-
for post in raw_posts:
|
159 |
-
post_id = post.get("id")
|
160 |
-
stats = stats_map.get(post_id, {})
|
161 |
-
timestamp = post.get("publishedAt") or post.get("createdAt")
|
162 |
-
when = datetime.fromtimestamp(timestamp / 1000).strftime("%Y-%m-%d %H:%M") if timestamp else "Unknown"
|
163 |
-
|
164 |
-
text = post.get("commentary") or post.get("specificContent", {}).get("com.linkedin.ugc.ShareContent", {}).get("shareCommentaryV2", {}).get("text") or "[No text]"
|
165 |
-
text = html.escape(text[:250]).replace("\n", "<br>") + ("..." if len(text) > 250 else "")
|
166 |
-
|
167 |
-
likes = stats.get("likeCount", 0)
|
168 |
-
comments_count = stats.get("commentCount", 0)
|
169 |
-
clicks = stats.get("clickCount", 0)
|
170 |
-
shares = stats.get("shareCount", 0)
|
171 |
-
impressions = stats.get("impressionCount", 0)
|
172 |
-
engagement = stats.get("engagement", likes + comments_count + clicks + shares) / impressions * 100 if impressions else 0.0
|
173 |
-
|
174 |
-
sentiment_info = sentiments.get(post_id, {"sentiment": "Neutral 😐", "percentage": 0.0})
|
175 |
-
|
176 |
-
posts.append({
|
177 |
-
"id": post_id, "when": when, "text": text, "likes": likes,
|
178 |
-
"comments": comments_count, "clicks": clicks, "shares": shares,
|
179 |
-
"impressions": impressions, "engagement": f"{engagement:.2f}%",
|
180 |
-
"sentiment": sentiment_info["sentiment"], "sentiment_percent": sentiment_info["percentage"]
|
181 |
-
})
|
182 |
-
logging.info(f"Appended post data for {post_id}: Likes={likes}, Comments={comments_count}, Shares={shares}, Clicks={clicks}")
|
183 |
-
|
184 |
-
for post, structured in zip(posts, structured_results):
|
185 |
-
post["summary"] = structured["summary"]
|
186 |
-
post["category"] = structured["category"]
|
187 |
-
|
188 |
-
return posts, org_name, sentiments
|
189 |
-
|
190 |
-
def render_post_cards(posts, org_name):
|
191 |
-
safe_name = html.escape(org_name or "Your Organization")
|
192 |
-
if not posts:
|
193 |
-
return f"<h2 style='text-align:center;color:#555;'>No recent posts found for {safe_name}.</h2>"
|
194 |
-
|
195 |
-
cards = [
|
196 |
-
f"<div style='border:1px solid #ccc;border-radius:8px;padding:15px;width:280px;background:#fff;'>"
|
197 |
-
f"<div style='font-size:0.8em;color:#666;margin-bottom:8px;'>{p['when']}</div>"
|
198 |
-
f"<div style='font-size:0.95em;margin-bottom:12px;max-height:120px;overflow:auto'>{p['text']}</div>"
|
199 |
-
f"<div style='font-size:0.9em;color:#333;border-top:1px solid #eee;padding-top:10px;'>"
|
200 |
-
f"👁️ {p['impressions']:,} | 👍 {p['likes']:,} | 💬 {p['comments']:,} | 🔗 {p['shares']:,} | 🖱️ {p['clicks']:,}<br>"
|
201 |
-
f"<strong>📈 {p['engagement']}</strong><br>"
|
202 |
-
f"<span style='color:#444;'>🧠 Sentiment: <strong>{p['sentiment']}</strong> ({p['sentiment_percent']}%)</span>"
|
203 |
-
f"</div></div>"
|
204 |
-
for p in posts
|
205 |
-
]
|
206 |
-
return f"<h2 style='text-align:center;margin-bottom:20px;'>Recent Posts for {safe_name}</h2><div style='display:flex;flex-wrap:wrap;gap:15px;justify-content:center;'>" + "".join(cards) + "</div>"
|
207 |
-
|
208 |
-
def fetch_and_render_dashboard(comm_client_id, community_token):
|
209 |
-
try:
|
210 |
-
posts, org_name, _ = fetch_posts_and_stats(comm_client_id, community_token)
|
211 |
-
return render_post_cards(posts, org_name)
|
212 |
-
except Exception as e:
|
213 |
-
return display_error("Dashboard Error", e).get('value', '<p style="color:red;text-align:center;">❌ An error occurred.</p>')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|