Spaces:
Running
Running
| """ | |
| 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, | |
| } | |
| } | |