""" Rankora ML Engine 4 ML Features: 1. Fake Review Detector 2. Price Prediction (Linear Regression) 3. Demand Forecasting (Seasonality) 4. Niche Scorer """ import math import random from typing import Optional from datetime import datetime, timezone # ═══════════════════════════════════════════════════════════════ # 1. FAKE REVIEW DETECTOR # ═══════════════════════════════════════════════════════════════ def detect_fake_reviews( rating: float, review_count: int, rating_distribution: Optional[dict] = None, monthly_sales_estimate: int = 100, asin: str = "" ) -> dict: """ Detect suspicious review patterns using rule-based ML heuristics. Returns a suspicion score 0-100 and detailed signals. """ signals = [] suspicion_score = 0 # Signal 1: Perfect or near-perfect rating with many reviews if rating >= 4.8 and review_count > 500: suspicion_score += 20 signals.append({ "signal": "Suspiciously High Rating", "detail": f"{rating}★ with {review_count:,} reviews — real products rarely maintain 4.8+ at scale", "severity": "high", "weight": 20 }) elif rating >= 4.9 and review_count > 100: suspicion_score += 25 signals.append({ "signal": "Near-Perfect Rating", "detail": f"{rating}★ is unusually high — may indicate review manipulation", "severity": "high", "weight": 25 }) # Signal 2: Review velocity vs sales ratio # If reviews >> expected for sales level, reviews may be incentivized expected_reviews = monthly_sales_estimate * 0.02 # ~2% of sales leave reviews if review_count > 0 and monthly_sales_estimate > 0: review_rate = review_count / max(monthly_sales_estimate * 6, 1) # 6 months if review_rate > 0.15: # more than 15% of buyers reviewing is suspicious suspicion_score += 20 signals.append({ "signal": "High Review Velocity", "detail": f"Review rate ({review_rate:.1%}) is unusually high vs estimated sales — possible incentivized reviews", "severity": "medium", "weight": 20 }) # Signal 3: Very low review count but high BSR # High sales rank but very few reviews = possibly review reset / new ASIN if monthly_sales_estimate > 500 and review_count < 20: suspicion_score += 15 signals.append({ "signal": "Sales/Review Mismatch", "detail": f"High estimated sales ({monthly_sales_estimate:,}/mo) but only {review_count} reviews — possible review manipulation reset", "severity": "medium", "weight": 15 }) # Signal 4: Rating distribution analysis (if provided) if rating_distribution: five_star_pct = rating_distribution.get("5_star", 0) one_star_pct = rating_distribution.get("1_star", 0) if five_star_pct > 85: suspicion_score += 20 signals.append({ "signal": "Extreme 5-Star Concentration", "detail": f"{five_star_pct}% five-star reviews — legitimate products rarely exceed 80%", "severity": "high", "weight": 20 }) if one_star_pct < 1 and review_count > 200: suspicion_score += 10 signals.append({ "signal": "Missing Negative Reviews", "detail": f"Only {one_star_pct}% 1-star reviews out of {review_count:,} — statistically unlikely for real products", "severity": "medium", "weight": 10 }) # Signal 5: Round number review counts often indicate manipulation if review_count > 100: str_count = str(review_count) trailing_zeros = len(str_count) - len(str_count.rstrip("0")) if trailing_zeros >= 2: suspicion_score += 10 signals.append({ "signal": "Suspicious Review Count", "detail": f"Round number ({review_count:,}) with trailing zeros — may indicate manipulated count", "severity": "low", "weight": 10 }) suspicion_score = min(suspicion_score, 100) if suspicion_score >= 60: verdict = "HIGH RISK — Likely Fake Reviews" verdict_color = "#EF4444" recommendation = "Avoid this product — high probability of review manipulation" elif suspicion_score >= 35: verdict = "MODERATE RISK — Suspicious Patterns" verdict_color = "#F59E0B" recommendation = "Investigate further before competing in this space" elif suspicion_score >= 15: verdict = "LOW RISK — Minor Concerns" verdict_color = "#FB923C" recommendation = "Reviews appear mostly genuine with minor anomalies" else: verdict = "AUTHENTIC — Reviews Appear Genuine" verdict_color = "#10B981" recommendation = "No significant red flags detected in review patterns" return { "suspicion_score": suspicion_score, "verdict": verdict, "verdict_color": verdict_color, "recommendation": recommendation, "signals": signals, "signals_count": len(signals), "is_suspicious": suspicion_score >= 35, "analysis_basis": { "rating": rating, "review_count": review_count, "monthly_sales_estimate": monthly_sales_estimate, } } # ═══════════════════════════════════════════════════════════════ # 2. PRICE PREDICTION (Linear Regression) # ═══════════════════════════════════════════════════════════════ def predict_price( price_history: list, days_ahead: int = 7 ) -> dict: """ Predict future price using linear regression on price history. price_history: list of {"price": float, "recorded_at": str} """ if not price_history or len(price_history) < 3: return { "predicted_price": None, "confidence": "low", "trend": "insufficient_data", "message": "Need at least 3 data points for prediction" } # Extract prices prices = [float(p["price"]) for p in price_history if p.get("price")] if len(prices) < 3: return {"predicted_price": None, "confidence": "low", "trend": "insufficient_data"} n = len(prices) x = list(range(n)) # Linear regression: y = mx + b sum_x = sum(x) sum_y = sum(prices) sum_xy = sum(x[i] * prices[i] for i in range(n)) sum_x2 = sum(xi ** 2 for xi in x) denom = n * sum_x2 - sum_x ** 2 if denom == 0: slope = 0 else: slope = (n * sum_xy - sum_x * sum_y) / denom intercept = (sum_y - slope * sum_x) / n # Predict future price future_x = n - 1 + days_ahead predicted = round(intercept + slope * future_x, 2) predicted = max(0.01, predicted) # price can't be negative # Calculate R² for confidence y_mean = sum_y / n ss_tot = sum((p - y_mean) ** 2 for p in prices) ss_res = sum((prices[i] - (intercept + slope * x[i])) ** 2 for i in range(n)) r2 = 1 - (ss_res / ss_tot) if ss_tot > 0 else 0 # Determine trend current_price = prices[-1] price_change_pct = ((predicted - current_price) / current_price) * 100 if slope > 0.01: trend = "rising" trend_emoji = "📈" trend_color = "#EF4444" elif slope < -0.01: trend = "falling" trend_emoji = "📉" trend_color = "#10B981" else: trend = "stable" trend_emoji = "➡️" trend_color = "#6B7280" confidence = "high" if r2 > 0.7 else "medium" if r2 > 0.4 else "low" # Price stats min_price = min(prices) max_price = max(prices) avg_price = sum(prices) / len(prices) return { "predicted_price": predicted, "current_price": current_price, "price_change": round(predicted - current_price, 2), "price_change_pct": round(price_change_pct, 1), "trend": trend, "trend_emoji": trend_emoji, "trend_color": trend_color, "confidence": confidence, "r_squared": round(r2, 3), "days_ahead": days_ahead, "slope_per_day": round(slope, 4), "price_stats": { "min": round(min_price, 2), "max": round(max_price, 2), "avg": round(avg_price, 2), "volatility": round((max_price - min_price) / avg_price * 100, 1) }, "recommendation": ( f"Price expected to {'rise' if trend == 'rising' else 'fall' if trend == 'falling' else 'stay stable'} " f"by {abs(price_change_pct):.1f}% over next {days_ahead} days" ) } # ═══════════════════════════════════════════════════════════════ # 3. DEMAND FORECASTING (Seasonality Detection) # ═══════════════════════════════════════════════════════════════ def forecast_demand( bsr_history: list, category: str = "general", current_month: Optional[int] = None ) -> dict: """ Forecast demand using BSR trends and seasonal patterns. Lower BSR = higher demand. """ if current_month is None: current_month = datetime.now(timezone.utc).month # Category seasonal multipliers (month 1-12) seasonal_patterns = { "electronics": [0.8, 0.7, 0.8, 0.9, 0.9, 0.8, 0.9, 0.9, 1.0, 1.0, 1.3, 1.4], "home": [0.9, 0.8, 1.0, 1.1, 1.2, 1.1, 1.0, 0.9, 0.9, 1.0, 1.1, 1.0], "toys": [0.7, 0.6, 0.7, 0.7, 0.8, 0.8, 0.9, 0.9, 1.0, 1.1, 1.3, 1.8], "sports": [0.9, 0.9, 1.1, 1.2, 1.3, 1.3, 1.2, 1.1, 1.0, 0.9, 0.8, 0.8], "kitchen": [0.9, 0.9, 1.0, 1.0, 1.0, 0.9, 0.9, 0.9, 0.9, 1.0, 1.1, 1.2], "general": [1.0, 0.9, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.1, 1.2], } # Match category cat_key = "general" category_lower = category.lower() for key in seasonal_patterns: if key in category_lower: cat_key = key break pattern = seasonal_patterns[cat_key] current_multiplier = pattern[current_month - 1] next_month = (current_month % 12) next_multiplier = pattern[next_month] # BSR trend analysis bsr_trend = "stable" bsr_change_pct = 0 demand_trend = "stable" if bsr_history and len(bsr_history) >= 3: bsr_values = [h.get("bsr") for h in bsr_history if h.get("bsr")] if len(bsr_values) >= 3: old_bsr = sum(bsr_values[:3]) / 3 new_bsr = sum(bsr_values[-3:]) / 3 bsr_change_pct = ((new_bsr - old_bsr) / old_bsr) * 100 if bsr_change_pct < -10: bsr_trend = "improving" # BSR going down = demand going up demand_trend = "increasing" elif bsr_change_pct > 10: bsr_trend = "declining" demand_trend = "decreasing" else: bsr_trend = "stable" demand_trend = "stable" # Seasonal score for each month months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] monthly_forecast = [] for i, mult in enumerate(pattern): monthly_forecast.append({ "month": months[i], "demand_index": round(mult * 100), "is_peak": mult >= 1.2, "is_low": mult <= 0.8, }) peak_months = [months[i] for i, m in enumerate(pattern) if m >= 1.2] low_months = [months[i] for i, m in enumerate(pattern) if m <= 0.8] if current_multiplier >= 1.2: season_status = "PEAK SEASON" season_color = "#10B981" season_advice = "Great time to sell — high demand period" elif current_multiplier >= 1.0: season_status = "NORMAL SEASON" season_color = "#3B82F6" season_advice = "Average demand — maintain stock levels" elif current_multiplier >= 0.85: season_status = "SLOW SEASON" season_color = "#F59E0B" season_advice = "Reduce inventory — lower demand period" else: season_status = "OFF SEASON" season_color = "#EF4444" season_advice = "Avoid heavy stock — very low demand" demand_change_next = round((next_multiplier - current_multiplier) / current_multiplier * 100, 1) return { "current_season": season_status, "season_color": season_color, "season_advice": season_advice, "current_demand_index": round(current_multiplier * 100), "next_month_demand_index": round(next_multiplier * 100), "demand_change_next_month": demand_change_next, "demand_trend": demand_trend, "bsr_trend": bsr_trend, "bsr_change_pct": round(bsr_change_pct, 1), "category": cat_key, "peak_months": peak_months, "low_months": low_months, "monthly_forecast": monthly_forecast, "recommendation": ( f"{season_status}: {season_advice}. " f"Next month demand expected to {'increase' if demand_change_next > 5 else 'decrease' if demand_change_next < -5 else 'stay similar'} " f"by {abs(demand_change_next):.0f}%." ) } # ═══════════════════════════════════════════════════════════════ # 4. NICHE SCORER # ═══════════════════════════════════════════════════════════════ def score_niche( keyword: str, products: list, # list of {bsr, price, reviews, rating} category: str = "general" ) -> dict: """ Score an entire niche/keyword based on multiple product data points. Returns opportunity score 0-100 and detailed breakdown. """ if not products: return {"error": "No products provided for niche analysis"} prices = [p.get("price", 0) for p in products if p.get("price")] bsrs = [p.get("bsr", 0) for p in products if p.get("bsr")] reviews = [p.get("reviews", 0) for p in products if p.get("reviews") is not None] ratings = [p.get("rating", 0) for p in products if p.get("rating")] def avg(lst): return sum(lst) / len(lst) if lst else 0 def median(lst): s = sorted(lst) n = len(s) return (s[n//2] + s[n//2-1]) / 2 if n % 2 == 0 else s[n//2] avg_price = avg(prices) avg_bsr = avg(bsrs) avg_reviews = avg(reviews) avg_rating = avg(ratings) med_reviews = median(reviews) if reviews else 0 scores = {} # 1. Demand Score (based on BSR) if avg_bsr < 1000: scores["demand"] = 95 elif avg_bsr < 5000: scores["demand"] = 80 elif avg_bsr < 20000: scores["demand"] = 65 elif avg_bsr < 100000: scores["demand"] = 45 else: scores["demand"] = 20 # 2. Competition Score (lower reviews = easier to compete) if avg_reviews < 50: scores["competition"] = 90 elif avg_reviews < 200: scores["competition"] = 75 elif avg_reviews < 500: scores["competition"] = 55 elif avg_reviews < 2000: scores["competition"] = 35 else: scores["competition"] = 15 # 3. Profitability Score (based on price) if avg_price >= 25 and avg_price <= 70: scores["profitability"] = 85 elif avg_price >= 15 and avg_price < 25: scores["profitability"] = 65 elif avg_price >= 70 and avg_price <= 150: scores["profitability"] = 70 elif avg_price > 150: scores["profitability"] = 50 else: scores["profitability"] = 35 # 4. Quality Gap Score (lower ratings = easier to beat with better product) if avg_rating < 3.8: scores["quality_gap"] = 90 elif avg_rating < 4.2: scores["quality_gap"] = 70 elif avg_rating < 4.5: scores["quality_gap"] = 50 else: scores["quality_gap"] = 25 # 5. Market Size Score n = len(products) if n >= 10: scores["market_size"] = 80 elif n >= 5: scores["market_size"] = 60 else: scores["market_size"] = 40 # Weighted total total = ( scores["demand"] * 0.30 + scores["competition"] * 0.30 + scores["profitability"]* 0.20 + scores["quality_gap"] * 0.15 + scores["market_size"] * 0.05 ) total = round(total) if total >= 70: verdict = "Excellent Niche" verdict_color = "#10B981" recommendation = "Strong opportunity — low competition, good demand, profitable price range" elif total >= 55: verdict = "Good Niche" verdict_color = "#3B82F6" recommendation = "Decent opportunity — worth pursuing with right differentiation" elif total >= 40: verdict = "Moderate Niche" verdict_color = "#F59E0B" recommendation = "Average opportunity — possible but competitive, needs strong USP" else: verdict = "Tough Niche" verdict_color = "#EF4444" recommendation = "Difficult market — high competition or low margins" # Review gaps — products with < 100 reviews in a market with demand low_review_products = [p for p in products if (p.get("reviews") or 0) < 100 and (p.get("bsr") or 999999) < 50000] return { "keyword": keyword, "niche_score": total, "verdict": verdict, "verdict_color": verdict_color, "recommendation": recommendation, "scores": { "demand": {"score": scores["demand"], "label": "Market Demand", "weight": "30%"}, "competition": {"score": scores["competition"], "label": "Competition Level", "weight": "30%"}, "profitability": {"score": scores["profitability"], "label": "Profitability", "weight": "20%"}, "quality_gap": {"score": scores["quality_gap"], "label": "Quality Gap", "weight": "15%"}, "market_size": {"score": scores["market_size"], "label": "Market Size", "weight": "5%"}, }, "market_stats": { "products_analyzed": len(products), "avg_price": round(avg_price, 2), "avg_bsr": round(avg_bsr), "avg_reviews": round(avg_reviews), "avg_rating": round(avg_rating, 1), "median_reviews": round(med_reviews), }, "opportunities": { "low_review_opportunities": len(low_review_products), "easy_entry_products": [p.get("asin", "") for p in low_review_products[:3]], "price_gap": round(max(prices) - min(prices), 2) if prices else 0, } }