{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":59156,"databundleVersionId":13719397,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"16d65f05","cell_type":"markdown","source":"# MABe Mouse Behavior Detection - Advanced Baseline\nBased on: https://www.kaggle.com/code/snnguynvnk19hl/mabe-challenge-baseline\n\nThis notebook contains an advanced baseline model for the MABe (Multi-Agent Behavior) mouse behavior detection challenge. The model uses sophisticated heuristics including:\n\n## Key Features\n\n1. **Prior-based Prediction**: Uses statistical priors computed from training data\n   - Action duration priors (per lab and global)\n   - Action timing priors (when actions typically occur in videos)\n   - Rare action handling with clipping and normalization\n\n2. **Proximity Windows**: Creates time windows based on mouse tracking data\n   - Computes pairwise features (distance, relative speed, distance change)\n   - Identifies periods when mice are close or interacting\n   - Uses local percentiles for robust thresholds\n\n3. **Smart Allocation**: Allocates behavior segments intelligently\n   - Orders actions by timing priors (when they typically occur)\n   - Allocates durations based on weight and median duration priors\n   - Smooths segments and closes small gaps\n\n4. **Flexible Configuration**: Supports multiple parameter configurations\n   - Lab-specific vs global priors\n   - Window-based vs full-video prediction\n   - Configurable thresholds and smoothing parameters\n\n## Parameters\n\n- `beta`: F-beta score parameter (1.0 for F1 score)\n- `prior_scope`: \"lab\", \"global\", or \"mixed\" for prior selection\n- `eps`: Laplace smoothing parameter for priors\n- `use_windows`: Enable/disable proximity windows\n- `min_len`: Minimum segment length (frames)\n- `gap_close`: Gap closing threshold (frames)\n- `p_min`: Rare action minimum threshold\n- `cap`: Rare action maximum share\n\n## Usage\n\n1. Run the configuration cell to set up paths\n2. Run validation to test on training data\n3. Generate submission file for test data\n\nThe model achieves competitive performance without machine learning by leveraging domain knowledge and statistical patterns in the data.","metadata":{}},{"id":"267d666f","cell_type":"code","source":"# MABe Mouse Behavior Detection - Advanced Baseline\n# This notebook contains an advanced baseline model for mouse behavior detection\n# Run with: !python baseline.py --mode validate --prior-scope mixed --eps 12 -vv\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport logging\nimport os\nimport warnings\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Dict, Iterable, List, Optional, Set, Tuple\nfrom collections import defaultdict\n\nimport polars as pl\nimport numpy as np\nfrom tqdm.auto import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:42.111802Z","iopub.execute_input":"2025-09-22T04:12:42.112041Z","iopub.status.idle":"2025-09-22T04:12:43.111890Z","shell.execute_reply.started":"2025-09-22T04:12:42.112020Z","shell.execute_reply":"2025-09-22T04:12:43.110051Z"}},"outputs":[],"execution_count":null},{"id":"63eb0e9e","cell_type":"code","source":"# ========================\n# Configuration\n# ========================\nIS_KAGGLE=False\nif os.path.exists(\"/kaggle/working\"):\n    IS_KAGGLE=True \n@dataclass(frozen=True)\nclass Config:\n    data_root: Path = Path(os.getenv(\"MABE_DATA_ROOT\", default= \"/kaggle/input/MABe-mouse-behavior-detection\" if IS_KAGGLE else \"./MABe-mouse-behavior-detection\"))\n    submission_file: str = os.getenv(\"MABE_SUBMISSION\", \"submission.csv\")\n    row_id_col: str = os.getenv(\"MABE_ROW_ID_COL\", \"row_id\")\n\n    @property\n    def train_csv(self) -> Path: return self.data_root / \"train.csv\"\n    @property\n    def test_csv(self) -> Path: return self.data_root / \"test.csv\"\n    @property\n    def train_annot_dir(self) -> Path: return self.data_root / \"train_annotation\"\n    @property\n    def train_track_dir(self) -> Path: return self.data_root / \"train_tracking\"\n    @property\n    def test_track_dir(self) -> Path: return self.data_root / \"test_tracking\"\n\n    @property\n    def submission_schema(self) -> Dict[str, pl.DataType]:\n        return {\n            \"video_id\": pl.Int64, \"agent_id\": pl.Utf8, \"target_id\": pl.Utf8,\n            \"action\": pl.Utf8, \"start_frame\": pl.Int64, \"stop_frame\": pl.Int64,\n        }\n\n    @property\n    def solution_schema(self) -> Dict[str, pl.DataType]:\n        return {\n            \"video_id\": pl.Int64, \"agent_id\": pl.Utf8, \"target_id\": pl.Utf8,\n            \"action\": pl.Utf8, \"start_frame\": pl.Int64, \"stop_frame\": pl.Int64,\n            \"lab_id\": pl.Utf8, \"behaviors_labeled\": pl.Utf8,\n        }\n\nlogger = logging.getLogger(__name__)\n\nclass HostVisibleError(Exception): \n    pass\n\ndef setup_logging(verbosity: int = 1) -> None:\n    level = logging.WARNING if verbosity <= 0 else logging.INFO if verbosity == 1 else logging.DEBUG\n    logging.basicConfig(level=level, format=\"%(asctime)s | %(levelname)s | %(name)s | %(message)s\", force=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.112952Z","iopub.execute_input":"2025-09-22T04:12:43.113245Z","iopub.status.idle":"2025-09-22T04:12:43.126526Z","shell.execute_reply.started":"2025-09-22T04:12:43.113222Z","shell.execute_reply":"2025-09-22T04:12:43.125119Z"}},"outputs":[],"execution_count":null},{"id":"2abf5038","cell_type":"code","source":"# ========================\n# Utility Functions & Validators\n# ========================\n\ndef safe_json_loads(s: Optional[str]) -> List[str]:\n    if s is None: return []\n    if isinstance(s, list): return [str(x) for x in s]\n    if not isinstance(s, str): return []\n    s = s.strip()\n    if not s: return []\n    try:\n        return json.loads(s)\n    except Exception:\n        try: return json.loads(s.replace(\"'\", '\"'))\n        except Exception: return []\n\ndef validate_schema(df: pl.DataFrame, schema: Dict[str, pl.DataType], name: str) -> pl.DataFrame:\n    missing = set(schema.keys()) - set(df.columns)\n    if missing: raise ValueError(f\"{name} is missing columns: {missing}\")\n    casts = [pl.col(col).cast(dtype) for col, dtype in schema.items() if df[col].dtype != dtype]\n    return df.with_columns(casts) if casts else df\n\ndef validate_frame_ranges(df: pl.DataFrame, name: str) -> None:\n    if not (df[\"start_frame\"] <= df[\"stop_frame\"]).all():\n        raise ValueError(f\"{name}: start_frame > stop_frame detected\")\n\ndef _norm_mouse_id(x: str | int) -> str:\n    s = str(x)\n    return s if s.startswith(\"mouse\") else f\"mouse{s}\"\n\ndef _norm_triplet(agent: str | int, target: str | int, action: str) -> str:\n    return f\"{_norm_mouse_id(agent)},{_norm_mouse_id(target)},{action}\"\n\ndef _range_frames(start: int, stop: int) -> Iterable[int]:\n    return range(start, stop)  # [start, stop)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.129801Z","iopub.execute_input":"2025-09-22T04:12:43.130188Z","iopub.status.idle":"2025-09-22T04:12:43.167771Z","shell.execute_reply.started":"2025-09-22T04:12:43.130149Z","shell.execute_reply":"2025-09-22T04:12:43.166491Z"}},"outputs":[],"execution_count":null},{"id":"2f83dec8","cell_type":"code","source":"# Additional utility functions for intervals and allocation\n\ndef merge_intervals(intervals: List[Tuple[int,int]]) -> List[Tuple[int,int]]:\n    if not intervals: return []\n    intervals = sorted(intervals)\n    merged = [intervals[0]]\n    for s,e in intervals[1:]:\n        ps,pe = merged[-1]\n        if s <= pe: merged[-1] = (ps, max(pe, e))\n        else: merged.append((s,e))\n    return merged\n\ndef split_interval(s: int, e: int, parts: int) -> List[Tuple[int,int]]:\n    if parts <= 1: return [(s,e)]\n    L = e - s\n    step = L // parts\n    rem = L % parts\n    out = []\n    cur = s\n    for i in range(parts):\n        extra = 1 if i < rem else 0\n        nxt = cur + step + extra\n        out.append((cur, min(nxt, e)))\n        cur = nxt\n    return out\n\ndef largest_remainder_allocation(total: int, weights: List[float]) -> List[int]:\n    if total <= 0 or not weights: return [0]*len(weights)\n    s = sum(weights) or 1.0\n    w = [x/s for x in weights]\n    raw = [total*x for x in w]\n    base = [int(v) for v in raw]\n    remainder = total - sum(base)\n    if remainder > 0:\n        fr = sorted([(i, raw[i]-base[i]) for i in range(len(w))], key=lambda x: x[1], reverse=True)\n        for i in range(remainder):\n            base[fr[i % len(w)][0]] += 1\n    return base","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.169073Z","iopub.execute_input":"2025-09-22T04:12:43.169468Z","iopub.status.idle":"2025-09-22T04:12:43.204894Z","shell.execute_reply.started":"2025-09-22T04:12:43.169435Z","shell.execute_reply":"2025-09-22T04:12:43.203494Z"}},"outputs":[],"execution_count":null},{"id":"ad642119","cell_type":"code","source":"# ========================\n# Metrics (F-beta Score Calculation)\n# ========================\n\ndef single_lab_f1(lab_solution: pl.DataFrame, lab_submission: pl.DataFrame, beta: float = 1.0) -> float:\n    label_frames: Dict[str, Set[int]] = defaultdict(set)\n    for row in lab_solution.to_dicts():\n        label_frames[row[\"label_key\"]].update(_range_frames(row[\"start_frame\"], row[\"stop_frame\"]))\n\n    active_by_video: Dict[int, Set[str]] = {}\n    for row in lab_solution.select([\"video_id\", \"behaviors_labeled\"]).unique().to_dicts():\n        s: Set[str] = set()\n        for item in safe_json_loads(row[\"behaviors_labeled\"]):\n            parts = [p.strip() for p in str(item).replace(\"'\", \"\").split(\",\")]\n            if len(parts) == 3:\n                a, t, act = parts\n                s.add(_norm_triplet(a, t, act))\n        active_by_video[int(row[\"video_id\"])] = s\n\n    prediction_frames: Dict[str, Set[int]] = defaultdict(set)\n    for video_id in lab_solution[\"video_id\"].unique():\n        active = active_by_video.get(int(video_id), set())\n        predicted_mouse_pairs: Dict[str, Set[int]] = defaultdict(set)\n        for row in lab_submission.filter(pl.col(\"video_id\") == video_id).to_dicts():\n            triple_norm = _norm_triplet(row[\"agent_id\"], row[\"target_id\"], row[\"action\"])\n            if triple_norm not in active:\n                continue\n            pred_key = row[\"prediction_key\"]\n            agent_target = f\"{row['agent_id']},{row['target_id']}\"\n            new_frames = set(_range_frames(row[\"start_frame\"], row[\"stop_frame\"]))\n            new_frames -= prediction_frames[pred_key]\n            if predicted_mouse_pairs[agent_target] & new_frames:\n                raise HostVisibleError(\"Multiple predictions for the same frame from one agent/target pair\")\n            prediction_frames[pred_key].update(new_frames)\n            predicted_mouse_pairs[agent_target].update(new_frames)\n\n    tps: Dict[str, int] = defaultdict(int)\n    fns: Dict[str, int] = defaultdict(int)\n    fps: Dict[str, int] = defaultdict(int)\n    distinct_actions: Set[str] = set()\n\n    for key, pred_frames in prediction_frames.items():\n        action = key.split(\"_\")[-1]\n        distinct_actions.add(action)\n        gt_frames = label_frames.get(key, set())\n        tps[action] += len(pred_frames & gt_frames)\n        fns[action] += len(gt_frames - pred_frames)\n        fps[action] += len(pred_frames - gt_frames)\n\n    for key, gt_frames in label_frames.items():\n        action = key.split(\"_\")[-1]\n        distinct_actions.add(action)\n        if key not in prediction_frames:\n            fns[action] += len(gt_frames)\n\n    if not distinct_actions:\n        return 0.0\n\n    beta2 = beta * beta\n    f_scores: List[float] = []\n    for action in distinct_actions:\n        tp, fn, fp = tps[action], fns[action], fps[action]\n        denom = (1 + beta2) * tp + beta2 * fn + fp\n        f_scores.append(0.0 if denom == 0 else (1 + beta2) * tp / denom)\n    return sum(f_scores) / len(f_scores)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.206236Z","iopub.execute_input":"2025-09-22T04:12:43.206622Z","iopub.status.idle":"2025-09-22T04:12:43.235886Z","shell.execute_reply.started":"2025-09-22T04:12:43.206588Z","shell.execute_reply":"2025-09-22T04:12:43.234703Z"}},"outputs":[],"execution_count":null},{"id":"2f3afe4a","cell_type":"code","source":"# Main F-beta score function\n\ndef mouse_fbeta(solution: pl.DataFrame, submission: pl.DataFrame, beta: float = 1.0, cfg: Optional[Config] = None) -> float:\n    cfg = cfg or Config()\n    solution = validate_schema(solution, cfg.solution_schema, \"Solution\")\n    submission = validate_schema(submission, cfg.submission_schema, \"Submission\")\n    validate_frame_ranges(solution, \"Solution\")\n    validate_frame_ranges(submission, \"Submission\")\n\n    solution_videos = solution[\"video_id\"].unique()\n    submission = submission.filter(pl.col(\"video_id\").is_in(solution_videos))\n\n    def add_key(df: pl.DataFrame, col_name: str) -> pl.DataFrame:\n        return df.with_columns(\n            pl.concat_str(\n                [\n                    pl.col(\"video_id\").cast(pl.Utf8),\n                    pl.col(\"agent_id\").cast(pl.Utf8),\n                    pl.col(\"target_id\").cast(pl.Utf8),\n                    pl.col(\"action\"),\n                ],\n                separator=\"_\",\n            ).alias(col_name)\n        )\n\n    solution = add_key(solution, \"label_key\")\n    submission = add_key(submission, \"prediction_key\")\n\n    lab_scores: List[float] = []\n    for lab_id in solution[\"lab_id\"].unique():\n        lab_solution = solution.filter(pl.col(\"lab_id\") == lab_id)\n        lab_videos = lab_solution[\"video_id\"].unique()\n        lab_submission = submission.filter(pl.col(\"video_id\").is_in(lab_videos))\n        lab_scores.append(single_lab_f1(lab_solution, lab_submission, beta=beta))\n\n    return sum(lab_scores) / len(lab_scores) if lab_scores else 0.0\n\ndef score(solution: pl.DataFrame, submission: pl.DataFrame, row_id_column_name: str = \"\", beta: float = 1.0, cfg: Optional[Config] = None) -> float:\n    if row_id_column_name:\n        solution = solution.drop(row_id_column_name, strict=False)\n        submission = submission.drop(row_id_column_name, strict=False)\n    return mouse_fbeta(solution, submission, beta=beta, cfg=cfg)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.237035Z","iopub.execute_input":"2025-09-22T04:12:43.237315Z","iopub.status.idle":"2025-09-22T04:12:43.264552Z","shell.execute_reply.started":"2025-09-22T04:12:43.237286Z","shell.execute_reply":"2025-09-22T04:12:43.263406Z"}},"outputs":[],"execution_count":null},{"id":"28ef109b","cell_type":"code","source":"# ========================\n# Solution Building & Video Spans\n# ========================\n\ndef create_solution_df(dataset: pl.DataFrame, cfg: Optional[Config] = None) -> pl.DataFrame:\n    cfg = cfg or Config()\n    records: List[pl.DataFrame] = []\n    for row in tqdm(dataset.to_dicts(), total=len(dataset), desc=\"Building solution\"):\n        lab_id: str = row[\"lab_id\"]\n        if lab_id.startswith(\"MABe22\"): continue\n        video_id: int = row[\"video_id\"]\n        annot_path = cfg.train_annot_dir / lab_id / f\"{video_id}.parquet\"\n        if not annot_path.exists():\n            logger.warning(\"No annotations for %s\", annot_path)\n            continue\n        try:\n            annot = pl.read_parquet(annot_path).with_columns(\n                [\n                    pl.lit(lab_id).alias(\"lab_id\"),\n                    pl.lit(video_id).alias(\"video_id\"),\n                    pl.lit(row[\"behaviors_labeled\"]).alias(\"behaviors_labeled\"),\n                    pl.concat_str([pl.lit(\"mouse\"), pl.col(\"agent_id\").cast(pl.Utf8)]).alias(\"agent_id\"),\n                    pl.concat_str([pl.lit(\"mouse\"), pl.col(\"target_id\").cast(pl.Utf8)]).alias(\"target_id\"),\n                ]\n            )\n            for col, dtype in (cfg.solution_schema).items():\n                if col in annot.columns and annot[col].dtype != dtype:\n                    annot = annot.with_columns(pl.col(col).cast(dtype))\n            annot = annot.select([c for c in cfg.solution_schema.keys() if c in annot.columns])\n            records.append(annot)\n        except Exception as e:\n            logger.error(\"Failed to load %s: %s\", annot_path, e)\n            continue\n    if not records: raise ValueError(\"No annotation files loaded.\")\n    solution = pl.concat(records, how=\"vertical\")\n    solution = validate_schema(solution, cfg.solution_schema, \"Solution\")\n    return solution","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.265576Z","iopub.execute_input":"2025-09-22T04:12:43.266010Z","iopub.status.idle":"2025-09-22T04:12:43.293780Z","shell.execute_reply.started":"2025-09-22T04:12:43.265942Z","shell.execute_reply":"2025-09-22T04:12:43.292499Z"}},"outputs":[],"execution_count":null},{"id":"cff815e0","cell_type":"code","source":"# Video spans builder\n\ndef build_video_spans(dataset: pl.DataFrame, split: str, cfg: Optional[Config] = None) -> Dict[int, Tuple[int,int]]:\n    \"\"\"\n    Map video_id -> (min_frame, max_frame+1).\n    \"\"\"\n    cfg = cfg or Config()\n    track_dir = cfg.train_track_dir if split == \"train\" else cfg.test_track_dir\n    spans: Dict[int, Tuple[int,int]] = {}\n    for row in tqdm(dataset.to_dicts(), total=len(dataset), desc=\"Scanning spans\"):\n        lab_id = row[\"lab_id\"]\n        if lab_id.startswith(\"MABe22\"): continue\n        vid = row[\"video_id\"]\n        path = track_dir / lab_id / f\"{vid}.parquet\"\n        if not path.exists(): continue\n        try:\n            df = pl.read_parquet(path).select([\"video_frame\"])\n            s = int(df[\"video_frame\"].min())\n            e = int(df[\"video_frame\"].max()) + 1\n            spans[int(vid)] = (s,e)\n        except Exception as e:\n            logger.warning(\"Span read failed for %s: %s\", path, e)\n    return spans","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.294994Z","iopub.execute_input":"2025-09-22T04:12:43.295553Z","iopub.status.idle":"2025-09-22T04:12:43.324680Z","shell.execute_reply.started":"2025-09-22T04:12:43.295517Z","shell.execute_reply":"2025-09-22T04:12:43.323443Z"}},"outputs":[],"execution_count":null},{"id":"5eec149b","cell_type":"code","source":"# ========================\n# Prior Computation (Duration & Timing)\n# ========================\n\ndef compute_action_priors(solution: pl.DataFrame, eps: float = 1.0) -> Tuple[Dict[str, Dict[str, float]], Dict[str, float], Dict[str, Dict[str, int]], Dict[str, int]]:\n    \"\"\"\n    Returns:\n      per_lab_weight: {lab: {action: weight_share}}\n      global_weight: {action: weight_share}\n      per_lab_med_dur: {lab: {action: median_duration_frames}}\n      global_med_dur: {action: median_duration_frames}\n    \"\"\"\n    sol = solution.with_columns((pl.col(\"stop_frame\") - pl.col(\"start_frame\")).alias(\"dur\"))\n    # shares\n    by_lab = sol.group_by([\"lab_id\", \"action\"]).agg(pl.col(\"dur\").sum().alias(\"dur_sum\"))\n    global_ = sol.group_by([\"action\"]).agg(pl.col(\"dur\").sum().alias(\"dur_sum\"))\n    actions = set(global_[\"action\"].to_list())\n\n    per_lab_weight: Dict[str, Dict[str, float]] = defaultdict(dict)\n    for lab in by_lab[\"lab_id\"].unique():\n        sub = by_lab.filter(pl.col(\"lab_id\") == lab)\n        dmap = {r[\"action\"]: float(r[\"dur_sum\"]) for r in sub.to_dicts()}\n        for a in actions: dmap[a] = dmap.get(a, 0.0) + eps\n        total = sum(dmap.values()) or 1.0\n        per_lab_weight[str(lab)] = {a: dmap[a]/total for a in actions}\n\n    gmap = {r[\"action\"]: float(r[\"dur_sum\"]) for r in global_.to_dicts()}\n    for a in actions: gmap[a] = gmap.get(a, 0.0) + eps\n    gtotal = sum(gmap.values()) or 1.0\n    global_weight = {a: gmap[a]/gtotal for a in actions}\n\n    # median durations\n    med_by_lab = sol.group_by([\"lab_id\", \"action\"]).median().select([\"lab_id\",\"action\",\"dur\"])\n    per_lab_med_dur: Dict[str, Dict[str, int]] = defaultdict(dict)\n    for r in med_by_lab.to_dicts():\n        per_lab_med_dur[str(r[\"lab_id\"])][str(r[\"action\"])] = int(r[\"dur\"])\n    med_global = sol.group_by([\"action\"]).median().select([\"action\",\"dur\"])\n    global_med_dur: Dict[str, int] = {r[\"action\"]: int(r[\"dur\"]) for r in med_global.to_dicts()}\n\n    return per_lab_weight, global_weight, per_lab_med_dur, global_med_dur","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.328578Z","iopub.execute_input":"2025-09-22T04:12:43.330025Z","iopub.status.idle":"2025-09-22T04:12:43.356327Z","shell.execute_reply.started":"2025-09-22T04:12:43.329991Z","shell.execute_reply":"2025-09-22T04:12:43.354983Z"}},"outputs":[],"execution_count":null},{"id":"8d1d7f84","cell_type":"code","source":"# Timing priors computation\n\ndef compute_timing_priors(solution: pl.DataFrame, video_spans: Dict[int, Tuple[int,int]]) -> Tuple[Dict[str, Dict[str, float]], Dict[str, float]]:\n    \"\"\"\n    Median start percentile per (lab, action) and global.\n    start_pct = (start_frame - video_start)/(video_stop - video_start)\n    \"\"\"\n    # attach start_pct\n    def start_pct_func(row) -> float:\n        vid = int(row[\"video_id\"])\n        if vid not in video_spans: return 0.5\n        s,e = video_spans[vid]\n        denom = max(1, e - s)\n        return float(max(0, min(1, (int(row[\"start_frame\"]) - s) / denom)))\n\n    rows = []\n    for r in solution.select([\"lab_id\",\"action\",\"video_id\",\"start_frame\"]).to_dicts():\n        rows.append({\"lab_id\": r[\"lab_id\"], \"action\": r[\"action\"], \"start_pct\": start_pct_func(r)})\n    df = pl.DataFrame(rows)\n    by_lab = df.group_by([\"lab_id\",\"action\"]).median().select([\"lab_id\",\"action\",\"start_pct\"])\n    per_lab: Dict[str, Dict[str, float]] = defaultdict(dict)\n    for r in by_lab.to_dicts():\n        per_lab[str(r[\"lab_id\"])][str(r[\"action\"])] = float(r[\"start_pct\"])\n    g = df.group_by([\"action\"]).median().select([\"action\",\"start_pct\"])\n    global_: Dict[str, float] = {r[\"action\"]: float(r[\"start_pct\"]) for r in g.to_dicts()}\n    return per_lab, global_","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.357630Z","iopub.execute_input":"2025-09-22T04:12:43.358012Z","iopub.status.idle":"2025-09-22T04:12:43.386693Z","shell.execute_reply.started":"2025-09-22T04:12:43.357982Z","shell.execute_reply":"2025-09-22T04:12:43.385484Z"}},"outputs":[],"execution_count":null},{"id":"e5f77308","cell_type":"code","source":"# ========================\n# Tracking Features → Windows\n# ========================\n\ndef _strip_mouse_prefix(s: str | int) -> str:\n    s = str(s)\n    return s[5:] if s.startswith(\"mouse\") else s\n\ndef _pair_features(df: pl.DataFrame, agent_raw: str, target_raw: str, downsample: int = 1) -> Optional[pl.DataFrame]:\n    \"\"\"\n    Tính features cho một cặp (agent,target) bằng NumPy:\n      - dist  = sqrt((ax-bx)^2 + (ay-by)^2)\n      - rel_speed = speed_a - speed_b, với speed = sqrt(dx^2 + dy^2) khung-kề-khung\n      - ddist = diff(dist)\n    Trả về Polars DataFrame: [\"frame\",\"dist\",\"rel_speed\",\"ddist\"] đã sort theo frame.\n    downsample: lấy mỗi N khung (N>=1). Giá trị 2–3 giúp tăng tốc đáng kể.\n    \"\"\"\n    # --- auto-detect schema ---\n    frame_candidates = [\"video_frame\",\"frame\",\"frame_idx\"]\n    id_candidates    = [\"mouse_id\",\"id\",\"track_id\",\"agent_id\"]\n    x_candidates     = [\"x\",\"x_pos\",\"x_position\",\"x_mm\",\"centroid_x\",\"cx\"]\n    y_candidates     = [\"y\",\"y_pos\",\"y_position\",\"y_mm\",\"centroid_y\",\"cy\"]\n\n    cols = set(df.columns)\n    frame_col = next((c for c in frame_candidates if c in cols), None)\n    id_col    = next((c for c in id_candidates    if c in cols), None)\n    x_col     = next((c for c in x_candidates     if c in cols), None)\n    y_col     = next((c for c in y_candidates     if c in cols), None)\n    if not all([frame_col, id_col, x_col, y_col]):\n        return None\n\n    # --- chuẩn hoá ID ---\n    a_id = _strip_mouse_prefix(agent_raw)\n    t_id = _strip_mouse_prefix(target_raw)\n\n    # --- lấy tối thiểu các cột cần thiết & chuyển sang pandas để dùng NumPy ---\n    # (to_pandas trên 4 cột nhỏ rất nhanh; tránh join nhiều lần trong Polars)\n    pdf = df.select([frame_col, id_col, x_col, y_col]).to_pandas()\n    # ép kiểu an toàn\n    pdf[frame_col] = pdf[frame_col].astype(np.int64, copy=False)\n    pdf[id_col]    = pdf[id_col].astype(str, copy=False)\n\n    a = pdf[pdf[id_col] == a_id].copy()\n    b = pdf[pdf[id_col] == t_id].copy()\n    if a.empty or b.empty:\n        return None\n\n    # ưu tiên một bản ghi / frame / mouse (nếu trùng) để merge gọn\n    a.drop_duplicates(subset=[frame_col], keep=\"first\", inplace=True)\n    b.drop_duplicates(subset=[frame_col], keep=\"first\", inplace=True)\n\n    merged = a.merge(b, on=frame_col, how=\"inner\", suffixes=(\"_a\", \"_b\"))\n    if merged.empty:\n        return None\n    merged.sort_values(frame_col, inplace=True)\n\n    # --- NumPy vectors ---\n    ax = merged[f\"{x_col}_a\"].to_numpy(dtype=np.float64, copy=False)\n    ay = merged[f\"{y_col}_a\"].to_numpy(dtype=np.float64, copy=False)\n    bx = merged[f\"{x_col}_b\"].to_numpy(dtype=np.float64, copy=False)\n    by = merged[f\"{y_col}_b\"].to_numpy(dtype=np.float64, copy=False)\n    frames = merged[frame_col].to_numpy(dtype=np.int64, copy=False)\n\n    # downsample nếu cần\n    if downsample > 1:\n        sl = slice(0, None, int(downsample))\n        ax, ay, bx, by, frames = ax[sl], ay[sl], bx[sl], by[sl], frames[sl]\n        if ax.size == 0:\n            return None\n\n    dx = ax - bx\n    dy = ay - by\n    dist = np.sqrt(dx*dx + dy*dy)\n\n    # tốc độ từng chuột (dịch khung-kề-khung; prepend để giữ chiều)\n    dax = np.diff(ax, prepend=ax[0])\n    day = np.diff(ay, prepend=ay[0])\n    dbx = np.diff(bx, prepend=bx[0])\n    dby = np.diff(by, prepend=by[0])\n    speed_a = np.sqrt(dax*dax + day*day)\n    speed_b = np.sqrt(dbx*dbx + dby*dby)\n\n    rel_speed = speed_a - speed_b\n    ddist = np.diff(dist, prepend=dist[0])\n\n    # --- trả về Polars DF cho _make_windows(...) hiện tại ---\n    feat = pl.DataFrame(\n        {\n            \"frame\": frames,\n            \"dist\": dist,\n            \"rel_speed\": rel_speed,\n            \"ddist\": ddist,\n        }\n    ).sort(\"frame\")\n\n    return feat","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.387901Z","iopub.execute_input":"2025-09-22T04:12:43.388227Z","iopub.status.idle":"2025-09-22T04:12:43.427007Z","shell.execute_reply.started":"2025-09-22T04:12:43.388198Z","shell.execute_reply":"2025-09-22T04:12:43.425383Z"}},"outputs":[],"execution_count":null},{"id":"f06640e0","cell_type":"code","source":"# Window creation from features\n\ndef _make_windows(feat: pl.DataFrame, min_len: int, q_dist: float = 0.40, q_rel: float = 0.60, q_ddist: float = 0.40) -> List[Tuple[int,int]]:\n    \"\"\"\n    Local percentiles per pair for robust thresholds.\n    Condition:\n      (dist <= Pq_dist) or (rel_speed >= Pq_rel and ddist <= Pq_ddist)\n    \"\"\"\n    if len(feat) == 0:\n        return []\n    # quantiles\n    qd = float(feat[\"dist\"].quantile(q_dist))\n    qr = float(feat[\"rel_speed\"].quantile(q_rel))\n    qdd = float(feat[\"ddist\"].quantile(q_ddist))  # typically negative\n    # boolean mask\n    cond = (pl.col(\"dist\") <= qd) | ((pl.col(\"rel_speed\") >= qr) & (pl.col(\"ddist\") <= qdd))\n    mask = feat.select(cond.alias(\"m\")).to_series().to_list()\n    frames = feat[\"frame\"].to_list()\n\n    windows: List[Tuple[int,int]] = []\n    run: Optional[List[int]] = None\n    for i, flag in enumerate(mask):\n        if flag and run is None:\n            run = [frames[i], frames[i]]\n        elif flag and run is not None:\n            run[1] = frames[i]\n        elif (not flag) and run is not None:\n            s,e = run[0], run[1]+1\n            if e - s >= min_len:\n                windows.append((s,e))\n            run = None\n    if run is not None:\n        s,e = run[0], run[1]+1\n        if e - s >= min_len:\n            windows.append((s,e))\n    return merge_intervals(windows)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.428346Z","iopub.execute_input":"2025-09-22T04:12:43.428931Z","iopub.status.idle":"2025-09-22T04:12:43.465029Z","shell.execute_reply.started":"2025-09-22T04:12:43.428886Z","shell.execute_reply":"2025-09-22T04:12:43.463590Z"}},"outputs":[],"execution_count":null},{"id":"c2864480","cell_type":"code","source":"# ========================\n# Advanced Baseline Prediction\n# ========================\n\ndef _order_actions_by_timing(actions: List[str], lab_id: str,\n                             timing_lab: Dict[str, Dict[str, float]],\n                             timing_global: Dict[str, float],\n                             canonical: Dict[str,int]) -> List[str]:\n    def score(a: str) -> float:\n        if lab_id in timing_lab and a in timing_lab[lab_id]:\n            return timing_lab[lab_id][a]\n        return timing_global.get(a, 0.5)\n    # earlier → smaller\n    return sorted(actions, key=lambda a: (score(a), canonical.get(a, 99)))\n\ndef _clip_rare_actions(weights_map: Dict[str,float], actions: List[str], p_min: float, cap: float) -> Dict[str,float]:\n    w = {a: max(0.0, float(weights_map.get(a, 0.0))) for a in actions}\n    # if an action extremely rare, cap its share\n    for a in actions:\n        if w[a] < p_min:\n            w[a] = min(w[a], cap)\n    s = sum(w.values()) or 1.0\n    return {a: w[a]/s for a in actions}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.466369Z","iopub.execute_input":"2025-09-22T04:12:43.466777Z","iopub.status.idle":"2025-09-22T04:12:43.501476Z","shell.execute_reply.started":"2025-09-22T04:12:43.466743Z","shell.execute_reply":"2025-09-22T04:12:43.499855Z"}},"outputs":[],"execution_count":null},{"id":"66b766d4","cell_type":"code","source":"# Segment allocation and smoothing functions\n\ndef _allocate_segments_in_windows(windows: List[Tuple[int,int]],\n                                  ordered_actions: List[str],\n                                  weights: Dict[str,float],\n                                  med_dur: Dict[str,int],\n                                  total_frames: int) -> List[Tuple[str,int,int]]:\n    \"\"\"\n    Allocate contiguous segments across union(windows) sequentially following ordered_actions.\n    Length per action ~ max(weight*total, median_duration) but clipped by remaining frames.\n    \"\"\"\n    # flatten windows into a sequence of positions\n    win_idx = 0\n    cur_s, cur_e = (windows[0] if windows else (0,0))\n    remain = sum(e-s for s,e in windows)\n    out: List[Tuple[str,int,int]] = []\n\n    for a in ordered_actions:\n        if remain <= 0: break\n        want = int(weights.get(a, 0.0) * total_frames)\n        want = max(want, int(med_dur.get(a, 0) or 0))\n        want = min(want, remain)\n        got = 0\n        while got < want and win_idx < len(windows):\n            s,e = cur_s, cur_e\n            if s >= e:\n                win_idx += 1\n                if win_idx >= len(windows): break\n                cur_s, cur_e = windows[win_idx]\n                continue\n            take = min(want - got, e - s)\n            out.append((a, s, s+take))\n            got += take\n            remain -= take\n            cur_s = s + take\n            if cur_s >= e and win_idx < len(windows):\n                win_idx += 1\n                if win_idx < len(windows):\n                    cur_s, cur_e = windows[win_idx]\n    return out\n\ndef _smooth_segments(segments: List[Tuple[str,int,int]], min_len: int, gap_close: int) -> List[Tuple[str,int,int]]:\n    if not segments: return []\n    # sort\n    segments = sorted(segments, key=lambda x: (x[1], x[2], x[0]))\n    # remove too short\n    segments = [seg for seg in segments if seg[2] - seg[1] >= min_len]\n    if not segments: return []\n    # merge same-action with small gap\n    out = [segments[0]]\n    for a,s,e in segments[1:]:\n        pa,ps,pe = out[-1]\n        if a == pa and s - pe <= gap_close:\n            out[-1] = (a, ps, e)\n        else:\n            out.append((a,s,e))\n    return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.502853Z","iopub.execute_input":"2025-09-22T04:12:43.503253Z","iopub.status.idle":"2025-09-22T04:12:43.536830Z","shell.execute_reply.started":"2025-09-22T04:12:43.503222Z","shell.execute_reply":"2025-09-22T04:12:43.534792Z"}},"outputs":[],"execution_count":null},{"id":"58395528","cell_type":"code","source":"# Main prediction function (Part 1)\n\ndef predict_without_ml(dataset: pl.DataFrame, data_split: str, cfg: Optional[Config] = None,\n                       priors_per_lab: Optional[Dict[str, Dict[str, float]]] = None,\n                       priors_global: Optional[Dict[str, float]] = None,\n                       meddur_per_lab: Optional[Dict[str, Dict[str, int]]] = None,\n                       meddur_global: Optional[Dict[str, int]] = None,\n                       timing_lab: Optional[Dict[str, Dict[str, float]]] = None,\n                       timing_global: Optional[Dict[str, float]] = None,\n                       prior_scope: str = \"mixed\",\n                       use_windows: bool = True,\n                       min_len: int = 10,\n                       gap_close: int = 5,\n                       p_min: float = 0.03,\n                       cap: float = 0.02) -> pl.DataFrame:\n    \"\"\"\n    Advanced heuristic:\n      - Optionally create proximity windows from tracking per (agent,target) using pairwise features.\n      - Allocate within union(windows) following action order by timing prior; lengths by weight & median duration.\n      - Smooth & gap-close small segments; rare-action clipping.\n    \"\"\"\n    cfg = cfg or Config()\n    track_dir = cfg.test_track_dir if data_split == \"test\" else cfg.train_track_dir\n    records: List[Tuple[int, str, str, str, int, int]] = []\n    canonical = {\"approach\": 0, \"avoid\": 1, \"chase\": 2, \"chaseattack\": 3, \"attack\": 4, \"mount\": 5, \"submit\": 6}\n    # Main prediction function (Part 2 - Processing loop)\n\n    for row in tqdm(dataset.to_dicts(), total=len(dataset), desc=f\"Predicting ({data_split})\"):\n        lab_id: str = row[\"lab_id\"]\n        if lab_id.startswith(\"MABe22\"): continue\n        video_id: int = row[\"video_id\"]\n        path = track_dir / lab_id / f\"{video_id}.parquet\"\n        if not path.exists():\n            logger.warning(\"Tracking file not found: %s\", path)\n            continue\n\n        try:\n            trk = pl.read_parquet(path)\n            start_frame = int(trk[\"video_frame\"].min())\n            stop_frame = int(trk[\"video_frame\"].max()) + 1\n            video_frames = stop_frame - start_frame\n            if video_frames <= 0: continue\n\n            # parse behaviors\n            raw_list = safe_json_loads(row[\"behaviors_labeled\"])\n            triples: List[List[str]] = []\n            for b in raw_list:\n                parts = [p.strip() for p in str(b).replace(\"'\", \"\").split(\",\")]\n                if len(parts) == 3:\n                    triples.append(parts)\n            if not triples:  # no actions labeled for this video\n                continue\n\n            beh_df = pl.DataFrame(triples, schema=[\"agent\",\"target\",\"action\"], orient=\"row\").with_columns(\n                [pl.col(\"agent\").cast(pl.Utf8), pl.col(\"target\").cast(pl.Utf8), pl.col(\"action\").cast(pl.Utf8)]\n            )\n        # Main prediction function (Part 3 - Per agent-target processing)\n\n            # per (agent,target)\n            for (agent, target), group in beh_df.group_by([\"agent\",\"target\"]):\n                actions = sorted(list(set(group[\"action\"].to_list())), key=lambda a: canonical.get(a, 99))\n                if not actions: continue\n\n                # choose priors\n                if prior_scope == \"lab\" and priors_per_lab is not None:\n                    w_map = priors_per_lab.get(str(lab_id), {})\n                    md_map = meddur_per_lab.get(str(lab_id), {}) if meddur_per_lab else {}\n                elif prior_scope == \"global\" and priors_global is not None:\n                    w_map = priors_global\n                    md_map = meddur_global or {}\n                else:\n                    w_map = (priors_per_lab or {}).get(str(lab_id), {}) or (priors_global or {})\n                    md_map = (meddur_per_lab or {}).get(str(lab_id), {}) or (meddur_global or {})\n\n                # rare-action clipping & renorm\n                weights = _clip_rare_actions(w_map, actions, p_min=p_min, cap=cap)\n\n                # order actions by timing prior\n                ordered_actions = _order_actions_by_timing(\n                    actions, str(lab_id), timing_lab or {}, timing_global or {}, canonical\n                )\n\n                # windows\n                windows: List[Tuple[int,int]]\n                if use_windows:\n                    feat = _pair_features(trk, _norm_mouse_id(agent), _norm_mouse_id(target))\n                    if feat is None:\n                        windows = [(start_frame, stop_frame)]\n                    else:\n                        windows = _make_windows(feat, min_len=min_len)\n                        if not windows:\n                            windows = [(start_frame, stop_frame)]\n                else:\n                    windows = [(start_frame, stop_frame)]\n\n                windows = merge_intervals(windows)\n                allowed_total = sum(e - s for s,e in windows)\n                if allowed_total <= 0:\n                    continue\n\n                # allocate segments along windows\n                segs = _allocate_segments_in_windows(\n                    windows=windows,\n                    ordered_actions=ordered_actions,\n                    weights=weights,\n                    med_dur=md_map,\n                    total_frames=allowed_total\n                )\n\n                segs = _smooth_segments(segs, min_len=min_len, gap_close=gap_close)\n\n                # emit rows\n                for a, s, e in segs:\n                    if e > s:\n                        records.append((\n                            video_id,\n                            _norm_mouse_id(agent), _norm_mouse_id(target),\n                            a, int(s), int(e)\n                        ))\n\n        except Exception as e:\n            logger.error(\"Error processing %s: %s\", path, e)\n            continue\n    # Main prediction function (Part 4 - Return results)\n\n    if not records:\n        raise ValueError(\"No predictions generated.\")\n\n    df = pl.DataFrame(\n        records,\n        schema={\n            \"video_id\": pl.Int64, \"agent_id\": pl.Utf8, \"target_id\": pl.Utf8,\n            \"action\": pl.Utf8, \"start_frame\": pl.Int64, \"stop_frame\": pl.Int64,\n        },\n        orient=\"row\",\n    )\n    df = validate_schema(df, cfg.submission_schema, \"Submission\")\n    validate_frame_ranges(df, \"Submission\")\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.538123Z","iopub.execute_input":"2025-09-22T04:12:43.538461Z","iopub.status.idle":"2025-09-22T04:12:43.600071Z","shell.execute_reply.started":"2025-09-22T04:12:43.538405Z","shell.execute_reply":"2025-09-22T04:12:43.597907Z"}},"outputs":[],"execution_count":null},{"id":"c7fa8134","cell_type":"code","source":"# ========================\n# Main Functions for Validation and Submission\n# ========================\n\ndef run_validate(cfg: Config, beta: float, prior_scope: str, eps: float,\n                 use_windows: bool, min_len: int, gap_close: int, p_min: float, cap: float) -> float:\n    logger.info(\"Loading train data for validation: %s\", cfg.train_csv)\n    train = pl.read_csv(cfg.train_csv)\n    train_subset = train.filter(~pl.col(\"lab_id\").str.starts_with(\"MABe22\"))\n\n    logger.info(\"Building solution dataframe & spans...\")\n    solution = create_solution_df(train_subset, cfg)\n    spans = build_video_spans(train_subset, \"train\", cfg)\n\n    logger.info(\"Computing priors (eps=%.2f) & timing...\", eps)\n    per_lab, global_w, med_lab, med_glob = compute_action_priors(solution, eps=eps)\n    timing_lab, timing_glob = compute_timing_priors(solution, spans)\n\n    logger.info(\"Generating predictions (advanced)...\")\n    submission_train = predict_without_ml(\n        train_subset, \"train\", cfg,\n        priors_per_lab=per_lab, priors_global=global_w,\n        meddur_per_lab=med_lab, meddur_global=med_glob,\n        timing_lab=timing_lab, timing_global=timing_glob,\n        prior_scope=prior_scope,\n        use_windows=use_windows, min_len=min_len, gap_close=gap_close,\n        p_min=p_min, cap=cap\n    )\n\n    logger.info(\"Scoring (beta=%.3f)...\", beta)\n    val_score = score(solution, submission_train, cfg.row_id_col, beta=beta, cfg=cfg)\n    print(f\"[RESULT] Validation F1: {val_score:.6f}\")\n    return val_score","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.601344Z","iopub.execute_input":"2025-09-22T04:12:43.602526Z","iopub.status.idle":"2025-09-22T04:12:43.642385Z","shell.execute_reply.started":"2025-09-22T04:12:43.602485Z","shell.execute_reply":"2025-09-22T04:12:43.641388Z"}},"outputs":[],"execution_count":null},{"id":"0c88e146","cell_type":"code","source":"# Submission generation function\n\ndef run_submit(cfg: Config, prior_scope: str, eps: float,\n               use_windows: bool, min_len: int, gap_close: int, p_min: float, cap: float) -> None:\n    logger.info(\"Loading train (for priors) and test data...\")\n    train = pl.read_csv(cfg.train_csv)\n    train_subset = train.filter(~pl.col(\"lab_id\").str.starts_with(\"MABe22\"))\n    solution = create_solution_df(train_subset, cfg)\n    spans = build_video_spans(train_subset, \"train\", cfg)\n    per_lab, global_w, med_lab, med_glob = compute_action_priors(solution, eps=eps)\n    timing_lab, timing_glob = compute_timing_priors(solution, spans)\n\n    test = pl.read_csv(cfg.test_csv)\n\n    logger.info(\"Generating predictions (advanced, test)...\")\n    submission_test = predict_without_ml(\n        test, \"test\", cfg,\n        priors_per_lab=per_lab, priors_global=global_w,\n        meddur_per_lab=med_lab, meddur_global=med_glob,\n        timing_lab=timing_lab, timing_global=timing_glob,\n        prior_scope=prior_scope,\n        use_windows=use_windows, min_len=min_len, gap_close=gap_close,\n        p_min=p_min, cap=cap\n    )\n\n    ordered = list(cfg.submission_schema.keys())\n    submission_test = submission_test.select(ordered).with_row_index(cfg.row_id_col)\n\n    logger.info(\"Saving submission to %s\", cfg.submission_file)\n    submission_test.write_csv(cfg.submission_file)\n\n    try:\n        with open(cfg.submission_file, \"r\") as f:\n            for _ in range(10):\n                line = f.readline()\n                if not line: break\n                logger.info(\"SUBMISSION PREVIEW | %s\", line.strip())\n    except Exception as e:\n        logger.warning(\"Preview failed: %s\", e)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.643749Z","iopub.execute_input":"2025-09-22T04:12:43.644125Z","iopub.status.idle":"2025-09-22T04:12:43.677504Z","shell.execute_reply.started":"2025-09-22T04:12:43.644097Z","shell.execute_reply":"2025-09-22T04:12:43.676500Z"}},"outputs":[],"execution_count":null},{"id":"5bd4e1b8","cell_type":"code","source":"# ========================\n# Example Usage and Testing\n# ========================\n\n# Initialize configuration and setup logging\nsetup_logging(verbosity=2)\nwarnings.filterwarnings(\"ignore\")\n\n# Create configuration instance\ncfg = Config()\nprint(f\"Data root: {cfg.data_root}\")\nprint(f\"Train CSV: {cfg.train_csv}\")\nprint(f\"Test CSV: {cfg.test_csv}\")\n\n# Check if data files exist\nprint(f\"Train CSV exists: {cfg.train_csv.exists()}\")\nprint(f\"Test CSV exists: {cfg.test_csv.exists()}\")\nprint(f\"Train annotation dir exists: {cfg.train_annot_dir.exists()}\")\nprint(f\"Train tracking dir exists: {cfg.train_track_dir.exists()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.678631Z","iopub.execute_input":"2025-09-22T04:12:43.678923Z","iopub.status.idle":"2025-09-22T04:12:43.712561Z","shell.execute_reply.started":"2025-09-22T04:12:43.678901Z","shell.execute_reply":"2025-09-22T04:12:43.710687Z"}},"outputs":[],"execution_count":null},{"id":"58338dc4","cell_type":"code","source":"# Run validation with default parameters\n# This will validate the model using the training data\n\nprint(\"Running validation...\")\ntry:\n    val_score = run_validate(\n        cfg=cfg,\n        beta=1.0,  # F1 score (beta=1)\n        prior_scope=\"mixed\",  # Use lab-specific priors with global fallback\n        eps=12.0,  # Laplace smoothing parameter\n        use_windows=True,  # Use proximity windows\n        min_len=10,  # Minimum segment length\n        gap_close=5,  # Gap closing threshold\n        p_min=0.03,  # Rare action threshold\n        cap=0.02  # Rare action cap\n    )\n    print(f\"Validation completed successfully! F1 Score: {val_score:.6f}\")\nexcept Exception as e:\n    print(f\"Validation failed: {e}\")\n    import traceback\n    traceback.print_exc()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:12:43.713896Z","iopub.execute_input":"2025-09-22T04:12:43.714247Z","iopub.status.idle":"2025-09-22T04:15:26.707330Z","shell.execute_reply.started":"2025-09-22T04:12:43.714217Z","shell.execute_reply":"2025-09-22T04:15:26.705720Z"}},"outputs":[],"execution_count":null},{"id":"d78b16a4","cell_type":"code","source":"# Generate submission for test data\n# Uncomment and run this cell to generate a submission file\n\nprint(\"Generating submission...\")\ntry:\n    run_submit(\n        cfg=cfg,\n        prior_scope=\"mixed\",\n        eps=12.0,\n        use_windows=True,\n        min_len=10,\n        gap_close=5,\n        p_min=0.03,\n        cap=0.02\n    )\n    print(\"Submission generated successfully!\")\nexcept Exception as e:\n    print(f\"Submission generation failed: {e}\")\n    import traceback\n    traceback.print_exc()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-22T04:15:26.708093Z","iopub.status.idle":"2025-09-22T04:15:26.708504Z","shell.execute_reply.started":"2025-09-22T04:15:26.708258Z","shell.execute_reply":"2025-09-22T04:15:26.708272Z"}},"outputs":[],"execution_count":null}]}