{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"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":116062,"databundleVersionId":14875579,"sourceType":"competition"},{"sourceId":13680623,"sourceType":"datasetVersion","datasetId":8699749},{"sourceId":14073266,"sourceType":"datasetVersion","datasetId":8916743},{"sourceId":14114790,"sourceType":"datasetVersion","datasetId":8917158},{"sourceId":14114804,"sourceType":"datasetVersion","datasetId":8917141},{"sourceId":14152266,"sourceType":"datasetVersion","datasetId":8908959},{"sourceId":14169699,"sourceType":"datasetVersion","datasetId":8919436}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# !pip install obonet networkx --quiet\n\n# import obonet\n# import networkx as nx\n# import pandas as pd\n# import numpy as np\n# import pickle\n# from collections import defaultdict, deque\n# from tqdm import tqdm\n\n# # ============================================================================\n# # 1. CẤU HÌNH ĐƯỜNG DẪN (CHECK KỸ)\n# # ============================================================================\n# CONFIG = {\n#     # File OBO gốc\n#     'OBO_FILE': \"/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo\",\n    \n#     'VOCAB_FILE': \"/kaggle/input/c99-cafa6/vocab_C99_remove.csv\",\n#     'TARGET_FILE': \"/kaggle/input/c99-cafa6/train_targets_C99.pkl\",\n    \n#     # File đầu ra\n#     'OUTPUT_PKL': \"hierarchy_metadata.pkl\"\n# }\n\n# def build_metadata_v2_final():\n#     print(\"🚀 BẮT ĐẦU TẠO METADATA (FULL & CORRECTED)...\")\n    \n#     # --- A. LOAD DỮ LIỆU ---\n#     print(\"\\n[1/4] Loading Graph & Vocab...\")\n#     graph = obonet.read_obo(CONFIG['OBO_FILE'])\n#     vocab_df = pd.read_csv(CONFIG['VOCAB_FILE'])\n#     vocab_terms = vocab_df['term'].tolist()\n#     vocab_size = len(vocab_terms)\n#     term_to_idx = {t: i for i, t in enumerate(vocab_terms)}\n    \n#     print(f\"   - Graph nodes: {len(graph):,}\")\n#     print(f\"   - Vocab size: {vocab_size:,}\")\n\n#     # --- B. TÍNH DEPTH NORM (BFS) ---\n#     print(\"\\n[2/4] Calculating Depth Norm...\")\n#     # (Phần này code cũ đã đúng, giữ nguyên logic BFS)\n#     roots = [\"GO:0008150\", \"GO:0003674\", \"GO:0005575\"]\n#     depth_dict = {node: float('inf') for node in graph.nodes()}\n#     queue = deque()\n    \n#     for root in roots:\n#         if root in graph:\n#             depth_dict[root] = 0\n#             queue.append(root)\n            \n#     while queue:\n#         node = queue.popleft()\n#         d = depth_dict[node]\n#         # Predecessors = CON (Edge đi từ con -> cha)\n#         for child in graph.predecessors(node):\n#             if depth_dict[child] > d + 1:\n#                 depth_dict[child] = d + 1\n#                 queue.append(child)\n                \n#     depth_arr = np.array([depth_dict.get(t, 0) for t in vocab_terms], dtype=np.float32)\n#     depth_arr[depth_arr == float('inf')] = 0.0\n    \n#     if depth_arr.max() > 0:\n#         depth_norm = depth_arr / depth_arr.max()\n#     else:\n#         depth_norm = depth_arr\n#     print(f\"   - Max Depth: {depth_arr.max()}\")\n\n#     # --- C. TÍNH PARENT MAP (TRANSITIVE CLOSURE) - [NÂNG CẤP] ---\n#     print(\"\\n[3/4] Building Parent Map (Transitive - Fix Broken Chains)...\")\n    \n#     # 1. Xây dựng đồ thị 'is_a' toàn vẹn từ file OBO gốc\n#     # (Để tìm đường đi ngay cả khi node trung gian bị cắt)\n#     full_isa_graph = nx.DiGraph()\n    \n#     print(\"   - Building full is_a graph...\")\n#     for node, data in graph.nodes(data=True):\n#         if \"is_a\" in data:\n#             for p_str in data[\"is_a\"]:\n#                 p_id = p_str.split(\" ! \")[0]\n#                 # Thêm cạnh Con -> Cha\n#                 full_isa_graph.add_edge(node, p_id)\n                \n#     # 2. Map Con -> Tất cả Tổ tiên (Ancestors) có trong Vocab\n#     child_to_parent = defaultdict(list)\n#     count_edges = 0\n#     vocab_set = set(vocab_terms) # Để tra cứu nhanh\n    \n#     for term, idx in tqdm(term_to_idx.items(), desc=\"Mapping Transitive\"):\n#         if term not in full_isa_graph: continue\n        \n#         # Tìm tất cả tổ tiên trong đồ thị gốc (bao gồm cả cha, ông, cụ...)\n#         # nx.descendants trong DiGraph(Con->Cha) sẽ trả về tất cả Ancestors\n#         try:\n#             all_ancestors = nx.descendants(full_isa_graph, term)\n#         except:\n#             continue # Phòng trường hợp lỗi graph\n            \n#         # Chỉ giữ lại những tổ tiên CÓ MẶT trong Vocab\n#         valid_ancestors = []\n#         for anc in all_ancestors:\n#             if anc in vocab_set:\n#                 p_idx = term_to_idx[anc]\n#                 valid_ancestors.append(p_idx)\n                \n#         if valid_ancestors:\n#             child_to_parent[idx] = valid_ancestors\n#             count_edges += len(valid_ancestors)\n\n#     print(f\"   - Mapped {count_edges:,} transitive relationships (Bridged gaps).\")\n\n#     # --- D. TÍNH IC NORM (INFORMATION CONTENT) - [SỬA QUAN TRỌNG] ---\n#     print(\"\\n[4/4] Calculating Information Content (IC)...\")\n    \n#     # Load Targets\n#     with open(CONFIG['TARGET_FILE'], 'rb') as f:\n#         labels_dict = pickle.load(f)\n    \n#     # Đếm tần suất\n#     term_counts = np.zeros(vocab_size, dtype=np.float32)\n#     total_samples = len(labels_dict)\n    \n#     for indices in labels_dict.values():\n#         if len(indices) > 0:\n#             term_counts[indices] += 1\n            \n#     # Tính Frequency (Cộng epsilon nhỏ)\n#     freq = (term_counts + 1e-9) / total_samples\n    \n#     # IC = -log(Freq)\n#     ic_values = -np.log(freq)\n    \n#     # Normalize IC [0, 1]\n#     ic_max = ic_values.max()\n#     if ic_max > 0:\n#         ic_norm = ic_values / ic_max\n#     else:\n#         ic_norm = np.zeros_like(ic_values)\n        \n#     print(f\"   - IC Max Value: {ic_max:.4f}\")\n#     print(f\"   - IC Norm Shape: {ic_norm.shape}\")\n\n#     # --- E. LƯU FILE ---\n#     print(f\"\\n>>> Saving to {CONFIG['OUTPUT_PKL']}...\")\n    \n#     save_data = {\n#         'depth_norm': depth_norm,\n#         'ic_norm': ic_norm,          \n#         'child_to_parent': dict(child_to_parent), # Đã fix logic is_a\n#         'term_to_idx': term_to_idx\n#     }\n    \n#     with open(CONFIG['OUTPUT_PKL'], 'wb') as f:\n#         pickle.dump(save_data, f)\n        \n#     print(\"✅ DONE! Metadata V2 (Final) Created Successfully.\")\n\n# if __name__ == \"__main__\":\n#     build_metadata_v2_final()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:03:52.053840Z","iopub.execute_input":"2025-12-17T05:03:52.054105Z","iopub.status.idle":"2025-12-17T05:03:52.064580Z","shell.execute_reply.started":"2025-12-17T05:03:52.054079Z","shell.execute_reply":"2025-12-17T05:03:52.063628Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q gdown\n!gdown 1jG2P6dxeH_By7_zc8ioiKcTLP-cIokff","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:03:52.066405Z","iopub.execute_input":"2025-12-17T05:03:52.066796Z","iopub.status.idle":"2025-12-17T05:04:20.376220Z","shell.execute_reply.started":"2025-12-17T05:03:52.066774Z","shell.execute_reply":"2025-12-17T05:04:20.374989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n# import matplotlib.pyplot as plt\n\n# # --- NÊN ĐỔI TÊN FILE CHO ĐÚNG ---\n# # base_path   = \"/kaggle/input/sample-submission/0.275_submission.tsv\"\n# # boost350    = \"/kaggle/input/sample-submission/0.229_submission.tsv\"\n# # boost50     = \"/kaggle/input/sample-submission/0.233_submission.tsv\"\n# # low     = \"/kaggle/input/sample-submission/0.266_submission.tsv\"\n\n# submit = \"/kaggle/working/submission.tsv\"\n\n# def load_sub(path):\n#     return pd.read_csv(path, sep='\\t', names=['protein','term','score'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:04:20.377483Z","iopub.execute_input":"2025-12-17T05:04:20.377843Z","iopub.status.idle":"2025-12-17T05:04:20.383030Z","shell.execute_reply.started":"2025-12-17T05:04:20.377791Z","shell.execute_reply":"2025-12-17T05:04:20.381840Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n# import matplotlib.pyplot as plt\n# import seaborn as sns\n\n# # File bạn vừa tạo\n# SUBMISSION_FILE = \"submission_goa_mlp.tsv\"\n\n# def analyze_threshold_impact():\n#     print(f\"📂 Đang đọc file {SUBMISSION_FILE}...\")\n#     # Đọc file (cột: Protein, Term, Score)\n#     df = pd.read_csv(SUBMISSION_FILE, sep='\\t', names=['Protein', 'Term', 'Score'])\n    \n#     total_rows = len(df)\n#     total_proteins = df['Protein'].nunique()\n    \n#     print(f\"✅ Đã tải xong!\")\n#     print(f\"   - Tổng dòng hiện tại: {total_rows:,}\")\n#     print(f\"   - Tổng Protein: {total_proteins:,}\")\n#     print(f\"   - Trung bình nhãn/Protein (Hiện tại): {total_rows / total_proteins:.1f}\")\n    \n#     # ✅ THỐNG KÊ MIN / MAX LABELS PER PROTEIN\n#     labels_per_protein = df.groupby('Protein').size()\n#     min_labels = labels_per_protein.min()\n#     max_labels = labels_per_protein.max()\n    \n#     print(f\"\\n📌 THỐNG KÊ LABELS / PROTEIN:\")\n#     print(f\"   - Min labels / protein : {min_labels}\")\n#     print(f\"   - Max labels / protein : {max_labels}\")\n    \n#     # --- THỐNG KÊ NGƯỠNG GIẢ LẬP ---\n#     print(\"\\n📊 NẾU TĂNG THRESHOLD THÌ SAO?\")\n#     print(\"-\" * 50)\n#     print(f\"{'Threshold':<10} | {'Số dòng còn lại':<15} | {'Nhãn/Protein':<15} | {'% Giữ lại':<10}\")\n#     print(\"-\" * 50)\n    \n#     # Các mốc threshold muốn thử\n#     thresholds = [0.01, 0.05, 0.08, 0.10, 0.12, 0.15, 0.25, 0.35, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]\n    \n#     for thr in thresholds:\n#         # Lọc giả lập\n#         filtered_count = (df['Score'] >= thr).sum()\n#         avg_labels = filtered_count / total_proteins\n#         percent_kept = (filtered_count / total_rows) * 100\n        \n#         print(f\"{thr:<10.2f} | {filtered_count:<15,} | {avg_labels:<15.1f} | {percent_kept:<9.1f}%\")\n        \n#     print(\"-\" * 50)\n    \n#     # --- VẼ BIỂU ĐỒ PHÂN BỐ SCORE ---\n#     plt.figure(figsize=(10, 5))\n#     sns.histplot(df['Score'], bins=100, color='teal', kde=False)\n#     plt.axvline(0.08, color='red', linestyle='--', label='Current Threshold (0.08)')\n#     plt.title(f'Phân bố điểm số trong file {SUBMISSION_FILE}')\n#     plt.xlabel('Score')\n#     plt.ylabel('Số lượng')\n#     plt.legend()\n#     plt.yscale('log') # Dùng log scale để nhìn rõ vùng thấp\n#     plt.show()\n\n# analyze_threshold_impact()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T01:18:15.072641Z","iopub.execute_input":"2025-12-18T01:18:15.073040Z","iopub.status.idle":"2025-12-18T01:18:47.070976Z","shell.execute_reply.started":"2025-12-18T01:18:15.073014Z","shell.execute_reply":"2025-12-18T01:18:47.070005Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n\n# # ========= FILE PATH =========\n# submission_path = \"submission.tsv\"\n# ia_path = \"/kaggle/input/cafa-6-protein-function-prediction/IA.tsv\"\n\n# # ========= ROOT TERMS (BỎ KHỎI THỐNG KÊ) =========\n# ROOT_TERMS = {\"GO:0003674\", \"GO:0005575\", \"GO:0008150\"}\n\n# # ========= LOAD IA =========\n# df_ia = pd.read_csv(\n#     ia_path,\n#     sep=\"\\t\",\n#     header=None,\n#     names=[\"go_term\", \"ia\"]\n# )\n# ia_map = dict(zip(df_ia[\"go_term\"], df_ia[\"ia\"]))\n# print(\"✅ Số GO term có IA:\", len(ia_map))\n\n# # ========= IA BUCKET =========\n# ia_labels = [\"IA = 0\", \"0 < IA < 1\", \"1 → 2\", \"2 → 4\", \"≥ 4\"]\n\n# # ========= SCORE BUCKET =========\n# score_bins = [(i/10, (i+1)/10) for i in range(0, 10)]\n# score_bins.append((\"==1.0\", \"==1.0\"))  # ✅ bucket đặc biệt\n\n# results = {b: {k: 0 for k in ia_labels} for b in score_bins}\n# totals = {b: 0 for b in score_bins}\n\n# missing_ia = 0\n# skipped_root = 0\n\n# # ========= STREAM SUBMISSION =========\n# for chunk in pd.read_csv(\n#     submission_path,\n#     sep=\"\\t\",\n#     header=None,\n#     names=[\"protein_id\", \"go_term\", \"score\"],\n#     chunksize=2_000_000,\n#     engine=\"python\",          # ✅ BẮT BUỘC\n#     on_bad_lines=\"skip\"       # ✅ TỰ ĐỘNG BỎ DÒNG LỖI\n# ):\n#     for row in chunk.itertuples(index=False):\n#         pid, go, sc = row\n\n#         # ✅ BỎ ROOT\n#         if go in ROOT_TERMS:\n#             skipped_root += 1\n#             continue\n\n#         ia = ia_map.get(go, None)\n#         if ia is None:\n#             missing_ia += 1\n#             continue\n\n#         # ========= XÁC ĐỊNH SCORE BUCKET =========\n#         if sc == 1.0:\n#             sb = (\"==1.0\", \"==1.0\")\n#         else:\n#             sb = None\n#             for (s_lo, s_hi) in score_bins[:-1]:\n#                 if s_lo <= sc < s_hi:\n#                     sb = (s_lo, s_hi)\n#                     break\n\n#             if sb is None:\n#                 continue\n\n#         # ========= IA BUCKET LOGIC =========\n#         if ia == 0:\n#             results[sb][\"IA = 0\"] += 1\n#         elif 0 < ia < 1:\n#             results[sb][\"0 < IA < 1\"] += 1\n#         elif 1 <= ia < 2:\n#             results[sb][\"1 → 2\"] += 1\n#         elif 2 <= ia < 4:\n#             results[sb][\"2 → 4\"] += 1\n#         else:\n#             results[sb][\"≥ 4\"] += 1\n\n#         totals[sb] += 1\n\n# # ========= IN KẾT QUẢ =========\n# for b in score_bins:\n#     if b == (\"==1.0\", \"==1.0\"):\n#         print(f\"\\n🔥 PHÂN BỐ IA (score == 1.0) [ĐÃ BỎ ROOT]:\")\n#     else:\n#         print(f\"\\n📊 PHÂN BỐ IA ({b[0]:.1f} → {b[1]:.1f}) [ĐÃ BỎ ROOT]:\")\n\n#     total = totals[b]\n#     if total == 0:\n#         print(\"  (Trống)\")\n#         continue\n\n#     for k in ia_labels:\n#         cnt = results[b][k]\n#         pct = cnt / total * 100\n#         print(f\"{k:6s}: {cnt:>10,}  |  {pct:6.2f} %\")\n\n# print(\"\\n⚠️ Số dòng bị thiếu IA:\", missing_ia)\n# print(\"🚫 Số dòng bị loại vì là ROOT:\", skipped_root)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:04:20.406937Z","iopub.execute_input":"2025-12-17T05:04:20.407291Z","iopub.status.idle":"2025-12-17T05:04:20.428683Z","shell.execute_reply.started":"2025-12-17T05:04:20.407254Z","shell.execute_reply":"2025-12-17T05:04:20.427619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import numpy as np\n# from tqdm import tqdm\n# import os\n# from collections import defaultdict\n\n# # =========================================================\n# # CẤU HÌNH & THAM SỐ\n# # =========================================================\n# SUB_FILE = \"submission.tsv\"\n# IA_FILE  = \"/kaggle/input/cafa-6-protein-function-prediction/IA.tsv\"\n# CHUNK_SIZE = 5_000_000 \n# ROOT_TERMS = [\"GO:0003674\", \"GO:0005575\", \"GO:0008150\"]\n# SCORE_BINS = np.linspace(0.0, 1.0, 11) # Tạo 11 mốc: 0.0, 0.1, 0.2, ..., 1.0\n\n# def analyze_ia_extremes_fixed():\n#     # 1. Load submission (Phát hiện cột)\n#     print(\"1. Reading submission to identify columns...\")\n#     df_head = pd.read_csv(SUB_FILE, sep=None, engine=\"python\", header=None, nrows=1000)\n\n#     go_col, score_col = None, None\n#     for col in df_head.columns:\n#         sample = df_head[col].astype(str)\n#         if sample.str.startswith(\"GO:\").any(): go_col = col\n#         if pd.to_numeric(sample, errors=\"coerce\").notna().mean() > 0.9: score_col = col\n\n#     if go_col is None or score_col is None:\n#         raise ValueError(\"Không tìm thấy cột GO-term hoặc Score. Kiểm tra lại SUB_FILE.\")\n\n#     # 2. Load IA Map\n#     ia_df = pd.read_csv(IA_FILE, sep=\"\\t\", header=None, names=[\"go\", \"ia\"])\n#     ia_map = dict(zip(ia_df.go, ia_df.ia))\n#     print(f\"2. IA Map Loaded ({len(ia_map):,} terms).\")\n\n#     # 3. PHÂN TÍCH STREAMING\n#     ia_bin_stats = defaultdict(lambda: {'total_ia': 0.0, 'count': 0})\n\n#     print(\"\\n3. Starting Streaming Analysis (Binning Mean IA)...\")\n#     reader = pd.read_csv(SUB_FILE, sep=None, engine=\"python\", header=None, chunksize=CHUNK_SIZE)\n\n#     for chunk in tqdm(reader, desc=\"Processing Chunks\"):\n#         # Đổi tên cột\n#         df_chunk = chunk.rename(columns={go_col: \"go\", score_col: \"score\"})\n        \n#         # Lọc 3 nhãn gốc\n#         df_chunk = df_chunk[~df_chunk['go'].isin(ROOT_TERMS)].copy()\n        \n#         # Gán IA (Fill NaN = 0 cho an toàn)\n#         df_chunk[\"ia\"] = df_chunk[\"go\"].map(ia_map).fillna(0.0)\n        \n#         # Lọc điểm số hợp lệ\n#         df_chunk = df_chunk[(df_chunk['score'] >= 0) & (df_chunk['score'] <= 1.0)].copy()\n\n#         # Tính toán BINS\n#         df_chunk['bin_index'] = np.digitize(df_chunk['score'], bins=SCORE_BINS) - 1 # Đổi về index 0-9\n        \n#         # Nhóm theo index bin và tính tổng IA và Count\n#         chunk_stats = df_chunk.groupby('bin_index').agg(\n#             total_ia=('ia', 'sum'),\n#             count=('ia', 'count')\n#         ).reset_index()\n\n#         # Cập nhật global stats\n#         for _, row in chunk_stats.iterrows():\n#             # [FIX LỖI] Ép kiểu về int tiêu chuẩn\n#             bin_idx = int(row['bin_index']) \n            \n#             # Kiểm tra lỗi ngoài biên (do np.digitize đôi khi trả về 10)\n#             if bin_idx >= len(SCORE_BINS) - 1 or bin_idx < 0:\n#                 continue \n            \n#             bin_min = SCORE_BINS[bin_idx]\n#             bin_max = SCORE_BINS[bin_idx + 1]\n            \n#             key = (bin_min, bin_max)\n#             ia_bin_stats[key]['total_ia'] += row['total_ia']\n#             ia_bin_stats[key]['count'] += row['count']\n\n\n#     # --- KẾT QUẢ CUỐI CÙNG ---\n#     print(\"\\n\" + \"=\"*70)\n#     print(f\"📊 PHÂN TÍCH MEAN IA THEO KHOẢNG ĐIỂM SỐ\")\n#     print(\"=\"*70)\n    \n#     total_all_counts = sum(s['count'] for s in ia_bin_stats.values())\n    \n#     print(f\"{'Score Range':<15} | {'Mean IA':<10} | {'Total Count':<15} | {'Density (%)':<10}\")\n#     print(\"-\" * 70)\n\n#     for (bin_min, bin_max), stats in sorted(ia_bin_stats.items()):\n#         count = stats['count']\n#         mean_ia = stats['total_ia'] / count if count > 0 else 0.0\n#         density = (count / total_all_counts) * 100 if total_all_counts > 0 else 0.0\n        \n#         range_str = f\"[{bin_min:.2f} - {bin_max:.2f})\"\n        \n#         print(f\"{range_str:<15} | {mean_ia:<10.4f} | {count:<15,} | {density:<9.1f}%\")\n\n#     print(\"=\"*70)\n\n# if __name__ == \"__main__\":\n#     analyze_ia_extremes_fixed()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:04:20.429714Z","iopub.execute_input":"2025-12-17T05:04:20.430042Z","iopub.status.idle":"2025-12-17T05:04:20.452873Z","shell.execute_reply.started":"2025-12-17T05:04:20.430013Z","shell.execute_reply":"2025-12-17T05:04:20.451637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import numpy as np\n# import pandas as pd\n# from torch.utils.data import Dataset, DataLoader\n# from tqdm import tqdm\n# import pickle\n# from collections import OrderedDict\n# from torch.cuda.amp import autocast\n\n# # =========================================================\n# # CẤU HÌNH DUMP (FAST)\n# # =========================================================\n# CONFIG = {\n#     'MODEL1_PATH': \"/kaggle/input/model-cafa6/best_model_wide_0.63.pth\",     \n#     'MODEL2_PATH': \"/kaggle/input/model-cafa6/model_v3_hmc_warmup.pth\",  \n#     'MODEL3_PATH': \"/kaggle/input/model-cafa6/best_model_c99_wide.pth\",  \n    \n#     'EMBED_DIR': \"/kaggle/input/cafa6-embeds\",\n#     'VOCAB_C95': \"/kaggle/input/c95-cafa6/vocab_C95_remove.csv\",\n#     'VOCAB_C99': \"/kaggle/input/c99-cafa6/vocab_C99_remove.csv\",\n#     'IA_FILE': \"/kaggle/input/cafa-6-protein-function-prediction/IA.tsv\",\n    \n#     # --- GATING CONDITIONS ---\n#     'C99_CONFIDENCE_MIN': 0.30,  # C99 phải tự tin > 0.30 mới được xét duyệt\n#     'C99_DELTA_MIN': 0.05,       # C99 phải lớn hơn C95 ít nhất 0.05\n\n#     # --- BASE WEIGHTS (BACKBONE) ---\n#     'BASE_W1': 0.6,\n#     'BASE_W2': 0.1,\n#     'BASE_W3': 0.3,\n    \n#     # --- DUMP CONFIG ---\n#     'DUMP_THRESHOLD': 0.01,      # Vẫn xả cổng thấp để Phase 2 có dữ liệu làm việc\n    \n#     'device': 'cuda', 'batch_size': 128, 'num_workers': 4\n# }\n\n# # (Giữ nguyên Class Model, Dataset, Load Model - Copy lại nếu cần)\n# class WideProteinMLP(nn.Module):\n#     def __init__(self, input_dim, num_classes, hidden_dims=[2048, 4096], dropout=0.3):\n#         super().__init__()\n#         layers = [nn.LayerNorm(input_dim)]\n#         prev = input_dim\n#         for h in hidden_dims:\n#             layers += [nn.Linear(prev, h), nn.GELU(), nn.Dropout(dropout)]\n#             prev = h\n#         layers.append(nn.Linear(prev, num_classes))\n#         self.net = nn.Sequential(*layers)\n#     def forward(self, x): return self.net(x)\n\n# class TestDataset(Dataset):\n#     def __init__(self, embed_dir):\n#         with open(os.path.join(embed_dir, \"test_ids.txt\")) as f: self.ids = [line.strip() for line in f]\n#         self.embed_matrix = np.load(os.path.join(embed_dir, \"test_embeds.npy\"), mmap_mode=\"r\")\n#     def __len__(self): return len(self.ids)\n#     def __getitem__(self, idx):\n#         return torch.from_numpy(self.embed_matrix[idx].copy()).float(), self.ids[idx]\n\n# def load_and_clean_model(path, num_classes, device):\n#     m = WideProteinMLP(1280, num_classes).to(device)\n#     try:\n#         ckpt = torch.load(path, map_location='cpu')\n#         sd = ckpt['model_state'] if isinstance(ckpt, dict) and 'model_state' in ckpt else ckpt\n#         new_sd = OrderedDict()\n#         for k, v in sd.items():\n#             if k.startswith('module.'): new_sd[k[7:]] = v\n#             else: new_sd[k] = v\n#         m.load_state_dict(new_sd)\n#         m.eval()\n#         return m\n#     except Exception as e:\n#         print(f\"❌ Error: {e}\")\n#         return None\n\n# def run_multi_tier_dump_fixed():\n#     print(\"🚀 PHASE 1: MULTI-TIER IA GATING DUMP (FIXED LOGIC)...\")\n#     # Cấu hình Dump Top-K\n#     DUMP_K = 500 \n#     device = CONFIG['device']\n    \n#     # 1. Load Vocabs & Map\n#     df_c95 = pd.read_csv(CONFIG['VOCAB_C95'])\n#     df_c99 = pd.read_csv(CONFIG['VOCAB_C99'])\n#     c95_terms = df_c95['term'].tolist()\n#     c99_terms = np.array(df_c99['term'].tolist())\n    \n#     c99_term_to_idx = {t: i for i, t in enumerate(c99_terms)}\n#     c95_to_c99_indices = []\n#     valid_c95_indices = []\n#     for i, t in enumerate(c95_terms):\n#         if t in c99_term_to_idx:\n#             c95_to_c99_indices.append(c99_term_to_idx[t])\n#             valid_c95_indices.append(i)\n#     c95_to_c99_indices = torch.tensor(c95_to_c99_indices, device=device, dtype=torch.long)\n#     valid_c95_indices = torch.tensor(valid_c95_indices, device=device, dtype=torch.long)\n    \n#     # IA Vector (GPU)\n#     try:\n#         ia_df = pd.read_csv(CONFIG['IA_FILE'], sep='\\t', names=['term', 'ia'], header=None)\n#         ia_map = dict(zip(ia_df.term, ia_df.ia))\n#         ia_vector = np.array([ia_map.get(t, 0.0) for t in c99_terms], dtype=np.float32)\n#         ia_tensor = torch.tensor(ia_vector, device=device)\n#         print(\"✅ IA Vector Loaded on GPU.\")\n#     except:\n#         ia_tensor = torch.zeros(len(c99_terms), device=device)\n\n#     # 2. Load Models\n#     m1 = load_and_clean_model(CONFIG['MODEL1_PATH'], len(c95_terms), device)\n#     m2 = load_and_clean_model(CONFIG['MODEL2_PATH'], len(c95_terms), device)\n#     m3 = load_and_clean_model(CONFIG['MODEL3_PATH'], len(c99_terms), device)\n#     if torch.cuda.device_count() > 1:\n#         m1 = nn.DataParallel(m1); m2 = nn.DataParallel(m2); m3 = nn.DataParallel(m3)\n\n#     # 3. Inference Loop\n#     ds = TestDataset(CONFIG['EMBED_DIR'])\n#     dl = DataLoader(ds, batch_size=CONFIG['batch_size'], shuffle=False, num_workers=4)\n#     f_out = open(\"raw_predictions.tsv\", \"w\")\n\n    \n#     print(\">>> Streaming & Gating...\")\n#     with torch.no_grad():\n#         for features, prot_ids in tqdm(dl):\n#             features = features.to(device)\n#             with autocast():\n#                 p1_raw = torch.sigmoid(m1(features))\n#                 p2_raw = torch.sigmoid(m2(features))\n#                 p3 = torch.sigmoid(m3(features))\n            \n#             # Map C95 -> C99\n#             p1_mapped = torch.zeros_like(p3)\n#             p2_mapped = torch.zeros_like(p3)\n#             p1_mapped.index_add_(1, c95_to_c99_indices, p1_raw[:, valid_c95_indices])\n#             p2_mapped.index_add_(1, c95_to_c99_indices, p2_raw[:, valid_c95_indices])\n            \n#             # --- [LOGIC MULTI-TIER GATING - FIXED] ---\n            \n#             # 1. Base Probability (An toàn, dùng cho mọi trường hợp mặc định)\n#             # 0.6 M1 + 0.1 M2 + 0.3 M3\n#             base_prob = (CONFIG['BASE_W1'] * p1_mapped) + \\\n#                         (CONFIG['BASE_W2'] * p2_mapped) + \\\n#                         (CONFIG['BASE_W3'] * p3)\n            \n#             # 2. Define Masks theo IA (FIX BROADCASTING ERROR)\n#             # unsqueeze(0) để biến shape từ (15582,) thành (1, 15582) -> Khớp với batch\n#             mask_tier_1 = (ia_tensor >= 5.0).unsqueeze(0)                    \n#             mask_tier_2 = ((ia_tensor >= 4.0) & (ia_tensor < 5.0)).unsqueeze(0) \n            \n#             # (Tier 3 và 4 thực ra không cần tính Override nữa vì sẽ dùng Base, \n#             # nhưng để code đầy đủ ta cứ giữ logic tính toán nếu cần debug sau này)\n            \n#             # 3. Tính Probs cho High Tiers (Override Recipes)\n#             # Tier 1 (Super Rare): Tin C99 90%\n#             prob_t1 = (0.10 * p1_mapped) + (0.90 * p3)\n#             # Tier 2 (Rare): Tin C99 75%\n#             prob_t2 = (0.25 * p1_mapped) + (0.75 * p3)\n            \n#             # Gộp lại thành Override Prob\n#             # Lưu ý: Những chỗ không phải Tier 1/2 sẽ bằng 0, nhưng sẽ bị lọc bởi mask sau này\n#             override_prob = (mask_tier_1 * prob_t1) + (mask_tier_2 * prob_t2)\n            \n#             # 4. Điều kiện Kích hoạt \"Khóa An Toàn\" (FIX LOGIC)\n#             # Chỉ Override khi:\n#             # - C99 tự tin (>= 0.30)\n#             # - C99 vượt trội C95 (>= 0.05)\n#             # - VÀ QUAN TRỌNG: Chỉ áp dụng cho nhãn IA >= 4.0\n            \n#             confident_c99 = p3 >= CONFIG['C99_CONFIDENCE_MIN'] # 0.30\n#             better_c99 = p3 >= (p1_mapped + CONFIG['C99_DELTA_MIN']) # 0.05\n#             is_high_ia = (ia_tensor >= 4.0).unsqueeze(0) # Mask IA >= 4\n            \n#             # Điều kiện cuối cùng: Hội tụ đủ 3 yếu tố\n#             should_override = confident_c99 & better_c99 & is_high_ia\n            \n#             # 5. Quyết định cuối cùng\n#             # Nếu thỏa mãn điều kiện -> Dùng công thức Tier (Override)\n#             # Nếu không (hoặc là nhãn dễ) -> Dùng công thức Base (An toàn)\n#             final_probs = torch.where(should_override, override_prob, base_prob)\n            \n#             # --- DUMPING (GPU ACCELERATED) ---\n#             mask_low = final_probs < CONFIG['DUMP_THRESHOLD']\n#             final_probs.masked_fill_(mask_low, -1.0)\n            \n#             topk_vals, topk_inds = torch.topk(final_probs, k=DUMP_K, dim=1)\n            \n#             vals_np = topk_vals.float().cpu().numpy()\n#             inds_np = topk_inds.cpu().numpy()\n            \n#             # Buffer Write\n#             batch_lines = []\n#             for i, pid in enumerate(prot_ids):\n#                 for j in range(DUMP_K):\n#                     score = vals_np[i, j]\n#                     if score < CONFIG['DUMP_THRESHOLD']: continue\n                    \n#                     term = c99_terms[inds_np[i, j]]\n#                     batch_lines.append(f\"{pid}\\t{term}\\t{score:.4f}\\n\")\n            \n#             if batch_lines: f_out.write(\"\".join(batch_lines))\n\n#     f_out.close()\n#     print(\"✅ PHASE 1 COMPLETE! File: raw_predictions.tsv\")\n\n# if __name__ == \"__main__\":\n#     run_multi_tier_dump_fixed()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:04:20.453974Z","iopub.execute_input":"2025-12-17T05:04:20.454269Z","iopub.status.idle":"2025-12-17T05:04:20.477540Z","shell.execute_reply.started":"2025-12-17T05:04:20.454246Z","shell.execute_reply":"2025-12-17T05:04:20.476355Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !pip install -q gdown","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:04:20.478530Z","iopub.execute_input":"2025-12-17T05:04:20.478804Z","iopub.status.idle":"2025-12-17T05:04:24.469614Z","shell.execute_reply.started":"2025-12-17T05:04:20.478767Z","shell.execute_reply":"2025-12-17T05:04:24.468075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# !gdown --id 1Z2f8IEbZzzvT8Ys_I1diJP7O-XF1zte0 -O raw_predictions.tsv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:04:24.472234Z","iopub.execute_input":"2025-12-17T05:04:24.472837Z","iopub.status.idle":"2025-12-17T05:04:43.351852Z","shell.execute_reply.started":"2025-12-17T05:04:24.472806Z","shell.execute_reply":"2025-12-17T05:04:43.350875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n\n# SUB_FILE = \"submission.tsv\"  \n\n# df = pd.read_csv(\n#     SUB_FILE,\n#     sep=\"\\t\",\n#     header=None,\n#     names=[\"protein_id\", \"go_term\", \"score\"]\n# )\n\n# num_unique_go = df[\"go_term\"].nunique()\n# print(\" Số nhãn GO khác nhau trong submission:\", num_unique_go)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T01:40:23.347035Z","iopub.execute_input":"2025-12-18T01:40:23.347391Z","iopub.status.idle":"2025-12-18T01:40:36.755748Z","shell.execute_reply.started":"2025-12-18T01:40:23.347368Z","shell.execute_reply":"2025-12-18T01:40:36.754502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================\n# IMPORTS\n# =========================================================\nimport pandas as pd\nimport numpy as np\nimport os, gc\nfrom tqdm import tqdm\nfrom collections import defaultdict\n\n# =========================================================\n# CONFIG\n# =========================================================\nCONFIG = {\n    \"RAW_FILE\": \"/kaggle/input/sample-submission/raw_predictions_500_v2.tsv\",\n    \"GOA_FILE\": \"/kaggle/input/protein-go-annotations/goa_uniprot_all.csv\",\n    \"OBO_FILE\": \"/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo\",\n    \"VOCAB_FILE\": \"/kaggle/input/c99-cafa6/vocab_C99_remove.csv\",\n\n    \"OUTPUT_FILE\": \"submission.tsv\",\n\n    \"FINAL_THRESHOLD\": 0.01,\n    \"FINAL_CAP\": 50,\n\n    \"GOA_SCORE\": 1.0,\n    \"MLP_POWER\": 1.3,  \n}\n\n# =========================================================\n# UTILS\n# =========================================================\ndef noisy_or(a, b):\n    return 1.0 - (1.0 - a) * (1.0 - b)\n\ndef parse_obo_children(path):\n    children = defaultdict(set)\n    cur = None\n    with open(path, \"r\") as f:\n        for line in f:\n            line = line.strip()\n            if line == \"[Term]\":\n                cur = None\n            elif line.startswith(\"id: \"):\n                cur = line.split(\"id: \")[1]\n            elif line.startswith(\"is_a: \") and cur:\n                parent = line.split()[1]\n                children[parent].add(cur)\n            elif line.startswith(\"relationship: part_of \") and cur:\n                parent = line.split()[2]\n                children[parent].add(cur)\n    return children\n\ndef get_descendants(go, children):\n    out = set()\n    stack = [go]\n    while stack:\n        x = stack.pop()\n        for c in children.get(x, []):\n            if c not in out:\n                out.add(c)\n                stack.append(c)\n    return out\n\n# =========================================================\n# LOAD GOA (POS + NEG)\n# =========================================================\ndef load_goa(goa_path, children_map):\n    print(\"Loading GOA...\")\n\n    df = pd.read_csv(goa_path, usecols=[\"protein_id\",\"go_term\",\"qualifier\"], dtype=str)\n    df = df.drop_duplicates()\n\n    # --- NEGATIVES ---\n    neg_df = df[df[\"qualifier\"].str.contains(\"NOT\", na=False)]\n    neg_map = neg_df.groupby(\"protein_id\")[\"go_term\"].apply(list).to_dict()\n\n    neg_keys = set()\n    print(\"Propagating GOA negatives...\")\n    for pid, terms in tqdm(neg_map.items()):\n        for t in terms:\n            neg_keys.add(f\"{pid}_{t}\")\n            for d in get_descendants(t, children_map):\n                neg_keys.add(f\"{pid}_{d}\")\n\n    # --- POSITIVES ---\n    pos_df = df[~df[\"qualifier\"].str.contains(\"NOT\", na=False)]\n    pos_map = pos_df.groupby(\"protein_id\")[\"go_term\"].apply(set).to_dict()\n\n    print(f\"GOA positives: {sum(len(v) for v in pos_map.values())}\")\n    print(f\"GOA negatives: {len(neg_keys)}\")\n\n    del df, neg_df, pos_df\n    gc.collect()\n\n    return pos_map, neg_keys\n\n# =========================================================\n# MAIN PIPELINE\n# =========================================================\ndef run_goa_mlp_merge():\n\n    # --- Load vocab (CHỈ dùng cho MLP) ---\n    vocab = pd.read_csv(CONFIG[\"VOCAB_FILE\"])\n    vocab_set = set(vocab[\"term\"])\n\n    # --- Load OBO ---\n    children_map = parse_obo_children(CONFIG[\"OBO_FILE\"])\n\n    # --- Load GOA ---\n    goa_pos, goa_neg_keys = load_goa(CONFIG[\"GOA_FILE\"], children_map)\n\n    reader = pd.read_csv(\n        CONFIG[\"RAW_FILE\"],\n        sep=\"\\t\",\n        names=[\"PID\",\"Term\",\"Score\"],\n        chunksize=1_000_000,\n        dtype={\"PID\":str,\"Term\":str,\"Score\":float}\n    )\n\n    fout = open(CONFIG[\"OUTPUT_FILE\"], \"w\")\n\n    cur_pid = None\n    scores = {}   # go_term (str) -> score\n    total = 0\n\n    def flush(pid, score_map):\n        nonlocal total\n\n        # --- GOA injection (NO vocab filter) ---\n        if pid in goa_pos:\n            for go in goa_pos[pid]:\n                score_map[go] = noisy_or(\n                    score_map.get(go, 0.0),\n                    CONFIG[\"GOA_SCORE\"]\n                )\n\n        # --- Negative filter ---\n        for go in list(score_map.keys()):\n            if f\"{pid}_{go}\" in goa_neg_keys:\n                del score_map[go]\n\n        # --- Final filter ---\n        items = [(go,s) for go,s in score_map.items() if s >= CONFIG[\"FINAL_THRESHOLD\"]]\n        items.sort(key=lambda x: x[1], reverse=True)\n        items = items[:CONFIG[\"FINAL_CAP\"]]\n\n        for go,s in items:\n            fout.write(f\"{pid}\\t{go}\\t{s:.3f}\\n\")\n            total += 1\n\n    print(\"Processing MLP predictions...\")\n    for chunk in tqdm(reader):\n        for pid, term, score in zip(chunk.PID, chunk.Term, chunk.Score):\n\n            if term not in vocab_set:\n                continue\n\n            if pid != cur_pid:\n                if cur_pid is not None:\n                    flush(cur_pid, scores)\n                cur_pid = pid\n                scores = {}\n\n            score = score ** CONFIG[\"MLP_POWER\"]\n            scores[term] = noisy_or(scores.get(term, 0.0), score)\n\n    if cur_pid:\n        flush(cur_pid, scores)\n\n    fout.close()\n    print(f\"DONE. Total rows written: {total:,}\")\n\n# =========================================================\n# RUN\n# =========================================================\nif __name__ == \"__main__\":\n    run_goa_mlp_merge()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T01:33:04.414784Z","iopub.execute_input":"2025-12-18T01:33:04.415109Z","iopub.status.idle":"2025-12-18T01:36:42.731354Z","shell.execute_reply.started":"2025-12-18T01:33:04.415083Z","shell.execute_reply":"2025-12-18T01:36:42.730021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  import pandas as pd\n# import numpy as np\n# from tqdm import tqdm\n# import os\n# import math\n\n# # =========================================================\n# # CẤU HÌNH KIỂM TRA\n# # =================== ======================================\n# SUB_FILE = \"submission.tsv\"\n# TEST_IDS_FILE = \"/kaggle/input/cafa6-embeds/test_ids.txt\"\n# CHUNK_SIZE = 2_000_000  # Đọc từng chunk nhỏ để kiểm tra kỹ\n\n# def check_submission_full_integrity(submission_path, test_ids_path):\n#     print(f\"🔬 BẮT ĐẦU KIỂM TRA TOÀN DIỆN: {submission_path}\")\n#     print(\"=\" * 60)\n    \n#     if not os.path.exists(submission_path):\n#         print(f\"❌ LỖI: Không tìm thấy file submission.\")\n#         return False\n        \n#     # --- BƯỚC 1: LOAD GROUND TRUTH ---\n#     try:\n#         with open(test_ids_path, 'r') as f:\n#             required_pids = set(line.strip() for line in f)\n#         print(f\"✅ Đã load {len(required_pids):,} Protein ID bắt buộc.\")\n#     except Exception as e:\n#         print(f\"❌ LỖI: Không đọc được file test_ids. Lỗi: {e}\")\n#         return False\n\n#     # --- BƯỚC 2: QUÉT FILE VÀ KIỂM TRA LỖI ---\n#     found_pids = set()\n#     total_rows = 0\n    \n#     # Bộ đếm lỗi\n#     error_stats = {\n#         \"nan_score\": 0,\n#         \"inf_score\": 0,\n#         \"out_of_range\": 0, # Score < 0 hoặc > 1\n#         \"bad_format_go\": 0,\n#         \"bad_columns\": 0\n#     }\n    \n#     try:\n#         # Dùng iterator để đọc từng dòng\n#         reader = pd.read_csv(\n#             submission_path, \n#             sep='\\t', \n#             header=None, \n#             names=['Protein', 'GO', 'Score'],\n#             chunksize=CHUNK_SIZE, \n#             engine='python',\n#             on_bad_lines='skip' # Tự động bỏ qua dòng lỗi format nặng (nhưng ta sẽ cảnh báo)\n#         )\n        \n#         for i, chunk in enumerate(tqdm(reader, desc=\"Deep Scanning\")):\n#             total_rows += len(chunk)\n            \n#             # 1. Kiểm tra Score Numeric\n#             scores = pd.to_numeric(chunk['Score'], errors='coerce')\n            \n#             # Đếm lỗi NaN/Inf\n#             n_nans = scores.isna().sum()\n#             n_infs = np.isinf(scores).sum()\n            \n#             if n_nans > 0: error_stats[\"nan_score\"] += n_nans\n#             if n_infs > 0: error_stats[\"inf_score\"] += n_infs\n            \n#             # Kiểm tra Range [0, 1] (Chỉ check trên các giá trị hợp lệ)\n#             valid_scores = scores.dropna()\n#             n_out = ((valid_scores < 0.0) | (valid_scores > 1.0)).sum()\n#             if n_out > 0: error_stats[\"out_of_range\"] += n_out\n            \n#             # 2. Kiểm tra Format GO\n#             # GO term phải là chuỗi và bắt đầu bằng 'GO:'\n#             # (Chuyển sang str trước để tránh lỗi nếu pandas nhận diện nhầm kiểu)\n#             go_check = chunk['GO'].astype(str).str.startswith('GO:')\n#             n_bad_go = (~go_check).sum()\n#             if n_bad_go > 0: error_stats[\"bad_format_go\"] += n_bad_go\n            \n#             # 3. Thu thập Protein ID\n#             found_pids.update(chunk['Protein'].astype(str).unique())\n\n#     except Exception as e:\n#         print(f\"\\n❌ LỖI NGHIÊM TRỌNG KHI ĐỌC FILE: {e}\")\n#         print(\"   => Khả năng cao file bị lỗi cấu trúc (dòng thiếu cột, sai separator...)\")\n#         return False\n\n#     # --- BƯỚC 3: TỔNG HỢP KẾT QUẢ ---\n#     missing_pids = required_pids - found_pids\n#     n_missing = len(missing_pids)\n    \n#     print(\"\\n\" + \"=\"*60)\n#     print(\"📊 BÁO CÁO KẾT QUẢ KIỂM TRA\")\n#     print(\"=\"*60)\n    \n#     # Báo cáo Lỗi Cấu trúc/Số học\n#     has_error = False\n#     print(f\"1. Kiểm tra Dữ liệu & Định dạng:\")\n#     for err_type, count in error_stats.items():\n#         if count > 0:\n#             print(f\"   ❌ {err_type.upper()}: Phát hiện {count:,} dòng lỗi!\")\n#             has_error = True\n#         else:\n#             print(f\"   ✅ {err_type}: 0 lỗi\")\n            \n#     if not has_error:\n#         print(\"   => Dữ liệu sạch sẽ hoàn hảo.\")\n    \n#     # Báo cáo Coverage\n#     print(f\"\\n2. Kiểm tra Phủ sóng Protein:\")\n#     if n_missing == 0:\n#         print(\"   ✅ ĐẦY ĐỦ: File chứa đủ 100% Protein ID cần thiết.\")\n#     else:\n#         print(f\"   ❌ THIẾU SÓT: Thiếu {n_missing:,} Protein ID!\")\n#         print(f\"      Ví dụ: {list(missing_pids)[:5]}\")\n        \n#     print(f\"\\n3. Thống kê chung:\")\n#     print(f\"   - Tổng số dòng hợp lệ: {total_rows:,}\")\n#     print(\"=\"*60)\n    \n#     # Kết luận\n#     if n_missing == 0 and not has_error:\n#         print(\"🎉 FILE ĐẠT CHUẨN 100%. SẴN SÀNG NỘP!\")\n#         return True\n#     else:\n#         print(\"⚠️ FILE CÓ VẤN ĐỀ. VUI LÒNG SỬA TRƯỚC KHI NỘP.\")\n#         return False\n\n# if __name__ == \"__main__\":\n#     check_submission_full_integrity(SUB_FILE, TEST_IDS_FILE)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-17T05:08:30.838132Z","iopub.execute_input":"2025-12-17T05:08:30.838769Z","iopub.status.idle":"2025-12-17T05:08:30.849644Z","shell.execute_reply.started":"2025-12-17T05:08:30.838728Z","shell.execute_reply":"2025-12-17T05:08:30.848229Z"}},"outputs":[],"execution_count":null}]}