""" 9 Availability Algorithms for Maximum Traffic Each algo controls when/how the profile goes available/offline to maximize visibility, search rank, and traffic attraction. Algos: 1. JitterBurst — brief offline pulses → "recently available" signal 2. PeakHourSync — match availability to historical peak traffic hours 3. CompetitorGap — go available when competitors drop off 4. RefreshCascade — staggered refresh before expiry, never gap 5. SearchRankBoost — toggle to appear in "available now" filters more 6. DemandPulse — short availability windows during low-traffic to capture pent-up demand 7. GeoRotation — rotate timing to catch multiple timezone traffic waves 8. EngagementTrigger— refresh immediately after receiving a visit/message 9. BackoffRecovery — exponential backoff on API failure, never lose presence """ import time import random import logging import sqlite3 from typing import Dict, Any, Optional, List from dataclasses import dataclass, field log = logging.getLogger("rm.avail_algos") @dataclass class AlgoState: """Shared state across all algos.""" available: bool = False last_refresh: float = 0 last_visit: float = 0 last_message: float = 0 competitor_count: int = 0 competitor_available: int = 0 profile_views_1h: int = 0 profile_views_24h: int = 0 current_hour: int = 0 day_of_week: int = 0 api_failures: int = 0 last_api_error: str = "" availability_option: int = 0 availability_expires: float = 0 bursts_executed: int = 0 refreshes_executed: int = 0 toggles_executed: int = 0 algo_history: List[Dict] = field(default_factory=list) # ── Traffic Attribution ── attribution: Dict[str, Dict] = field(default_factory=lambda: {}) baseline_views: int = 0 last_attribution_ts: float = 0 view_samples: List[Dict] = field(default_factory=list) algo_fire_log: List[Dict] = field(default_factory=list) baseline_rate: float = 0.0 # views per minute when no algo recently fired def _record(state: AlgoState, algo: str, action: str, detail: str = ""): entry = {"ts": time.time(), "algo": algo, "action": action, "detail": detail} state.algo_history.append(entry) if len(state.algo_history) > 200: state.algo_history = state.algo_history[-200:] log.info(f"[{algo}] {action} — {detail}") def _capture_attribution(state: AlgoState, algo_name: str, action: str): """Record that an algo fired — attribution computed later in get_attribution_summary.""" now = time.time() state.algo_fire_log.append({ "ts": now, "algo": algo_name, "action": action, "views_at_fire": state.profile_views_24h, }) if len(state.algo_fire_log) > 500: state.algo_fire_log = state.algo_fire_log[-500:] def sample_views(state: AlgoState): """Call this every cycle to sample current view count. Builds a continuous timeline for baseline + lift computation. """ now = time.time() state.view_samples.append({ "ts": now, "views_24h": state.profile_views_24h, "available": state.available, }) if len(state.view_samples) > 1000: state.view_samples = state.view_samples[-1000:] def _compute_baseline_rate(state: AlgoState) -> float: """Compute baseline view rate (views/min) during periods with no algo fire in the prior 30 min.""" if len(state.view_samples) < 2: return 0.0 fire_ts_set = [f["ts"] for f in state.algo_fire_log] quiet_samples = [] for s in state.view_samples: recent_fire = any(abs(s["ts"] - ft) < 1800 for ft in fire_ts_set) if not recent_fire: quiet_samples.append(s) if len(quiet_samples) < 2: quiet_samples = state.view_samples deltas = [] for i in range(1, len(quiet_samples)): dt = quiet_samples[i]["ts"] - quiet_samples[i-1]["ts"] dv = quiet_samples[i]["views_24h"] - quiet_samples[i-1]["views_24h"] if dt > 0: deltas.append(dv / dt * 60) # views per minute if not deltas: return 0.0 deltas.sort() mid = len(deltas) // 2 return deltas[mid] # median rate def get_attribution_summary(state: AlgoState) -> Dict: """Compute per-algo traffic attribution using lift over baseline. For each algo fire, we look at views gained in the 30-minute window after the fire, compare to expected baseline views in that same window. Lift = actual - expected. Attribution = lift credited to that algo. """ baseline_rate = _compute_baseline_rate(state) state.baseline_rate = baseline_rate window = 1800 # 30 min attribution window now = time.time() per_algo = {} for fire in state.algo_fire_log: algo = fire["algo"] fire_ts = fire["ts"] views_at_fire = fire["views_at_fire"] # Find view sample closest to fire_ts + window end_ts = fire_ts + window closest = None for s in state.view_samples: if abs(s["ts"] - end_ts) < 300: # within 5 min of window end if closest is None or abs(s["ts"] - end_ts) < abs(closest["ts"] - end_ts): closest = s if closest is None: continue actual_views = closest["views_24h"] - views_at_fire if actual_views < 0: actual_views = 0 expected_views = baseline_rate * (window / 60) lift = actual_views - expected_views if algo not in per_algo: per_algo[algo] = { "fires": 0, "total_actual_views": 0, "total_expected_views": 0, "total_lift": 0, "last_action": "", "last_ts": 0, } a = per_algo[algo] a["fires"] += 1 a["total_actual_views"] += actual_views a["total_expected_views"] += expected_views a["total_lift"] += lift a["last_action"] = fire["action"] a["last_ts"] = fire_ts summary = [] for name, a in per_algo.items(): avg_lift = a["total_lift"] / max(a["fires"], 1) summary.append({ "algo": name, "fires": a["fires"], "total_actual_views": a["total_actual_views"], "total_expected_views": round(a["total_expected_views"], 1), "total_lift": round(a["total_lift"], 1), "avg_lift_per_fire": round(avg_lift, 2), "last_action": a["last_action"], "last_ts": a["last_ts"], }) summary.sort(key=lambda x: x["total_lift"], reverse=True) total_lift = sum(s["total_lift"] for s in summary) for s in summary: s["share_pct"] = round(s["total_lift"] / max(total_lift, 1) * 100, 1) if total_lift > 0 else 0 s["verdict"] = ( "POSITIVE" if s["total_lift"] > 0 and s["fires"] >= 2 else "NEGATIVE" if s["total_lift"] < 0 and s["fires"] >= 2 else "INSUFFICIENT_DATA" ) return { "total_lift": round(total_lift, 1), "baseline_rate_views_per_min": round(baseline_rate, 3), "algos": summary, "current_views_24h": state.profile_views_24h, "samples_collected": len(state.view_samples), "algo_fires_logged": len(state.algo_fire_log), "attribution_window_min": window // 60, } # ═══════════════════════════════════════════════════════════════════ # Algo 1: JitterBurst — brief offline pulse then back online # ═══════════════════════════════════════════════════════════════════ class JitterBurst: """2-5s offline pulse every 30 min to trigger 'recently available' signal.""" name = "jitter_burst" interval = 1800 # 30 min min_offline = 2.0 max_offline = 5.0 def __init__(self): self.last_burst = 0 def should_fire(self, state: AlgoState) -> bool: if not state.available: return False return (time.time() - self.last_burst) > self.interval def execute(self, api, state: AlgoState) -> Dict: duration = random.uniform(self.min_offline, self.max_offline) ts = time.time() try: api.set_availability(option=2, duration=0) state.available = False time.sleep(duration) api.set_availability(option=1, duration=5) state.available = True state.bursts_executed += 1 self.last_burst = ts _record(state, self.name, "burst", f"offline {duration:.1f}s") return {"algo": self.name, "action": "burst", "duration": round(duration, 1), "verified": True} except Exception as e: try: api.set_availability(option=1, duration=5) state.available = True except Exception: pass _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 2: PeakHourSync — availability matched to traffic peaks # ═══════════════════════════════════════════════════════════════════ class PeakHourSync: """Ensure availability during historically high-traffic hours.""" name = "peak_hour_sync" # Peak hours: 8-11am, 1-3pm, 7-11pm (local time, assumed ET) PEAK_HOURS = {8, 9, 10, 11, 13, 14, 15, 19, 20, 21, 22, 23} SHOULDER_HOURS = {7, 12, 16, 17, 18, 0, 1} def should_fire(self, state: AlgoState) -> bool: hour = state.current_hour if hour in self.PEAK_HOURS and not state.available: return True if hour in self.SHOULDER_HOURS and not state.available: return random.random() < 0.5 return False def execute(self, api, state: AlgoState) -> Dict: hour = state.current_hour is_peak = hour in self.PEAK_HOURS duration = 6 if is_peak else 3 try: api.set_availability(option=1, duration=duration) state.available = True state.refreshes_executed += 1 _record(state, self.name, "sync", f"hour={hour} peak={is_peak} dur={duration}h") return {"algo": self.name, "action": "sync", "hour": hour, "peak": is_peak, "duration": duration} except Exception as e: _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 3: CompetitorGap — go available when competitors drop # ═══════════════════════════════════════════════════════════════════ class CompetitorGap: """Monitor competitor availability — go available when they drop.""" name = "competitor_gap" threshold_ratio = 0.3 # if <30% of competitors available, fire def should_fire(self, state: AlgoState) -> bool: if state.competitor_count == 0: return False ratio = state.competitor_available / state.competitor_count return ratio < self.threshold_ratio and not state.available def execute(self, api, state: AlgoState) -> Dict: ratio = state.competitor_available / max(state.competitor_count, 1) try: api.set_availability(option=1, duration=5) state.available = True state.refreshes_executed += 1 _record(state, self.name, "gap_fill", f"competitor_ratio={ratio:.2f}") return {"algo": self.name, "action": "gap_fill", "competitor_ratio": round(ratio, 2)} except Exception as e: _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 4: RefreshCascade — staggered refresh before expiry # ═══════════════════════════════════════════════════════════════════ class RefreshCascade: """Refresh availability in a cascade: 1h before, 30min before, 5min before.""" name = "refresh_cascade" CASCADE_POINTS = [3600, 1800, 300] # 1h, 30min, 5min before expiry def should_fire(self, state: AlgoState) -> bool: if state.availability_expires <= 0: return False remaining = state.availability_expires - time.time() return remaining < self.CASCADE_POINTS[0] and remaining > 0 def execute(self, api, state: AlgoState) -> Dict: remaining = state.availability_expires - time.time() cascade_level = 0 for i, point in enumerate(self.CASCADE_POINTS): if remaining < point: cascade_level = i + 1 try: api.set_availability(option=1, duration=5) state.available = True state.refreshes_executed += 1 state.availability_expires = time.time() + 5 * 3600 _record(state, self.name, "cascade", f"level={cascade_level} remaining={remaining:.0f}s") return {"algo": self.name, "action": "cascade", "level": cascade_level, "remaining_s": int(remaining)} except Exception as e: _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 5: SearchRankBoost — toggle to appear in "available now" more # ═══════════════════════════════════════════════════════════════════ class SearchRankBoost: """Periodic toggle: 10s offline → back available → fresh in 'available now' sort.""" name = "search_rank_boost" interval = 7200 # every 2 hours offline_duration = 10.0 def __init__(self): self.last_toggle = 0 def should_fire(self, state: AlgoState) -> bool: return state.available and (time.time() - self.last_toggle) > self.interval def execute(self, api, state: AlgoState) -> Dict: try: api.set_availability(option=2, duration=0) state.available = False time.sleep(self.offline_duration) api.set_availability(option=1, duration=5) state.available = True state.toggles_executed += 1 self.last_toggle = time.time() _record(state, self.name, "rank_boost", f"offline {self.offline_duration}s") return {"algo": self.name, "action": "rank_boost", "offline_duration": self.offline_duration} except Exception as e: try: api.set_availability(option=1, duration=5) state.available = True except Exception: pass _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 6: DemandPulse — short availability windows in low-traffic periods # ═══════════════════════════════════════════════════════════════════ class DemandPulse: """During low-traffic hours, pulse availability 15min on / 5min off.""" name = "demand_pulse" LOW_HOURS = {2, 3, 4, 5, 6} PULSE_ON = 900 # 15 min available PULSE_OFF = 300 # 5 min unavailable def __init__(self): self.pulse_start = 0 self.pulsing = False def should_fire(self, state: AlgoState) -> bool: if state.current_hour not in self.LOW_HOURS: self.pulsing = False return False if not self.pulsing: return not state.available elapsed = time.time() - self.pulse_start if state.available and elapsed > self.PULSE_ON: return True # time to go offline briefly if not state.available and elapsed > self.PULSE_OFF: return True # time to go back available return False def execute(self, api, state: AlgoState) -> Dict: hour = state.current_hour if not self.pulsing: self.pulsing = True self.pulse_start = time.time() try: api.set_availability(option=1, duration=1) state.available = True _record(state, self.name, "pulse_on", f"hour={hour}") return {"algo": self.name, "action": "pulse_on", "hour": hour} except Exception as e: return {"algo": self.name, "action": "error", "error": str(e)} elapsed = time.time() - self.pulse_start if state.available and elapsed > self.PULSE_ON: try: api.set_availability(option=2, duration=0) state.available = False self.pulse_start = time.time() _record(state, self.name, "pulse_off", f"hour={hour} after {self.PULSE_ON}s") return {"algo": self.name, "action": "pulse_off", "hour": hour} except Exception as e: return {"algo": self.name, "action": "error", "error": str(e)} if not state.available and elapsed > self.PULSE_OFF: try: api.set_availability(option=1, duration=1) state.available = True self.pulse_start = time.time() state.refreshes_executed += 1 _record(state, self.name, "pulse_on", f"hour={hour} after {self.PULSE_OFF}s") return {"algo": self.name, "action": "pulse_on", "hour": hour} except Exception as e: return {"algo": self.name, "action": "error", "error": str(e)} return {"algo": self.name, "action": "noop"} # ═══════════════════════════════════════════════════════════════════ # Algo 7: GeoRotation — rotate timing for multiple timezone waves # ═══════════════════════════════════════════════════════════════════ class GeoRotation: """Stagger availability to catch ET, CT, PT traffic waves.""" name = "geo_rotation" # ET peak: 8-11, 19-23 | CT peak: 9-12, 20-00 | PT peak: 11-14, 22-02 # Combined: ensure availability 8-14 and 19-02 GEO_PEAKS = [(8, 14), (19, 24), (0, 2)] def should_fire(self, state: AlgoState) -> bool: hour = state.current_hour in_peak = any(start <= hour < end for start, end in self.GEO_PEAKS) return in_peak and not state.available def execute(self, api, state: AlgoState) -> Dict: hour = state.current_hour # Calculate duration until peak window ends duration = 2 for start, end in self.GEO_PEAKS: if start <= hour < end: duration = min(6, end - hour) break try: api.set_availability(option=1, duration=duration) state.available = True state.refreshes_executed += 1 _record(state, self.name, "geo_sync", f"hour={hour} dur={duration}h") return {"algo": self.name, "action": "geo_sync", "hour": hour, "duration": duration} except Exception as e: _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 8: EngagementTrigger — refresh after visit/message # ═══════════════════════════════════════════════════════════════════ class EngagementTrigger: """Immediately refresh availability after receiving a visit or message.""" name = "engagement_trigger" COOLDOWN = 300 # 5 min between triggers def __init__(self): self.last_trigger = 0 def should_fire(self, state: AlgoState) -> bool: now = time.time() if (now - self.last_trigger) < self.COOLDOWN: return False # Fire if recent visit or message within last 2 min recent_visit = (now - state.last_visit) < 120 recent_msg = (now - state.last_message) < 120 return recent_visit or recent_msg def execute(self, api, state: AlgoState) -> Dict: try: api.set_availability(option=1, duration=5) state.available = True state.refreshes_executed += 1 self.last_trigger = time.time() trigger = "visit" if (time.time() - state.last_visit) < 120 else "message" _record(state, self.name, "engagement_refresh", f"trigger={trigger}") return {"algo": self.name, "action": "engagement_refresh", "trigger": trigger} except Exception as e: _record(state, self.name, "error", str(e)) return {"algo": self.name, "action": "error", "error": str(e)} # ═══════════════════════════════════════════════════════════════════ # Algo 9: BackoffRecovery — exponential backoff on API failure # ═══════════════════════════════════════════════════════════════════ class BackoffRecovery: """Never lose presence: retry with exponential backoff on API failures.""" name = "backoff_recovery" MAX_RETRIES = 5 BASE_DELAY = 2.0 MAX_DELAY = 120.0 def should_fire(self, state: AlgoState) -> bool: return state.api_failures > 0 and not state.available def execute(self, api, state: AlgoState) -> Dict: for attempt in range(self.MAX_RETRIES): delay = min(self.MAX_DELAY, self.BASE_DELAY * (2 ** attempt)) try: time.sleep(delay) api.set_availability(option=1, duration=5) state.available = True state.api_failures = 0 _record(state, self.name, "recovered", f"attempt={attempt+1} delay={delay:.0f}s") return {"algo": self.name, "action": "recovered", "attempts": attempt + 1, "delay": delay} except Exception as e: state.api_failures += 1 state.last_api_error = str(e) _record(state, self.name, "retry", f"attempt={attempt+1} delay={delay:.0f}s err={str(e)[:80]}") _record(state, self.name, "exhausted", f"failures={state.api_failures}") return {"algo": self.name, "action": "exhausted", "failures": state.api_failures} # ═══════════════════════════════════════════════════════════════════ # Orchestrator — runs all 9 algos in priority order # ═══════════════════════════════════════════════════════════════════ ALL_ALGOS = [ BackoffRecovery(), # 9: recover first if broken RefreshCascade(), # 4: don't let availability lapse EngagementTrigger(), # 8: capitalize on engagement CompetitorGap(), # 3: fill competitor gaps PeakHourSync(), # 2: sync to peaks GeoRotation(), # 7: timezone coverage DemandPulse(), # 6: low-traffic pulsing JitterBurst(), # 1: traffic attraction bursts SearchRankBoost(), # 5: search rank freshness ] ALGO_NAMES = [a.name for a in ALL_ALGOS] def run_algos(api, state: AlgoState) -> List[Dict]: """Run all algos that should fire. Returns list of results.""" results = [] for algo in ALL_ALGOS: try: if algo.should_fire(state): result = algo.execute(api, state) _capture_attribution(state, algo.name, result.get("action", "unknown")) results.append(result) except Exception as e: log.error(f"algo {algo.name} crashed: {e}") _capture_attribution(state, algo.name, "crash") results.append({"algo": algo.name, "action": "crash", "error": str(e)}) return results def algo_status(state: AlgoState) -> Dict[str, Any]: """Get status of all algos for API response.""" return { "available": state.available, "bursts": state.bursts_executed, "refreshes": state.refreshes_executed, "toggles": state.toggles_executed, "api_failures": state.api_failures, "last_error": state.last_api_error, "algos": ALGO_NAMES, "recent": state.algo_history[-10:], "attribution": get_attribution_summary(state), }