{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# Deep Past: Git-Driven Kaggle Workflow + TF-IDF Baseline\n\nThis notebook demonstrates a **Git-driven workflow** for Kaggle code competitions using **GitHub Actions + Kaggle API**.\n\n## Why Manage Notebooks in GitHub?\n\nKaggle's browser editor works, but:\n\n- **No version control** \u2014 can't diff, branch, or roll back changes\n- **No editor choice** \u2014 stuck with Kaggle's UI instead of VSCode or your preferred editor\n- **Manual upload** \u2014 every change requires re-uploading through the browser\n\n## The Solution\n\n```\nEdit in VSCode / any editor \u2192 git push \u2192 GitHub Actions \u2192 kaggle kernels push \u2192 Submit via browser\n```\n\nBy managing notebooks in a GitHub repository, you get full Git history, code review, CI/CD, and one-command deployment to Kaggle.\n\n## Why Not Fully Automated?\n\nThe Kaggle API's `CreateCodeSubmission` endpoint returns **403 Forbidden** (`Permission 'kernelSessions.get' was denied`). This applies to both Legacy API Keys and new API Tokens (KGAT_...), across CLI versions 1.8.4 and 2.0.0 (as of Feb 2026). File submission (`competitions submit`) also returns 400 for code competitions.\n\n## Workflow Overview\n\n| Step | Automated? | How |\n|---|---|---|\n| Edit notebook | - | VSCode / any editor |\n| Upload to Kaggle | **Yes** | GitHub Actions + `kaggle kernels push` |\n| Submit | **Manual** | Browser: \"Submit to Competition\" |\n| Check score | **Yes** | `kaggle competitions submissions` API |\n\n## Key Gotchas\n\n1. **`enable_internet` must be `false`** \u2014 Internet ON prevents the notebook from being eligible for submission\n2. **Data path**: `competition_sources` mounts at `/kaggle/input/competitions/<slug>/`, NOT `/kaggle/input/<slug>/`\n3. **`kernels output` on Windows**: Non-ASCII characters (like Akkadian) cause cp932 encoding errors \u2014 use the API directly\n\n---\n\nNow let's build the actual baseline and generate a submission."}, {"cell_type": "markdown", "metadata": {}, "source": "## Step 1: Verify Data Path\n\nFirst, let's confirm where the competition data is mounted. This is a common gotcha."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "from pathlib import Path\n\ninput_dir = Path('/kaggle/input')\nfor item in sorted(input_dir.iterdir()):\n    print(f'{item.name}/')\n    for sub in sorted(item.iterdir()):\n        print(f'  {sub.name} ({sub.stat().st_size:,} bytes)')"}, {"cell_type": "markdown", "metadata": {}, "source": "## Step 2: Load Data\n\nNote the path: `/kaggle/input/competitions/deep-past-initiative-machine-translation/`"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "import pandas as pd\nimport numpy as np\n\nDATA_DIR = Path('/kaggle/input/competitions/deep-past-initiative-machine-translation')\n\ntrain = pd.read_csv(DATA_DIR / 'train.csv')\ntest = pd.read_csv(DATA_DIR / 'test.csv')\nsample_sub = pd.read_csv(DATA_DIR / 'sample_submission.csv')\n\nprint(f'Train: {train.shape}')\nprint(f'  Columns: {train.columns.tolist()}')\nprint(f'  Sample transliteration: {train[\"transliteration\"].iloc[0][:80]}')\nprint(f'  Sample translation: {train[\"translation\"].iloc[0][:80]}')\nprint()\nprint(f'Test: {test.shape}')\nprint(f'  Columns: {test.columns.tolist()}')\nprint()\nprint(f'Submission format: {sample_sub.shape}')\nprint(f'  Columns: {sample_sub.columns.tolist()}')"}, {"cell_type": "markdown", "metadata": {}, "source": "## Step 3: TF-IDF Nearest Neighbor Baseline\n\nFor this translation task (Akkadian \u2192 English), we use a simple approach:\n1. Vectorize all transliterations using **character n-gram TF-IDF**\n2. For each test row, find the **most similar** training transliteration\n3. Use the corresponding English translation as our prediction\n\nCharacter n-grams work well for transliterated cuneiform text where token boundaries are marked with hyphens."}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "from sklearn.feature_extraction.text import TfidfVectorizer\nfrom sklearn.metrics.pairwise import cosine_similarity\n\ntrain['transliteration'] = train['transliteration'].fillna('')\ntrain['translation'] = train['translation'].fillna('')\ntest['transliteration'] = test['transliteration'].fillna('')\n\nvectorizer = TfidfVectorizer(\n    analyzer='char_wb',\n    ngram_range=(2, 5),\n    max_features=50000,\n    sublinear_tf=True\n)\n\ntrain_tfidf = vectorizer.fit_transform(train['transliteration'])\ntest_tfidf = vectorizer.transform(test['transliteration'])\n\nprint(f'Train TF-IDF matrix: {train_tfidf.shape}')\nprint(f'Test TF-IDF matrix: {test_tfidf.shape}')"}, {"cell_type": "markdown", "metadata": {}, "source": "## Step 4: Find Nearest Neighbors and Predict"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "sims = cosine_similarity(test_tfidf, train_tfidf)\nbest_idx = sims.argmax(axis=1)\nbest_scores = sims.max(axis=1)\n\npredictions = []\nfor i, (idx, score) in enumerate(zip(best_idx, best_scores)):\n    pred = train['translation'].iloc[idx]\n    predictions.append(pred)\n    print(f'--- Test {i} (similarity: {score:.3f}) ---')\n    print(f'  Test:  {test[\"transliteration\"].iloc[i][:100]}')\n    print(f'  Match: {train[\"transliteration\"].iloc[idx][:100]}')\n    print(f'  Pred:  {pred[:100]}')\n    print()"}, {"cell_type": "markdown", "metadata": {}, "source": "## Step 5: Create Submission"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "submission = pd.DataFrame({\n    'id': test['id'],\n    'translation': predictions\n})\nsubmission['translation'] = submission['translation'].fillna('unknown')\n\nprint(submission)\nprint()\n\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\nprint(f'Saved submission.csv ({submission.shape})')"}, {"cell_type": "markdown", "metadata": {}, "source": "## GitHub Actions Workflow\n\nThis notebook was pushed to Kaggle using the following GitHub Actions workflow:\n\n```yaml\nname: Kaggle Kernels Push\n\non:\n  workflow_dispatch:\n    inputs:\n      notebook_dir:\n        description: 'Notebook directory (e.g., deep-past)'\n        required: true\n        type: string\n\njobs:\n  push:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.12'\n\n      - name: Install kaggle CLI\n        run: pip install kaggle\n\n      - name: Push notebook to Kaggle\n        env:\n          KAGGLE_API_TOKEN: ${{ secrets.KAGGLE_API_TOKEN }}\n        run: kaggle kernels push -p \"${{ inputs.notebook_dir }}\"\n```\n\n### kernel-metadata.json\n\n```json\n{\n  \"id\": \"your-username/your-kernel-slug\",\n  \"title\": \"Your Title\",\n  \"code_file\": \"your-notebook.ipynb\",\n  \"language\": \"python\",\n  \"kernel_type\": \"notebook\",\n  \"is_private\": \"false\",\n  \"enable_gpu\": \"false\",\n  \"enable_internet\": \"false\",\n  \"competition_sources\": [\"competition-slug\"]\n}\n```\n\n**Important**: `enable_internet` must be `\"false\"` for code competition submissions.\n\n## Full blog post\n\n- [DEV.to](https://dev.to/yasumorishima/kaggle-code-competitions-without-a-local-gpu-github-actions-kaggle-api-cloud-workflow-m3)\n\nIf you found this useful, please upvote!"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10.0"}}, "nbformat": 4, "nbformat_minor": 4}