{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7ca1b473",
   "metadata": {},
   "source": [
    "# Ranking Signal Analysis\n",
    "\n",
    "Reproducible capstone runner. The numbered Python modules beside this notebook contain the reviewed implementation; generated content-level files remain outside Git. All findings are observational and intended for decision support."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e55b29b1",
   "metadata": {},
   "source": [
    "## 1. Public-safe schema discovery\n",
    "Reuse the committed metadata receipt when present. A fresh checkout can regenerate it with `HF_TOKEN`; no raw rows are printed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5b336e39",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-22T08:12:01.173161Z",
     "iopub.status.busy": "2026-08-22T08:12:01.172161Z",
     "iopub.status.idle": "2026-08-22T08:12:01.225874Z",
     "shell.execute_reply": "2026-08-22T08:12:01.222725Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using committed public-safe schema receipt: ../../docs/schema_notes.md\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "schema_receipt = Path('../../docs/schema_notes.md')\n",
    "if schema_receipt.exists():\n",
    "    print(f'Using committed public-safe schema receipt: {schema_receipt.as_posix()}')\n",
    "else:\n",
    "    get_ipython().run_line_magic('run', './00_schema_discovery.py')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "776df6eb",
   "metadata": {},
   "source": [
    "## 2. Leakage-separated feature windows\n",
    "Reuse ignored content-level feature caches when present; a fresh checkout rebuilds the May robustness and June primary windows from the gated warehouse."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "41bbe89b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-22T08:12:01.233874Z",
     "iopub.status.busy": "2026-08-22T08:12:01.231354Z",
     "iopub.status.idle": "2026-08-22T08:12:01.685732Z",
     "shell.execute_reply": "2026-08-22T08:12:01.684736Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cache hit: features_2026_04_30.parquet | rows=132,875\n",
      "Cache hit: features_2026_05_31.parquet | rows=130,833\n"
     ]
    }
   ],
   "source": [
    "import pyarrow.parquet as pq\n",
    "feature_dir = Path('../../outputs/data')\n",
    "feature_paths = [\n",
    "    feature_dir / 'features_2026_04_30.parquet',\n",
    "    feature_dir / 'features_2026_05_31.parquet',\n",
    "]\n",
    "for cutoff, path in [('2026-04-30', feature_paths[0]), ('2026-05-31', feature_paths[1])]:\n",
    "    if path.exists():\n",
    "        print(f'Cache hit: {path.name} | rows={pq.ParquetFile(path).metadata.num_rows:,}')\n",
    "    else:\n",
    "        get_ipython().run_line_magic('run', f'./01_signal_features_audited.py --cutoff {cutoff}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa71f245",
   "metadata": {},
   "source": [
    "## 3. Grouped modeling and robustness\n",
    "The same held-out-client rule is used for baseline, Ridge, and random forest comparisons."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5de66903",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-22T08:12:01.688737Z",
     "iopub.status.busy": "2026-08-22T08:12:01.688737Z",
     "iopub.status.idle": "2026-08-22T08:14:29.291790Z",
     "shell.execute_reply": "2026-08-22T08:14:29.289793Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"rows\": 130833,\n",
      "  \"train_rows\": 120612,\n",
      "  \"test_rows\": 10221,\n",
      "  \"train_clients\": 39,\n",
      "  \"test_clients\": 9,\n",
      "  \"feature_cutoff\": \"2026-05-31\",\n",
      "  \"target_start\": \"2026-06-01\",\n",
      "  \"target_end\": \"2026-06-28\",\n",
      "  \"metrics\": {\n",
      "    \"baseline\": {\n",
      "      \"r2\": -0.049131612765582755,\n",
      "      \"rmse\": 10.098785757223997,\n",
      "      \"spearman\": -0.0964843057831924\n",
      "    },\n",
      "    \"ridge\": {\n",
      "      \"r2\": -0.004792692816643518,\n",
      "      \"rmse\": 9.883082168795072,\n",
      "      \"spearman\": 0.1652579319313056\n",
      "    },\n",
      "    \"random_forest\": {\n",
      "      \"r2\": -0.07451745105034946,\n",
      "      \"rmse\": 10.22023563047264,\n",
      "      \"spearman\": 0.1173919831750979\n",
      "    },\n",
      "    \"permutation_importance\": {\n",
      "      \"avg_position_28d\": 0.06280154675359184,\n",
      "      \"position_volatility_90d\": 0.02039839241800112,\n",
      "      \"position_trend_slope_90d\": -0.013190782023263884,\n",
      "      \"history_impressions_90d\": -0.010451484393169785,\n",
      "      \"history_ctr_90d\": -0.006058900337723827,\n",
      "      \"history_ctr_28d\": 0.002733676210646611,\n",
      "      \"ctr_gap\": 0.0008439972089608894,\n",
      "      \"keyword_char_count\": -0.0010070529275262678,\n",
      "      \"keyword_token_count\": -0.0009218974221751886,\n",
      "      \"url_char_count\": -0.0032554043394908603,\n",
      "      \"search_volume\": 0.0004427541864822822,\n",
      "      \"competition\": 0.0013560329458030356,\n",
      "      \"cpc\": 0.0004423225477386457,\n",
      "      \"backlinks\": 0.00043151230107800015,\n",
      "      \"category_count\": 0.002561230475293034,\n",
      "      \"char_count\": 0.007367633730697376,\n",
      "      \"word_count\": -0.02653928317061401,\n",
      "      \"client_tenure_days\": 0.004161187462624616,\n",
      "      \"content_type\": -0.0003120125862716705,\n",
      "      \"competition_level\": -0.0008125502384105735,\n",
      "      \"main_intent\": -7.478492351742361e-05,\n",
      "      \"is_published\": 0.0,\n",
      "      \"is_deleted\": 0.0\n",
      "    }\n",
      "  },\n",
      "  \"ridge_coefficient_rank_stability_spearman\": 0.6241405653170359,\n",
      "  \"earlier_window_metrics\": {\n",
      "    \"baseline\": {\n",
      "      \"r2\": 0.05893553566599874,\n",
      "      \"rmse\": 8.892979099345867,\n",
      "      \"spearman\": 0.22384196079821939\n",
      "    },\n",
      "    \"ridge\": {\n",
      "      \"r2\": 0.06774803125604212,\n",
      "      \"rmse\": 8.851242493374444,\n",
      "      \"spearman\": 0.2173549821928672\n",
      "    }\n",
      "  },\n",
      "  \"leakage_audit\": {\n",
      "    \"status\": \"pass\",\n",
      "    \"feature_window_ends_before_target\": true,\n",
      "    \"excluded_shared_window_query_features\": [\n",
      "      \"query_impression_entropy_90d\",\n",
      "      \"visible_query_click_momentum\",\n",
      "      \"visible_query_impression_momentum\"\n",
      "    ],\n",
      "    \"reason\": \"The fixed query snapshot ends after the outcome starts, so it is descriptive only.\"\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "%run ./02_modeling.py"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7b84016",
   "metadata": {},
   "source": [
    "## 4. Descriptive query analysis and paper artifacts\n",
    "Query signals overlap the primary outcome window and are explicitly excluded from the predictive models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3a4a5649",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-22T08:14:29.298792Z",
     "iopub.status.busy": "2026-08-22T08:14:29.297794Z",
     "iopub.status.idle": "2026-08-22T08:14:30.931105Z",
     "shell.execute_reply": "2026-08-22T08:14:30.929013Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                           signal      n  spearman_with_position_change                            interpretation\n",
      "     query_impression_entropy_90d 111928                       0.144823 descriptive_shared_window_not_model_input\n",
      "     visible_query_click_momentum 111928                      -0.071561 descriptive_shared_window_not_model_input\n",
      "visible_query_impression_momentum 111928                      -0.087455 descriptive_shared_window_not_model_input\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "  \"rows\": 130833,\n",
      "  \"clients\": 48,\n",
      "  \"feature_window_days\": 90,\n",
      "  \"outcome_window_days\": 28,\n",
      "  \"median_position_change\": 0.034,\n",
      "  \"archetypes\": [\n",
      "    {\n",
      "      \"name\": \"CTR opportunity\",\n",
      "      \"reason_code\": \"CTR-GAP-LOW\",\n",
      "      \"count\": 13165,\n",
      "      \"share_pct\": 10.1,\n",
      "      \"median_position_change\": 0.23\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Volatile visibility\",\n",
      "      \"reason_code\": \"POS-VOL-HIGH\",\n",
      "      \"count\": 13084,\n",
      "      \"share_pct\": 10.0,\n",
      "      \"median_position_change\": 3.29\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Deteriorating trend\",\n",
      "      \"reason_code\": \"POS-TREND-DOWN\",\n",
      "      \"count\": 13084,\n",
      "      \"share_pct\": 10.0,\n",
      "      \"median_position_change\": 0.25\n",
      "    }\n",
      "  ]\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "%run ./03_query_associations.py\n",
    "%run ./04_analysis_outputs.py"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.11.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
