{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from suno_utils.utils.text import read_jsonl, write_jsonl, read_json\n",
    "import pandas as pd\n",
    "import re\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "yt_parsed_results = read_json(\"youtube_results_parsed2.json\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "for result in yt_parsed_results:\n",
    "    if len(result[\"youtube_match\"]) > 0:\n",
    "        for k,v in result['original_data'].items():\n",
    "            print(k,v)\n",
    "        for match in result['youtube_match']:\n",
    "            print(match)\n",
    "        break\n",
    "\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from urllib.parse import urlparse, parse_qs\n",
    "\n",
    "\n",
    "def extract_youtube_id(url: str | None) -> str | None:\n",
    "    \"\"\"Extract the YouTube video ID from a variety of YouTube URL formats.\n",
    "\n",
    "    Handles URLs like:\n",
    "    - https://www.youtube.com/watch?v=VIDEO_ID\n",
    "    - https://youtube.com/watch?v=VIDEO_ID&...\n",
    "    - https://youtu.be/VIDEO_ID\n",
    "    \"\"\"\n",
    "    if not url:\n",
    "        return None\n",
    "\n",
    "    parsed = urlparse(url)\n",
    "\n",
    "    # Standard watch URLs: https://www.youtube.com/watch?v=VIDEO_ID\n",
    "    if parsed.query:\n",
    "        qs = parse_qs(parsed.query)\n",
    "        if \"v\" in qs and qs[\"v\"]:\n",
    "            return qs[\"v\"][0]\n",
    "\n",
    "    # Short URLs: https://youtu.be/VIDEO_ID\n",
    "    if parsed.netloc in {\"youtu.be\"} and parsed.path:\n",
    "        # path starts with /VIDEO_ID\n",
    "        return parsed.path.lstrip(\"/\") or None\n",
    "\n",
    "    # Fallback: if path looks like /watch/VIDEO_ID or similar, take last segment\n",
    "    if parsed.path:\n",
    "        parts = [p for p in parsed.path.split(\"/\") if p]\n",
    "        if parts:\n",
    "            return parts[-1]\n",
    "\n",
    "    return None\n",
    "\n",
    "\n",
    "to_keep = []\n",
    "\n",
    "for result in yt_parsed_results:\n",
    "    original = result.get(\"original_data\", {})\n",
    "\n",
    "    # If output_id already exists, keep as-is\n",
    "    if original.get(\"output_id\") not in (None, \"\"):\n",
    "        to_keep.append(original)\n",
    "        continue\n",
    "\n",
    "    parsing_metadata = original.get(\"parsing_metadata\")\n",
    "\n",
    "    # If we have parsing_metadata, try to fill output_id from yt_url\n",
    "    if parsing_metadata:\n",
    "        yt_url = parsing_metadata.get(\"yt_url\")\n",
    "        output_id = extract_youtube_id(yt_url)\n",
    "        if output_id:\n",
    "            original[\"output_id\"] = output_id\n",
    "\n",
    "    # Always try to fill source_ids from youtube_match\n",
    "    youtube_match = result.get(\"youtube_match\") or []\n",
    "    source_ids = []\n",
    "    for match in youtube_match:\n",
    "        url = match.get(\"url\")\n",
    "        vid = extract_youtube_id(url)\n",
    "        if vid:\n",
    "            source_ids.append(vid)\n",
    "\n",
    "    if source_ids:\n",
    "        original[\"source_ids\"] = source_ids\n",
    "\n",
    "    # If we have parsing_metadata, attach youtube_match list into it\n",
    "    if parsing_metadata is not None:\n",
    "        parsing_metadata[\"youtube_match\"] = youtube_match\n",
    "        original[\"parsing_metadata\"] = parsing_metadata\n",
    "\n",
    "    # Finally add the (possibly updated) original_data to to_keep\n",
    "    to_keep.append(original)\n",
    "\n",
    "\n",
    "len(to_keep)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "metas_df = pd.DataFrame(to_keep)\n",
    "metas_df.tail()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Drop URL columns we don't want to keep\n",
    "metas_df = metas_df.drop(columns=[\"output_url\", \"source_urls\"], errors=\"ignore\")\n",
    "\n",
    "# Replace NaN/NaT with Python None\n",
    "metas_df = metas_df.where(pd.notna(metas_df), None)\n",
    "\n",
    "metas_df.tail()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Extract strength metrics out of parsing_metadata into dedicated columns\n",
    "\n",
    "def _get_parsing_confidence(md):\n",
    "    if isinstance(md, dict):\n",
    "        return md.get(\"parsing_confidence\", None)\n",
    "    return None\n",
    "\n",
    "\n",
    "def _get_output_match_score(md):\n",
    "    \"\"\"Top-level match score for the output, from parsing_metadata['yt_match_score'].\"\"\"\n",
    "    if isinstance(md, dict):\n",
    "        return md.get(\"yt_match_score\", None)\n",
    "    return None\n",
    "\n",
    "\n",
    "def _get_source_match_sources(md):\n",
    "    \"\"\"Per-source confidence list, from parsing_metadata['youtube_match'][i]['match_score'].\"\"\"\n",
    "    if isinstance(md, dict):\n",
    "        matches = md.get(\"youtube_match\") or []\n",
    "        scores = []\n",
    "        for m in matches:\n",
    "            if isinstance(m, dict):\n",
    "                scores.append(m.get(\"match_score\", None))\n",
    "        return scores or None\n",
    "    return None\n",
    "\n",
    "metas_df[\"parsing_confidence\"] = metas_df[\"parsing_metadata\"].apply(_get_parsing_confidence)\n",
    "metas_df[\"output_match_score\"] = metas_df[\"parsing_metadata\"].apply(_get_output_match_score)\n",
    "metas_df[\"source_match_sources\"] = metas_df[\"parsing_metadata\"].apply(_get_source_match_sources)\n",
    "\n",
    "metas_df.tail()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "metas_df.tail()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Keep only rows where 2 <= len(source_ids) <= 4\n",
    "\n",
    "def _valid_source_ids(source_ids):\n",
    "    if isinstance(source_ids, list):\n",
    "        return 2 <= len(source_ids) <= 4\n",
    "    return False\n",
    "\n",
    "metas_df = metas_df[metas_df[\"source_ids\"].apply(_valid_source_ids)].reset_index(drop=True)\n",
    "\n",
    "metas_df.tail()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Filter out rows missing both s3_filepath and output_id (require at least one non-empty string)\n",
    "\n",
    "def _non_empty_str(x):\n",
    "    return isinstance(x, str) and x.strip() != \"\"\n",
    "\n",
    "\n",
    "def _has_mashup_asset(row):\n",
    "    return _non_empty_str(row.get(\"s3_filepath\")) or _non_empty_str(row.get(\"output_id\"))\n",
    "\n",
    "metas_df = metas_df[metas_df.apply(_has_mashup_asset, axis=1)].reset_index(drop=True)\n",
    "\n",
    "metas_df.tail()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reorder columns for final metas_df\n",
    "\n",
    "desired_order = [\n",
    "    \"output_id\",\n",
    "    \"source_ids\",\n",
    "    \"s3_filepath\",\n",
    "    \"data_source\",\n",
    "    \"artists\",\n",
    "    \"song_name\",\n",
    "    \"album_name\",\n",
    "    \"release_date\",\n",
    "    \"label\",\n",
    "    \"duration\",\n",
    "    \"genre\",\n",
    "    \"key\",\n",
    "    \"bpm\",\n",
    "    \"mashup_strength\",\n",
    "    \"source_votes\",\n",
    "    \"parsing_confidence\",\n",
    "    \"output_match_score\",\n",
    "    \"source_match_sources\",  # note: column name in DF\n",
    "    \"parsing_metadata\",\n",
    "]\n",
    "\n",
    "# Keep only columns that actually exist, in the requested order\n",
    "existing_in_order = [c for c in desired_order if c in metas_df.columns]\n",
    "\n",
    "# Append any remaining columns at the end so we don't lose information\n",
    "remaining = [c for c in metas_df.columns if c not in existing_in_order]\n",
    "\n",
    "metas_df = metas_df[existing_in_order + remaining]\n",
    "\n",
    "metas_df.head()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write metas_df to JSONL\n",
    "\n",
    "output_jsonl_path = \"/home/sara/task_data/mashups_full_metas_11_17_v0.jsonl\"  # change this path/name if you like\n",
    "\n",
    "records = metas_df.to_dict(orient=\"records\")\n",
    "write_jsonl(records, output_jsonl_path)\n",
    "\n",
    "output_jsonl_path, len(records)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Helper: safe length for lists\n",
    "\n",
    "def _safe_len(x):\n",
    "    return len(x) if isinstance(x, list) else 0\n",
    "\n",
    "# Create figure with subplots (similar style to scratch_11_13)\n",
    "fig, axes = plt.subplots(2, 2, figsize=(15, 12))\n",
    "fig.suptitle(\"Distribution Statistics for YouTube-Parsed Mashups\", fontsize=16, fontweight=\"bold\")\n",
    "\n",
    "# 1. Distribution of mashup_strength\n",
    "ax1 = axes[0, 0]\n",
    "strength_series = metas_df[\"mashup_strength\"].dropna()\n",
    "strength_counts = strength_series.value_counts()\n",
    "ax1.bar(range(len(strength_counts)), strength_counts.values, edgecolor=\"black\", alpha=0.7, color=\"mediumseagreen\")\n",
    "ax1.set_xticks(range(len(strength_counts)))\n",
    "ax1.set_xticklabels(strength_counts.index, rotation=45, ha=\"right\")\n",
    "ax1.set_xlabel(\"Mashup Strength\")\n",
    "ax1.set_ylabel(\"Frequency\")\n",
    "ax1.set_title(f\"Distribution of Mashup Strength\\n(n={len(strength_series):,})\")\n",
    "ax1.grid(True, alpha=0.3, axis=\"y\")\n",
    "\n",
    "# 2. Distribution of data_source\n",
    "ax2 = axes[0, 1]\n",
    "data_source_series = metas_df[\"data_source\"].dropna()\n",
    "source_counts = data_source_series.value_counts()\n",
    "ax2.bar(range(len(source_counts)), source_counts.values, edgecolor=\"black\", alpha=0.7, color=\"plum\")\n",
    "ax2.set_xticks(range(len(source_counts)))\n",
    "ax2.set_xticklabels(source_counts.index, rotation=45, ha=\"right\")\n",
    "ax2.set_xlabel(\"Data Source\")\n",
    "ax2.set_ylabel(\"Frequency\")\n",
    "ax2.set_title(f\"Distribution of Data Source\\n(n={len(data_source_series):,})\")\n",
    "ax2.grid(True, alpha=0.3, axis=\"y\")\n",
    "\n",
    "# 3. Distribution of parsing_confidence (numeric)\n",
    "ax3 = axes[1, 0]\n",
    "parsing_conf_series = pd.to_numeric(metas_df[\"parsing_confidence\"], errors=\"coerce\").dropna()\n",
    "ax3.hist(parsing_conf_series, bins=30, edgecolor=\"black\", alpha=0.7, color=\"steelblue\")\n",
    "ax3.set_xlabel(\"Parsing Confidence\")\n",
    "ax3.set_ylabel(\"Frequency\")\n",
    "ax3.set_title(\n",
    "    f\"Distribution of Parsing Confidence\\n(n={len(parsing_conf_series):,}, \"\n",
    "    f\"mean={parsing_conf_series.mean():.2f}, median={parsing_conf_series.median():.2f})\"\n",
    ")\n",
    "ax3.grid(True, alpha=0.3)\n",
    "\n",
    "# 4. Distribution of release_date (numeric, focus on reasonable year range)\n",
    "ax4 = axes[1, 1]\n",
    "release_dates = pd.to_numeric(metas_df[\"release_date\"], errors=\"coerce\").dropna()\n",
    "release_dates = release_dates[release_dates != 0]\n",
    "release_dates_filtered = release_dates[(release_dates >= 1950) & (release_dates <= 2025)]\n",
    "ax4.hist(release_dates_filtered, bins=50, edgecolor=\"black\", alpha=0.7, color=\"coral\")\n",
    "ax4.set_xlabel(\"Release Date (year)\")\n",
    "ax4.set_ylabel(\"Frequency\")\n",
    "ax4.set_xlim(1950, 2025)\n",
    "ax4.set_title(\n",
    "    f\"Distribution of Release Date\\n(n={len(release_dates_filtered):,}, \"\n",
    "    f\"mean={release_dates_filtered.mean():.0f}, median={release_dates_filtered.median():.0f})\"\n",
    ")\n",
    "ax4.grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# --- Summary stats (formatted like scratch_11_13) ---\n",
    "\n",
    "print(\"=\" * 60)\n",
    "print(\"SUMMARY STATISTICS\")\n",
    "print(\"=\" * 60)\n",
    "\n",
    "num_rows = len(metas_df)\n",
    "print(f\"\\nTotal rows: {num_rows:,}\")\n",
    "\n",
    "# Total duration in hours (assuming 'duration' column is in seconds)\n",
    "duration_seconds = pd.to_numeric(metas_df[\"duration\"], errors=\"coerce\").dropna()\n",
    "total_duration_seconds = duration_seconds.sum()\n",
    "total_hours = total_duration_seconds / 3600.0\n",
    "print(f\"Total duration: {total_duration_seconds:,.2f} seconds\")\n",
    "print(f\"Total duration: {total_hours:,.2f} hours\")\n",
    "print(f\"Total duration: {total_hours/24:,.2f} days\")\n",
    "\n",
    "# Total number of sources (sum of lengths of source_ids lists)\n",
    "total_sources = metas_df[\"source_ids\"].apply(_safe_len).sum()\n",
    "print(f\"Total number of sources: {total_sources:,}\")\n",
    "\n",
    "print(\"\\n\" + \"=\" * 60)\n",
    "print(\"DETAILED STATISTICS BY FIELD\")\n",
    "print(\"=\" * 60)\n",
    "\n",
    "print(\"\\nMashup Strength:\")\n",
    "print(strength_counts)\n",
    "\n",
    "print(\"\\nData Source:\")\n",
    "print(source_counts)\n",
    "\n",
    "print(\"\\nParsing Confidence:\")\n",
    "print(f\"  Non-null values: {metas_df['parsing_confidence'].notna().sum():,}\")\n",
    "print(f\"  Null values: {metas_df['parsing_confidence'].isna().sum():,}\")\n",
    "if len(parsing_conf_series) > 0:\n",
    "    print(f\"  Min: {parsing_conf_series.min():.2f}\")\n",
    "    print(f\"  Max: {parsing_conf_series.max():.2f}\")\n",
    "    print(f\"  Mean: {parsing_conf_series.mean():.2f}\")\n",
    "    print(f\"  Median: {parsing_conf_series.median():.2f}\")\n",
    "    print(f\"  Std Dev: {parsing_conf_series.std():.2f}\")\n",
    "\n",
    "print(\"\\nRelease Date:\")\n",
    "print(f\"  Non-null values: {metas_df['release_date'].notna().sum():,}\")\n",
    "print(f\"  Null values: {metas_df['release_date'].isna().sum():,}\")\n",
    "if len(release_dates_filtered) > 0:\n",
    "    print(f\"  Min (filtered): {int(release_dates_filtered.min())}\")\n",
    "    print(f\"  Max (filtered): {int(release_dates_filtered.max())}\")\n",
    "    print(f\"  Mean: {release_dates_filtered.mean():.0f}\")\n",
    "    print(f\"  Median: {release_dates_filtered.median():.0f}\")\n",
    "    print(f\"  Std Dev: {release_dates_filtered.std():.0f}\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "meta = read_jsonl(\"/home/sara/task_data/whosampled_task_labels.jsonl\")\n",
    "print(meta[0].keys())\n",
    "print(meta[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "valid_remixes = 0\n",
    "for m in meta:\n",
    "    source = m[\"data_source\"]\n",
    "    source_ids = m[\"source_ids\"]\n",
    "    if source  == \"whosampled_remix\" and len(source_ids) == 1:\n",
    "        m['is_valid_remix'] = True\n",
    "        valid_remixes += 1\n",
    "    else:\n",
    "        m['is_valid_remix'] = False\n",
    "print(f\"Valid remixes: {valid_remixes}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {},
   "outputs": [],
   "source": [
    "write_jsonl(meta, \"/home/sara/task_data/whosampled_task_labels.jsonl\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "beatport = read_jsonl(\"/home/sara/task_data/beatport_metadata_raw.jsonl\")\n",
    "print(beatport[0].keys())\n",
    "print(beatport[0])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(beatport)  # expects a 'title' column\n",
    "\n",
    "# Filter out rows where the title contains \"radio edit\" (case-insensitive)\n",
    "df = df[~df[\"title\"].str.contains(\"radio edit\", case=False, na=False)]\n",
    "\n",
    "# --- remix classifier ---\n",
    "\n",
    "STRONG_PATTERN = re.compile(\n",
    "    r\"\\b(remix|rework|bootleg|refix|flip)\\b\",\n",
    "    re.IGNORECASE,\n",
    ")\n",
    "\n",
    "WEAK_PATTERN = re.compile(\n",
    "    r\"\\b(edit|re[-\\s]?edit|club\\s+mix|extended\\s+mix|dub|version|alt\\s+version)\\b\",\n",
    "    re.IGNORECASE,\n",
    ")\n",
    "\n",
    "# segments inside (), [], {} and trailing dash segment\n",
    "MARKER_SEGMENT_PATTERN = re.compile(r\"[\\(\\[\\{]([^()\\[\\]{}]+)[\\)\\]\\}]\")\n",
    "\n",
    "\n",
    "def _extract_marker_segments(title: str) -> str:\n",
    "    segments = []\n",
    "\n",
    "    # ( ... ), [ ... ], { ... }\n",
    "    for m in MARKER_SEGMENT_PATTERN.finditer(title):\n",
    "        segments.append(m.group(1))\n",
    "\n",
    "    # trailing part after last hyphen, e.g. \"Song - Artist Remix\"\n",
    "    if \"-\" in title:\n",
    "        segments.append(title.split(\"-\")[-1])\n",
    "\n",
    "    return \" \".join(s.strip() for s in segments if s.strip())\n",
    "\n",
    "\n",
    "def classify_title(title: str) -> tuple[str, str]:\n",
    "    \"\"\"\n",
    "    Returns (remix_label, remix_confidence), where:\n",
    "      remix_label ∈ {\"strong\", \"weak\", \"none\"}\n",
    "      remix_confidence ∈ {\"very_strong\", \"strong\", \"weak\", \"none\"}\n",
    "    \"\"\"\n",
    "    if not isinstance(title, str):\n",
    "        title = \"\"\n",
    "    t = \" \".join(title.split())\n",
    "\n",
    "    has_strong_full = bool(STRONG_PATTERN.search(t))\n",
    "    has_weak_full = bool(WEAK_PATTERN.search(t))\n",
    "\n",
    "    marker_text = _extract_marker_segments(t)\n",
    "    has_strong_marker = bool(marker_text and STRONG_PATTERN.search(marker_text))\n",
    "    has_weak_marker = bool(marker_text and WEAK_PATTERN.search(marker_text))\n",
    "\n",
    "    # Strong keywords\n",
    "    if has_strong_full:\n",
    "        if has_strong_marker:\n",
    "            return \"strong\", \"very_strong\"  # strong keyword + markers\n",
    "        return \"strong\", \"strong\"\n",
    "\n",
    "    # Weak keywords\n",
    "    if has_weak_full:\n",
    "        if has_weak_marker:\n",
    "            return \"strong\", \"strong\"       # weak keyword + markers promoted\n",
    "        return \"weak\", \"weak\"\n",
    "\n",
    "    return None, None\n",
    "\n",
    "\n",
    "# --- apply to DataFrame ---\n",
    "\n",
    "df[\"remix_label\"], df[\"remix_confidence\"] = zip(\n",
    "    *df[\"title\"].fillna(\"\").map(classify_title)\n",
    ")\n",
    "\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "potential_remix = df[df[\"remix_confidence\"].notna()].copy()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "potential_remix.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "potential_remix.drop(columns=[\"remix_label\"], inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reset index and save potential_remix to JSONL\n",
    "potential_remix = potential_remix.reset_index(drop=True)\n",
    "\n",
    "beatport_output_jsonl_path = \"/home/sara/task_data/beatport_potential_remix_11_17.jsonl\"\n",
    "\n",
    "beatport_records = potential_remix.to_dict(orient=\"records\")\n",
    "write_jsonl(beatport_records, beatport_output_jsonl_path)\n",
    "\n",
    "beatport_output_jsonl_path, len(beatport_records)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "discogs_raw = read_jsonl(\"/home/sara/task_data/discogs_metas_raw.jsonl\")    \n",
    "print(discogs_raw[0].keys())\n",
    "print(discogs_raw[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(discogs_raw)  # expects a 'title' column\n",
    "\n",
    "# Filter out rows where the title contains \"radio edit\" (case-insensitive)\n",
    "df = df[~df[\"title\"].str.contains(\"radio edit\", case=False, na=False)]\n",
    "\n",
    "# --- remix classifier ---\n",
    "\n",
    "STRONG_PATTERN = re.compile(\n",
    "    r\"\\b(remix|rework|bootleg|refix|flip)\\b\",\n",
    "    re.IGNORECASE,\n",
    ")\n",
    "\n",
    "WEAK_PATTERN = re.compile(\n",
    "    r\"\\b(edit|re[-\\s]?edit|club\\s+mix|extended\\s+mix|dub|version|alt\\s+version)\\b\",\n",
    "    re.IGNORECASE,\n",
    ")\n",
    "\n",
    "# segments inside (), [], {} and trailing dash segment\n",
    "MARKER_SEGMENT_PATTERN = re.compile(r\"[\\(\\[\\{]([^()\\[\\]{}]+)[\\)\\]\\}]\")\n",
    "\n",
    "\n",
    "def _extract_marker_segments(title: str) -> str:\n",
    "    segments = []\n",
    "\n",
    "    # ( ... ), [ ... ], { ... }\n",
    "    for m in MARKER_SEGMENT_PATTERN.finditer(title):\n",
    "        segments.append(m.group(1))\n",
    "\n",
    "    # trailing part after last hyphen, e.g. \"Song - Artist Remix\"\n",
    "    if \"-\" in title:\n",
    "        segments.append(title.split(\"-\")[-1])\n",
    "\n",
    "    return \" \".join(s.strip() for s in segments if s.strip())\n",
    "\n",
    "\n",
    "def classify_title(title: str) -> tuple[str, str]:\n",
    "    \"\"\"\n",
    "    Returns (remix_label, remix_confidence), where:\n",
    "      remix_label ∈ {\"strong\", \"weak\", \"none\"}\n",
    "      remix_confidence ∈ {\"very_strong\", \"strong\", \"weak\", \"none\"}\n",
    "    \"\"\"\n",
    "    if not isinstance(title, str):\n",
    "        title = \"\"\n",
    "    t = \" \".join(title.split())\n",
    "\n",
    "    has_strong_full = bool(STRONG_PATTERN.search(t))\n",
    "    has_weak_full = bool(WEAK_PATTERN.search(t))\n",
    "\n",
    "    marker_text = _extract_marker_segments(t)\n",
    "    has_strong_marker = bool(marker_text and STRONG_PATTERN.search(marker_text))\n",
    "    has_weak_marker = bool(marker_text and WEAK_PATTERN.search(marker_text))\n",
    "\n",
    "    # Strong keywords\n",
    "    if has_strong_full:\n",
    "        if has_strong_marker:\n",
    "            return \"strong\", \"very_strong\"  # strong keyword + markers\n",
    "        return \"strong\", \"strong\"\n",
    "\n",
    "    # Weak keywords\n",
    "    if has_weak_full:\n",
    "        if has_weak_marker:\n",
    "            return \"strong\", \"strong\"       # weak keyword + markers promoted\n",
    "        return \"weak\", \"weak\"\n",
    "\n",
    "    return None, None\n",
    "\n",
    "\n",
    "# --- apply to DataFrame ---\n",
    "\n",
    "df[\"remix_label\"], df[\"remix_confidence\"] = zip(\n",
    "    *df[\"title\"].fillna(\"\").map(classify_title)\n",
    ")\n",
    "\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "potential_remix = df[df[\"remix_confidence\"].notna()].copy()\n",
    "potential_remix.drop(columns=[\"remix_label\"], inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reset index and save potential_remix to JSONL\n",
    "potential_remix = potential_remix.reset_index(drop=True)\n",
    "\n",
    "beatport_output_jsonl_path = \"/home/sara/task_data/discogs_potential_remix_11_17.jsonl\"\n",
    "\n",
    "beatport_records = potential_remix.to_dict(orient=\"records\")\n",
    "write_jsonl(beatport_records, beatport_output_jsonl_path)\n",
    "\n",
    "beatport_output_jsonl_path, len(beatport_records)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "suno_clean",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
