Download src/analysis.py from pima5/AuspexIQ: direct link, hf CLI and curl.
- Browser
- Download file 7.27 kB
-
https://huggingface.co/spaces/pima5/AuspexIQ/resolve/main/src/analysis.py
- Command line
-
hf download hf://spaces/pima5/AuspexIQ/src/analysis.py
-
curl -L -o analysis.py https://huggingface.co/spaces/pima5/AuspexIQ/resolve/main/src/analysis.py
7.27 kB
| """Baselines, saturation score, and verdict. Pure functions — no I/O, no API calls.""" | |
| import re | |
| from collections import defaultdict | |
| from datetime import datetime, timedelta | |
| from statistics import median | |
| import config | |
| _DURATION_RE = re.compile( | |
| r"^P(?:(?P<days>\d+)D)?(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?$" | |
| ) | |
| def duration_seconds(iso_duration): | |
| """Parse an ISO 8601 duration (e.g. PT4M13S) into seconds. Unparseable → 0.""" | |
| if not iso_duration: | |
| return 0 | |
| match = _DURATION_RE.match(iso_duration) | |
| if not match: | |
| return 0 | |
| days, hours, minutes, seconds = (int(g) if g else 0 for g in match.groups()) | |
| return days * 86400 + hours * 3600 + minutes * 60 + seconds | |
| def parse_timestamp(value): | |
| """Parse an RFC3339 timestamp from the API into an aware datetime.""" | |
| return datetime.fromisoformat(value.replace("Z", "+00:00")) | |
| def is_short(seconds): | |
| return seconds <= config.SHORTS_MAX_SECONDS | |
| def lifetime_average(view_count, video_count): | |
| if not video_count: | |
| return 0.0 | |
| return view_count / video_count | |
| def recent_median_baseline(uploads, now): | |
| """Median views of uploads older than BASELINE_MIN_AGE_DAYS and longer than the | |
| Shorts cutoff. Returns None when fewer than BASELINE_MIN_VIDEOS qualify (caller | |
| falls back to the channel's lifetime average).""" | |
| cutoff = now - timedelta(days=config.BASELINE_MIN_AGE_DAYS) | |
| qualifying = [ | |
| v["views"] | |
| for v in uploads | |
| if v["views"] is not None | |
| and v["published_at"] is not None | |
| and v["published_at"] < cutoff | |
| and not is_short(v["seconds"]) | |
| and not v.get("stream") # cumulative stream views would poison the median | |
| ] | |
| if len(qualifying) < config.BASELINE_MIN_VIDEOS: | |
| return None | |
| return float(median(qualifying)) | |
| def outlier_multiple(views, baseline): | |
| """views / baseline, or None when the baseline is below BASELINE_FLOOR | |
| (avoids absurd multiples on dead channels).""" | |
| if baseline < config.BASELINE_FLOOR: | |
| return None | |
| return views / baseline | |
| def views_per_day(views, published_at, now): | |
| age_days = max((now - published_at).total_seconds() / 86400, 1.0) | |
| return views / age_days | |
| def classify_video(multiple): | |
| """Deterministic performance label from the outlier multiple.""" | |
| if multiple is None: | |
| return "INSUFFICIENT_BASELINE" | |
| if multiple >= config.VIDEO_MEGA_OUTLIER_MULTIPLE: | |
| return "MEGA_OUTLIER" | |
| if multiple >= config.SCAN_OUTLIER_MULTIPLE: | |
| return "OUTLIER" | |
| if multiple >= config.VIDEO_ABOVE_BASELINE_MULTIPLE: | |
| return "ABOVE_BASELINE" | |
| if multiple >= config.VIDEO_TYPICAL_MULTIPLE: | |
| return "TYPICAL" | |
| return "UNDERPERFORMER" | |
| def assess_niche(videos, outlier_records, now): | |
| """Compute signals, saturation score, verdict, and reasons over the analyzed set. | |
| videos: normalized video dicts (post Shorts/hidden-count filtering). | |
| outlier_records: dicts carrying at least 'subs' (None when hidden) per outlier. | |
| Returns (saturation, verdict, reasons, signals). | |
| """ | |
| n = len(videos) | |
| if n == 0: | |
| # no qualifying videos: an honest no-data state, never a scored verdict | |
| return ( | |
| None, | |
| "NO_DATA", | |
| [ | |
| "no qualifying long-form videos were found for this query in the " | |
| "recency window, so no saturation score or entry verdict can be " | |
| "computed; try a broader query or a longer recency window" | |
| ], | |
| { | |
| "channel_diversity": None, | |
| "top3_view_concentration": None, | |
| "fresh_share_90d": None, | |
| "small_channel_outliers": 0, | |
| }, | |
| ) | |
| unique_channels = {v["channel_id"] for v in videos} | |
| u = len(unique_channels) | |
| diversity = u / n | |
| views_by_channel = defaultdict(int) | |
| for v in videos: | |
| views_by_channel[v["channel_id"]] += v["views"] | |
| total_views = sum(views_by_channel.values()) | |
| top3_views = sum(sorted(views_by_channel.values(), reverse=True)[:3]) | |
| c3 = top3_views / total_views if total_views else 0.0 | |
| fresh_cutoff = now - timedelta(days=config.FRESH_WINDOW_DAYS) | |
| fresh_count = sum(1 for v in videos if v["published_at"] >= fresh_cutoff) | |
| f90 = fresh_count / n if n else 0.0 | |
| sw = sum( | |
| 1 | |
| for r in outlier_records | |
| if r["subs"] is not None and r["subs"] < config.SMALL_CHANNEL_SUBS | |
| ) | |
| openness = ( | |
| config.OPENNESS_W_DIVERSITY * diversity | |
| + config.OPENNESS_W_CONCENTRATION * (1 - c3) | |
| + config.OPENNESS_W_FRESHNESS * f90 | |
| + config.OPENNESS_W_SMALL_OUTLIERS | |
| * min(sw, config.SMALL_OUTLIERS_CAP) | |
| / config.SMALL_OUTLIERS_CAP | |
| ) | |
| saturation = round(100 - openness) | |
| if ( | |
| saturation <= config.VERDICT_ENTER_MAX_SATURATION | |
| and sw >= config.VERDICT_ENTER_MIN_SMALL_OUTLIERS | |
| ): | |
| verdict = "ENTER" | |
| lead = ( | |
| f"saturation {saturation}/100 is at or below " | |
| f"{config.VERDICT_ENTER_MAX_SATURATION} with {sw} small-channel " | |
| f"breakout(s); there is room for a new entrant" | |
| ) | |
| elif ( | |
| saturation > config.VERDICT_AVOID_MIN_SATURATION | |
| or f90 < config.VERDICT_AVOID_MAX_FRESH_SHARE | |
| ): | |
| verdict = "AVOID" | |
| parts = [] | |
| if saturation > config.VERDICT_AVOID_MIN_SATURATION: | |
| parts.append( | |
| f"saturation {saturation}/100 exceeds {config.VERDICT_AVOID_MIN_SATURATION}" | |
| ) | |
| if f90 < config.VERDICT_AVOID_MAX_FRESH_SHARE: | |
| parts.append( | |
| f"only {round(f90 * 100)}% of analyzed videos are from the last " | |
| f"{config.FRESH_WINDOW_DAYS} days; the niche looks stale" | |
| ) | |
| lead = " and ".join(parts) | |
| else: | |
| verdict = "CROWDED" | |
| if saturation <= config.VERDICT_ENTER_MAX_SATURATION: | |
| lead = ( | |
| f"saturation {saturation}/100 is moderate, but only {sw} " | |
| f"small-channel breakout(s) cleared the bar; ENTER needs at least " | |
| f"{config.VERDICT_ENTER_MIN_SMALL_OUTLIERS}" | |
| ) | |
| else: | |
| lead = ( | |
| f"saturation {saturation}/100 sits between " | |
| f"{config.VERDICT_ENTER_MAX_SATURATION} and " | |
| f"{config.VERDICT_AVOID_MIN_SATURATION}; established channels " | |
| f"dominate but the niche is not closed" | |
| ) | |
| reasons = [lead] | |
| reasons.extend( | |
| [ | |
| f"{u} unique channels across {n} analyzed videos", | |
| f"top 3 channels hold {round(c3 * 100)}% of the views in the result set", | |
| f"{round(f90 * 100)}% of analyzed videos were published in the last " | |
| f"{config.FRESH_WINDOW_DAYS} days", | |
| f"{sw} outlier video(s) came from channels under " | |
| f"{config.SMALL_CHANNEL_SUBS:,} subscribers", | |
| ] | |
| ) | |
| signals = { | |
| "channel_diversity": round(diversity, 3), | |
| "top3_view_concentration": round(c3, 3), | |
| "fresh_share_90d": round(f90, 3), | |
| "small_channel_outliers": sw, | |
| } | |
| return saturation, verdict, reasons, signals | |