eishit32 commited on
Commit
a8df853
·
verified ·
1 Parent(s): 5b26d13

Create algorithm.py

Browse files
Files changed (1) hide show
  1. algorithm.py +281 -0
algorithm.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import random
3
+ from typing import List, Dict, Union, Tuple
4
+
5
+ # --- Configuration Constants (Normalized to 0-100 Scale) ---
6
+ # Weighting for the Final Content Score (CS)
7
+ W1_PREDICTION = 0.50
8
+ W2_TALENT = 0.15
9
+ W3_PROGRESS = 0.15
10
+ W4_QUALITY = 0.15
11
+ # Dynamic Allocation
12
+ MAX_DYNAMIC_BOOST = 0.05 # Max 5% of CS allowed via dynamic boosts/penalties
13
+ SUPPRESSION_PENALTY = 0.60 # Penalty multiplier for high ESR content
14
+ FOLLOW_THRESHOLD = 1000 # Max followers for Small-Creator Advantage
15
+
16
+ # --- Score Scaling and Clamping Constants ---
17
+ PS_FLAT_PROGRESS = 50.0 # PS score when a creator has 0% improvement
18
+ NETWORK_AVG_ESR = 0.10 # Network average Early Skip Rate (0-1)
19
+
20
+ class UpnisoAlgorithmPipeline:
21
+ """
22
+ Upniso Merit-Based Recommendation Algorithm Pipeline (V2.0).
23
+
24
+ Encapsulates the 6-stage architecture: Analyze, Score, Predict, Test,
25
+ Distribute, and Learn. All core scores are normalized to a 0-100 scale.
26
+ """
27
+
28
+ def __init__(self, avg_esr: float = NETWORK_AVG_ESR, avg_ts: float = 50.0, avg_wtpu: float = 0.50):
29
+ """Initializes the pipeline with network baseline averages for comparison."""
30
+ self.avg_esr = avg_esr
31
+ self.avg_ts = avg_ts
32
+ self.avg_wtpu = avg_wtpu
33
+ self.creators = {}
34
+ self.content_queue = {}
35
+
36
+ # --- Stage 1: Analyze (Data Ingestion and Cleanup) ---
37
+ def analyze(self, creator_id: str, creator_data: dict, content_id: str, content_data: dict):
38
+ """
39
+ Ingests, sanitizes, and verifies raw data from the database/API.
40
+ Adds fallbacks for missing historical data.
41
+ """
42
+ # Guard against division by zero and provide sensible fallbacks (Task B.3)
43
+ creator_data['ts_90_days_ago'] = max(creator_data.get('ts_90_days_ago', self.avg_ts), 1.0)
44
+ creator_data['avg_wtpu_month1'] = max(creator_data.get('avg_wtpu_month1', self.avg_wtpu), 0.01)
45
+ creator_data['ts_14_days_ago'] = creator_data.get('ts_14_days_ago', creator_data['ts_90_days_ago'])
46
+ creator_data['stdev_upload_days'] = creator_data.get('stdev_upload_days', 4.0)
47
+
48
+ content_data['wtpu_current'] = np.clip(content_data.get('wtpu_current', 0.0), 0.0, 1.0).item()
49
+ content_data['nvr'] = np.clip(content_data.get('nvr', 0.0), 0.0, 1.0).item()
50
+ # MIS is 1.0 for compliance (True), 0.5 otherwise (False/Missing)
51
+ content_data['mis_score'] = 1.0 if content_data.get('mis_compliance', False) else 0.5
52
+ content_data['esr_current'] = np.clip(content_data.get('esr_current', 0.5), 0.0, 1.0).item()
53
+ content_data['prediction_score_p_hva'] = content_data.get('prediction_score_p_hva', 0.5)
54
+
55
+ self.creators[creator_id] = creator_data
56
+ self.content_queue[content_id] = content_data
57
+ self.content_queue[content_id]['creator_id'] = creator_id
58
+
59
+ # --- Stage 2: Score (Creator Profile Merit Calculation) ---
60
+ def score(self, creator_id: str) -> Dict[str, float]:
61
+ """
62
+ Calculates the creator's persistent Talent Score (TS) and Progress Score (PS).
63
+ Both scores are normalized to 0-100.
64
+ """
65
+ creator_data = self.creators.get(creator_id)
66
+ if not creator_data: return {'TS': self.avg_ts, 'PS': PS_FLAT_PROGRESS}
67
+
68
+ # 1. Talent Score (TS) Calculation (0-100, Normalized)
69
+ wtpu_norm = creator_data['wtpu_last_10_avg']
70
+ hva_norm = creator_data.get('avg_hva_rate', 0.05)
71
+ # Consistency Factor: Closer to 1.0 is better (lower stdev)
72
+ consistency = 1.0 / (creator_data['stdev_upload_days'] + 1.0)
73
+
74
+ # TS = 100 * (0.5 * WTPU + 0.3 * HVA + 0.2 * Consistency)
75
+ ts_merit = 100 * (0.5 * wtpu_norm + 0.3 * hva_norm + 0.2 * consistency)
76
+ ts_today = np.clip(ts_merit, 0, 100).item()
77
+ self.creators[creator_id]['ts_today'] = ts_today
78
+
79
+ # 2. Progress Score (PS) Calculation (0-100, 50=Flat)
80
+ ts_90_days_ago = creator_data['ts_90_days_ago']
81
+ wtpu_m1 = creator_data['avg_wtpu_month1']
82
+ wtpu_m3 = creator_data.get('avg_wtpu_month3', wtpu_m1)
83
+
84
+ # PS_Raw = 50 + [Delta TS * 50] + [Delta WTPU * 50]
85
+ ts_delta_contribution = ((ts_today / ts_90_days_ago) - 1.0) * 50
86
+ wtpu_delta_contribution = ((wtpu_m3 / wtpu_m1) - 1.0) * 50
87
+
88
+ ps_raw = PS_FLAT_PROGRESS + ts_delta_contribution + wtpu_delta_contribution
89
+
90
+ # Forgiveness Boost Check (Task A.10: If TS improved >= 20% in 14 days)
91
+ ts_14_days_ago = creator_data['ts_14_days_ago']
92
+ ts_14_day_improvement = (ts_today - ts_14_days_ago) / ts_14_days_ago
93
+
94
+ forgiveness_boost = 0
95
+ if ts_14_day_improvement >= 0.20:
96
+ forgiveness_boost = 15 # +15 score for demonstrated momentum
97
+
98
+ # Clamping PS score (0-100)
99
+ ps_score = np.clip(ps_raw + forgiveness_boost, 0, 100).item()
100
+
101
+ return {'TS': ts_today, 'PS': ps_score}
102
+
103
+ # --- Stage 3: Predict (Affinity Model Simulation) ---
104
+ def predict(self, content_id: str) -> float:
105
+ """
106
+ Simulates the Prediction Score (P(HVA)) based on user affinity.
107
+ Returns score (0-100).
108
+ """
109
+ content_data = self.content_queue.get(content_id)
110
+ if not content_data: return PS_FLAT_PROGRESS
111
+
112
+ prediction_raw = content_data['prediction_score_p_hva'] * 100
113
+ return np.clip(prediction_raw, 0, 100).item()
114
+
115
+
116
+ # --- Stage 4: Test (Fair Exposure Engine - FEE) ---
117
+ def test(self, content_id: str, merit_scores: Dict[str, float]) -> Tuple[float, float]:
118
+ """
119
+ Calculates the Quality Score (QS) and the dynamic boost/penalty flag
120
+ based on initial T0/T1 micro-test results.
121
+ Returns QS (0-100) and Dynamic Factor (0 to 5%).
122
+ """
123
+ content_data = self.content_queue.get(content_id)
124
+ creator_data = self.creators.get(content_data['creator_id'])
125
+ if not content_data or not creator_data: return PS_FLAT_PROGRESS, 0.0
126
+
127
+ # 1. Quality Score (QS) Calculation (0-100, Normalized)
128
+ wtpu = content_data['wtpu_current']
129
+ nvr = content_data['nvr']
130
+ mis = content_data['mis_score']
131
+
132
+ # QS = 100 * (0.6 * WTPU + 0.3 * NVR + 0.1 * MIS)
133
+ qs_score = 100 * (0.6 * wtpu + 0.3 * nvr + 0.1 * mis)
134
+ qs_score = np.clip(qs_score, 0, 100).item()
135
+
136
+ # 2. Dynamic Boost Calculation (5% Max)
137
+ dynamic_factor = 0.0
138
+
139
+ # Small-Creator Advantage (1.5x Multiplier to Merit in Test Phase)
140
+ if creator_data['follower_count'] < FOLLOW_THRESHOLD:
141
+ # Merit component calculation (normalized to 0-1)
142
+ merit_value_norm = (W2_TALENT * merit_scores['TS'] + W3_PROGRESS * merit_scores['PS'] + W4_QUALITY * qs_score) / 100
143
+
144
+ # 1.5x multiplier means adding 50% of the calculated merit value (45% weight)
145
+ boost_value = merit_value_norm * 0.5
146
+
147
+ # Clamp the boost to the allocated 5% maximum dynamic weight
148
+ dynamic_factor += np.clip(boost_value, 0, MAX_DYNAMIC_BOOST).item()
149
+
150
+ # 3. Negative Signal Suppression Flag (ESR >= 5x Avg)
151
+ esr_ratio = content_data['esr_current'] / self.avg_esr if self.avg_esr > 0 else 1.0
152
+ penalty_flag = False
153
+ if esr_ratio >= 5.0:
154
+ penalty_flag = True
155
+
156
+ self.content_queue[content_id]['penalty_flag'] = penalty_flag
157
+ self.content_queue[content_id]['QS'] = qs_score
158
+
159
+ return qs_score, dynamic_factor
160
+
161
+ # --- Stage 5: Distribute (Final Content Score Ranking) ---
162
+ def distribute(self, content_id: str, merit_scores: Dict[str, float], pred_score: float, qs_score: float, dynamic_factor: float) -> float:
163
+ """
164
+ Calculates the Final Content Score (CS, 0-1.0) used for feed ranking.
165
+ """
166
+
167
+ # Convert 0-100 scores to 0-1 for weighting
168
+ ts_norm = merit_scores['TS'] / 100
169
+ ps_norm = merit_scores['PS'] / 100
170
+ qs_norm = qs_score / 100
171
+ pred_norm = pred_score / 100
172
+
173
+ # Baseline CS (95% weight) = (50% Pred) + (45% Merit)
174
+ cs_baseline = (W1_PREDICTION * pred_norm) + \
175
+ (W2_TALENT * ts_norm) + \
176
+ (W3_PROGRESS * ps_norm) + \
177
+ (W4_QUALITY * qs_norm)
178
+
179
+ # Final Content Score (Max 1.0)
180
+ final_cs = cs_baseline + dynamic_factor
181
+
182
+ # Apply Suppression Penalty if flagged
183
+ if self.content_queue.get(content_id, {}).get('penalty_flag', False):
184
+ final_cs *= SUPPRESSION_PENALTY
185
+
186
+ return np.clip(final_cs, 0, 1).item()
187
+
188
+ # --- Stage 6: Learn (Feedback Loop Simulation) ---
189
+ def learn(self, creator_id: str, content_id: str, final_cs: float):
190
+ """
191
+ Simulates updating the creator's profile data based on content performance (Offline Job).
192
+ """
193
+ # In a real system, this would run daily to update ts_90_days_ago, ts_14_days_ago, etc.
194
+ # For simulation, we just record results.
195
+ self.content_queue[content_id]['final_cs'] = final_cs
196
+ self.content_queue[content_id]['PS'] = self.content_queue[content_id].get('PS', 0)
197
+ self.content_queue[content_id]['TS'] = self.content_queue[content_id].get('TS', 0)
198
+
199
+
200
+ # --- Simulation Mode (Task B.6 / D) ---
201
+ def run_simulation(self, creators_data: Dict[str, Dict], contents_data: Dict[str, Dict]):
202
+ """Runs a competition simulation for all provided content and returns the ranked list."""
203
+ results = []
204
+
205
+ print("="*80)
206
+ print("UPNISO ALGORITHM SIMULATION (V2.0)")
207
+ print("="*80)
208
+
209
+ for content_id, content_input in contents_data.items():
210
+ creator_id = content_input['creator_id']
211
+ creator_input = creators_data[creator_id]
212
+
213
+ # --- Pipeline Execution ---
214
+ self.analyze(creator_id, creator_input, content_id, content_input)
215
+
216
+ merit = self.score(creator_id)
217
+ pred = self.predict(content_id)
218
+ qs, dynamic_boost = self.test(content_id, merit)
219
+
220
+ # Store scores for reporting
221
+ self.content_queue[content_id]['QS'] = qs
222
+ self.content_queue[content_id]['PS'] = merit['PS']
223
+ self.content_queue[content_id]['TS'] = merit['TS']
224
+ self.content_queue[content_id]['Pred'] = pred
225
+ self.content_queue[content_id]['dynamic_boost'] = dynamic_boost
226
+
227
+ final_cs = self.distribute(content_id, merit, pred, qs, dynamic_boost)
228
+ self.learn(creator_id, content_id, final_cs)
229
+
230
+ # --- Reporting ---
231
+ penalty = "APPLIED" if self.content_queue[content_id].get('penalty_flag') else "None"
232
+
233
+ results.append({
234
+ 'content_id': content_id,
235
+ 'creator_id': creator_id,
236
+ 'CS': final_cs,
237
+ 'TS': merit['TS'],
238
+ 'PS': merit['PS'],
239
+ 'QS': qs,
240
+ 'Pred': pred,
241
+ 'Dynamic_Boost': dynamic_boost,
242
+ 'Penalty': penalty
243
+ })
244
+
245
+ # Final Ranking
246
+ results.sort(key=lambda x: x['CS'], reverse=True)
247
+
248
+ print("\n" + "="*80)
249
+ print("FINAL RANKED ORDER (Rank #1 is highest CS)")
250
+ print("="*80)
251
+
252
+ for i, r in enumerate(results):
253
+ print(f"RANK {i+1:2}: CS={r['CS']:.4f} | TS={r['TS']:.1f} | PS={r['PS']:.1f} | QS={r['QS']:.1f} | Pred={r['Pred']:.1f} | ID={r['content_id']} ({r['creator_id']})")
254
+
255
+ return results
256
+
257
+ # --- Simulation Data and Execution (Task D) ---
258
+
259
+ # 1. Creator Data (Pre-calculated and Denormalized inputs)
260
+ CREATORS_DATA = {
261
+ 'C1': {'follower_count': 500, 'ts_90_days_ago': 20.0, 'ts_14_days_ago': 28.0, 'wtpu_last_10_avg': 0.85, 'avg_hva_rate': 0.15, 'stdev_upload_days': 0.5, 'avg_wtpu_month1': 0.2, 'avg_wtpu_month3': 0.4}, # Small/High-Skill: TS up 40% in 14 days
262
+ 'C2': {'follower_count': 15000, 'ts_90_days_ago': 70.0, 'ts_14_days_ago': 70.0, 'wtpu_last_10_avg': 0.65, 'avg_hva_rate': 0.05, 'stdev_upload_days': 4.0, 'avg_wtpu_month1': 0.6, 'avg_wtpu_month3': 0.6}, # Luck-Focused: Stagnant, inconsistent
263
+ 'C3': {'follower_count': 5000, 'ts_90_days_ago': 50.0, 'ts_14_days_ago': 45.0, 'wtpu_last_10_avg': 0.50, 'avg_hva_rate': 0.08, 'stdev_upload_days': 2.5, 'avg_wtpu_month1': 0.5, 'avg_wtpu_month3': 0.4}, # Mid-Stagnant: Regressing
264
+ 'C4': {'follower_count': 100000, 'ts_90_days_ago': 85.0, 'ts_14_days_ago': 88.0, 'wtpu_last_10_avg': 0.80, 'avg_hva_rate': 0.20, 'stdev_upload_days': 1.0, 'avg_wtpu_month1': 0.7, 'avg_wtpu_month3': 0.77}, # Large/Consistent: Elite skill, steady improvement
265
+ 'C5': {'follower_count': 50, 'ts_90_days_ago': 10.0, 'ts_14_days_ago': 11.0, 'wtpu_last_10_avg': 0.70, 'avg_hva_rate': 0.10, 'stdev_upload_days': 3.0, 'avg_wtpu_month1': 0.1, 'avg_wtpu_month3': 0.3}, # Micro/Improving: Huge WTPU progress, TS up 25% in 14 days
266
+ }
267
+
268
+ # 2. Content Data (Micro-Test results and Model Predictions)
269
+ CONTENTS_DATA = {
270
+ 'CN1': {'creator_id': 'C1', 'wtpu_current': 0.95, 'nvr': 0.20, 'mis_compliance': True, 'esr_current': 0.08, 'prediction_score_p_hva': 0.30}, # Niche, High Quality
271
+ 'CN2': {'creator_id': 'C2', 'wtpu_current': 0.15, 'nvr': 0.05, 'mis_compliance': False, 'esr_current': 0.55, 'prediction_score_p_hva': 0.90}, # Clickbait, Low Quality (ESR 5.5x Avg)
272
+ 'CN3': {'creator_id': 'C3', 'wtpu_current': 0.50, 'nvr': 0.10, 'mis_compliance': True, 'esr_current': 0.10, 'prediction_score_p_hva': 0.50}, # Average
273
+ 'CN4': {'creator_id': 'C4', 'wtpu_current': 0.80, 'nvr': 0.30, 'mis_compliance': True, 'esr_current': 0.12, 'prediction_score_p_hva': 0.85}, # Elite, High Affinity
274
+ 'CN5': {'creator_id': 'C5', 'wtpu_current': 0.75, 'nvr': 0.15, 'mis_compliance': True, 'esr_current': 0.05, 'prediction_score_p_hva': 0.20}, # High Progress, Unknown Affinity
275
+ }
276
+
277
+ if __name__ == '__main__':
278
+ pipeline = UpnisoAlgorithmPipeline()
279
+ pipeline.run_simulation(CREATORS_DATA, CONTENTS_DATA)
280
+ def run_demo():
281
+ return "Algorithm connected successfully"