{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":38760,"databundleVersionId":4493939}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# OTTO Recommender System – Strong Co-Visitation Baseline\nimport numpy as np\nimport pandas as pd\nfrom collections import defaultdict, Counter\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nimport json, gc, time, csv, sys\n\n# Config\nDATA_DIR   = Path(\"/kaggle/input/competitions/otto-recommender-system\")\nTRAIN_PATH = DATA_DIR / \"train.jsonl\"\nTEST_PATH  = DATA_DIR / \"test.jsonl\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:19:28.047664Z","iopub.execute_input":"2026-03-17T09:19:28.047946Z","iopub.status.idle":"2026-03-17T09:19:28.434980Z","shell.execute_reply.started":"2026-03-17T09:19:28.047919Z","shell.execute_reply":"2026-03-17T09:19:28.434350Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N_KEEP     = 40               # how many top candidates from covis we keep per aid\nN_FINAL    = 20               # final prediction length\n\n# Weights how much each covis matrix contributes to each target\n#               clicks    carts     orders\nW_CLICK2CLICK = [1.00,    0.40,     0.10 ]\nW_BUY2BUY     = [0.15,    1.00,     1.40 ]\nW_CLICK2BUY   = [0.25,    0.80,     1.10 ]\n\n# Recency boost for items inside the session (most recent = highest boost)\nRECENCY_BOOST = [1.0, 0.8, 0.6, 0.45, 0.3, 0.2, 0.15, 0.1] + [0.07]*20\n\n# Popularity top-up\nPOP_TOPN      = 40\n\nprint(\"Config:\", {k:v for k,v in globals().items() if k.isupper() and \"_\" not in k[:2]})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:19:28.436302Z","iopub.execute_input":"2026-03-17T09:19:28.436667Z","iopub.status.idle":"2026-03-17T09:19:28.441956Z","shell.execute_reply.started":"2026-03-17T09:19:28.436639Z","shell.execute_reply":"2026-03-17T09:19:28.441303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_session(line):\n    d = json.loads(line)\n    return int(d[\"session\"]), [(int(e[\"aid\"]), int(e[\"ts\"]), e[\"type\"]) for e in d[\"events\"]]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:19:28.442498Z","iopub.execute_input":"2026-03-17T09:19:28.442705Z","iopub.status.idle":"2026-03-17T09:19:28.458699Z","shell.execute_reply.started":"2026-03-17T09:19:28.442688Z","shell.execute_reply":"2026-03-17T09:19:28.457834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_covis_matrices(path, max_lines=None):\n    t0 = time.time()\n\n    type2int = {\"clicks\":0, \"carts\":1, \"orders\":2}\n    int2type = {v:k for k,v in type2int.items()}\n\n    click2click = defaultdict(Counter)\n    buy2buy     = defaultdict(Counter)\n    click2buy   = defaultdict(Counter)   # from click to later cart/order\n\n    popular     = Counter()              # global popularity (mostly for fallback)\n\n    n = 0\n    with open(path, \"r\") as f:\n        for line in tqdm(f, desc=\"Build covis\", total=12_887_413 if \"train\" in str(path) else 1_671_803):\n            sid, events = load_session(line)\n            if not events: continue\n\n            # take tail of long sessions\n            events = events[-20:]\n\n            aids = [aid for aid,_,_ in events]\n            types = [type2int[t] for _,_,t in events]\n\n            # global popularity\n            for a in aids:\n                popular[a] += 1\n\n            for i in range(len(events)):\n                if types[i] != 0: continue\n                a = aids[i]\n                for j in range(i+1, min(i+35, len(events))):\n                    if types[j] != 0: continue\n                    b = aids[j]\n                    click2click[a][b] += 1\n                    click2click[b][a] += 1   # symmetric\n\n            buy_aids = set()\n            for aid, typ in zip(aids, types):\n                if typ >= 1:\n                    buy_aids.add(aid)\n\n            buy_list = list(buy_aids)\n            for i in range(len(buy_list)):\n                for j in range(i+1, len(buy_list)):\n                    a, b = buy_list[i], buy_list[j]\n                    buy2buy[a][b] += 1\n                    buy2buy[b][a] += 1\n\n            for i in range(len(events)):\n                if types[i] != 0: continue\n                a = aids[i]\n                for j in range(i+1, len(events)):\n                    if types[j] == 0: continue\n                    b = aids[j]\n                    click2buy[a][b] += 1\n\n            n += 1\n            if max_lines is not None and n >= max_lines:\n                break\n\n    # Keep only top N neighbors\n    for d in [click2click, buy2buy, click2buy]:\n        for aid in list(d):\n            if len(d[aid]) > N_KEEP*2:\n                top = d[aid].most_common(N_KEEP*2)\n                d[aid] = Counter(dict(top))\n\n    print(f\"Built {n:,} sessions in {time.time()-t0:.1f} s\")\n    print(\"click2click aids:\", len(click2click))\n    print(\"buy2buy     aids:\", len(buy2buy))\n    print(\"click2buy   aids:\", len(click2buy))\n    print(\"Popular items  :\", len(popular))\n\n    return click2click, buy2buy, click2buy, popular.most_common(POP_TOPN)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:19:28.459874Z","iopub.execute_input":"2026-03-17T09:19:28.460086Z","iopub.status.idle":"2026-03-17T09:19:28.474008Z","shell.execute_reply.started":"2026-03-17T09:19:28.460064Z","shell.execute_reply":"2026-03-17T09:19:28.473292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nBuilding matrices from train...\")\nclick2click, buy2buy, click2buy, pop_items = build_covis_matrices(TRAIN_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:19:28.475652Z","iopub.execute_input":"2026-03-17T09:19:28.475891Z","iopub.status.idle":"2026-03-17T09:39:25.627074Z","shell.execute_reply.started":"2026-03-17T09:19:28.475871Z","shell.execute_reply":"2026-03-17T09:39:25.626281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def recommend(session_events, target_type):\n    # target_type: \"clicks\", \"carts\", \"orders\"\n\n    if target_type == \"clicks\":    tgt = 0\n    elif target_type == \"carts\":   tgt = 1\n    else:                          tgt = 2\n\n    w_c2c = W_CLICK2CLICK[tgt]\n    w_b2b = W_BUY2BUY[tgt]\n    w_c2b = W_CLICK2BUY[tgt]\n\n    aids = [aid for aid,_,typ in session_events if typ == \"clicks\" or tgt > 0]\n    if not aids:\n        return [aid for aid,_ in pop_items][:N_FINAL]\n\n    # reverse = most recent first\n    aids = aids[::-1]\n    unique_recent = []\n    seen = set()\n    for a in aids:\n        if a not in seen:\n            unique_recent.append(a)\n            seen.add(a)\n        if len(unique_recent) >= 35: break\n\n    scores = Counter()\n\n    # recency bias\n    for pos, a in enumerate(unique_recent):\n        boost = RECENCY_BOOST[min(pos, len(RECENCY_BOOST)-1)]\n        scores[a] += boost * 2.5   # self-boost\n\n        # click2click neighbors\n        for nb, cnt in click2click[a].items():\n            scores[nb] += cnt * w_c2c\n\n        # buy2buy neighbors\n        for nb, cnt in buy2buy[a].items():\n            scores[nb] += cnt * w_b2b\n\n        # click2buy neighbors\n        for nb, cnt in click2buy[a].items():\n            scores[nb] += cnt * w_c2b\n\n    # remove already seen\n    for a in unique_recent:\n        scores[a] = -9999\n\n    # top candidates\n    cands = [aid for aid,sc in scores.most_common(N_KEEP*3)]\n\n    # fill with pure popularity if needed\n    rec = []\n    used = set(unique_recent)\n\n    for a in cands:\n        if a not in used:\n            rec.append(a)\n            used.add(a)\n        if len(rec) >= N_FINAL: break\n\n    for aid, _ in pop_items:\n        if aid not in used:\n            rec.append(aid)\n            used.add(aid)\n        if len(rec) >= N_FINAL: break\n\n    return rec[:N_FINAL]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:39:25.628261Z","iopub.execute_input":"2026-03-17T09:39:25.628506Z","iopub.status.idle":"2026-03-17T09:39:29.831827Z","shell.execute_reply.started":"2026-03-17T09:39:25.628484Z","shell.execute_reply":"2026-03-17T09:39:29.830783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Submit\nwith open(TEST_PATH, \"r\") as f, open(\"submission.csv\", \"w\", newline=\"\") as out:\n    writer = csv.writer(out)\n    writer.writerow([\"session_type\", \"labels\"])\n\n    for line in tqdm(f, desc=\"Inference\", total=1_671_803):\n        sid, events = load_session(line)\n\n        for target in [\"clicks\", \"carts\", \"orders\"]:\n            preds = recommend(events, target)\n            labels_str = \" \".join(map(str, preds))\n            writer.writerow([f\"{sid}_{target}\", labels_str])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:39:29.832672Z","iopub.execute_input":"2026-03-17T09:39:29.832898Z","iopub.status.idle":"2026-03-17T10:04:01.097490Z","shell.execute_reply.started":"2026-03-17T09:39:29.832865Z","shell.execute_reply":"2026-03-17T10:04:01.096817Z"}},"outputs":[],"execution_count":null}]}