icoa-cli 2.19.394 → 2.19.396

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{existsSync as o,mkdirSync as e,readFileSync as n,readdirSync as l,writeFileSync as t}from"node:fs";import{join as s}from"node:path";import chalk from"chalk";import{makeReplAsker as i}from"../lib/repl-asker.js";import{logCommand as r}from"../lib/logger.js";import{openFile as a}from"../lib/open-file.js";import{serializeIpynb as c}from"../lib/notebook-doc.js";import{loadLearnState as g}from"../lib/learn-state.js";import{buildStarterCells as u,dataFileUrls as d,parseKaggleTaskList as y,parseSubmitResponse as m,validateSubmissionText as b}from"../lib/kaggle.js";function h(){return process.env.ICOA_KAGGLE_SERVER?process.env.ICOA_KAGGLE_SERVER.replace(/\/$/,""):"https://practice.icoa2026.au"}async function p(o){try{const e=await fetch(h()+o,{signal:AbortSignal.timeout(3e4)});return await e.json()}catch{return null}}async function f(o){try{const e=await fetch(o,{signal:AbortSignal.timeout(6e4)});return e.ok?await e.text():null}catch{return null}}function w(){console.log(),console.log(chalk.yellow(" The practice server is not reachable right now.")),console.log(chalk.gray(" Check your connection, or try again later.")),console.log()}function k(o){return s(process.cwd(),"icoa-kaggle",o)}function v(o){if("icoa-holdout"===o?.judge)return console.log(chalk.bold.white(" Submit to ICOA (scored on the server):")),console.log(chalk.gray(" ")+chalk.bold.cyan(`kaggle submit ${o.comp_id}`)),void console.log(chalk.gray(" scores vs the secret test labels → live rank on ")+chalk.cyan("board"));console.log(chalk.bold.white(" Submit on Kaggle (manual, your own account):")),o?.kaggle_url?(console.log(chalk.gray(" 1. open ")+chalk.cyan(o.kaggle_url)),console.log(chalk.gray(" 2. Submit Predictions → upload ")+chalk.white("submission.csv")),console.log(chalk.gray(" 3. read your score, improve the MODEL cell, re-run, repeat."))):(console.log(chalk.gray(" (placeholder dataset — no live Kaggle page yet; when the real")),console.log(chalk.gray(" competition dataset lands, its submission link will appear here.)")))}export async function runKaggle(n){const l=i(),a=async o=>await l.ask(o)??"";try{console.log(),console.log(chalk.bold.cyan(" 📊 KAGGLE PRACTICE")+chalk.gray(" · train locally · submit on Kaggle yourself")),console.log(chalk.gray(" Small real-format datasets, a runnable starter notebook, your own CPU.")),console.log();const l=await p("/api/icoa/kaggle/tasks");if(!l)return w();const i=y(l);if(0===i.length)return console.log(chalk.yellow(" No practice competitions are live yet — check back soon.")),void console.log();let g=n?i.find(o=>o.comp_id===n):void 0;if(n&&!g&&console.log(chalk.yellow(` No competition called "${n}".`)),!g){console.log(chalk.white(" Live practice competitions:")),i.forEach((o,e)=>{console.log(chalk.gray(` ${e+1}. `)+chalk.cyan(o.comp_id)+chalk.gray(` — ${o.title} · ${o.metric}`))});for(let o=0;o<3&&!g;o++){const o=(await a(chalk.cyan(" Pick one (number or id): "))).trim();if(!o)return;const e=Number(o);g=!Number.isNaN(e)&&i[e-1]||i.find(e=>e.comp_id===o.toLowerCase())||void 0,g||console.log(chalk.gray(` Try a number 1-${i.length} or an id like "${i[0].comp_id}".`))}if(!g)return}const m=g.comp_id;if(r(`kaggle: download ${m}`),console.log(),console.log(chalk.white(` ${g.title}`)+chalk.gray(` · ${g.metric}`)),g.description){console.log();for(const o of function(o){const e=[];let n="";for(const l of o.split(/\s+/))""!==n&&n.length+1+l.length>74?(e.push(n),n=l):n=""===n?l:`${n} ${l}`;return""!==n&&e.push(n),e}(g.description))console.log(chalk.white(` ${o}`))}console.log(),console.log(chalk.gray(" Downloading data…"));let b=null;const h=d(g);if(h){const[o,e,n]=await Promise.all([f(h.train),f(h.test),f(h.sample_submission)]);o&&e&&n&&(b={train:o,test:e,sample_submission:n})}if(!b){const o=await p(`/api/icoa/kaggle/${m}/data`);b=o?.data??o}if(!b?.train||!b?.test||!b?.sample_submission)return w();const v=k(m),_={...g,...b.meta??{}};try{e(v,{recursive:!0}),t(s(v,"train.csv"),b.train),t(s(v,"test.csv"),b.test),t(s(v,"sample_submission.csv"),b.sample_submission),t(s(v,"meta.json"),`${JSON.stringify(_,null,2)}\n`)}catch{return void console.log(chalk.yellow(" Could not write the data files here — check directory permissions."))}const j=s(v,"starter.ipynb");let S=!1;o(j)?S=!0:t(j,c(u(v,_)));const K="number"==typeof _.n_train?`${_.n_train} rows`:"labelled",N="number"==typeof _.n_test?`${_.n_test} rows`:"predict these";console.log(chalk.green(" ✓ train: ")+chalk.white(s(v,"train.csv"))+chalk.gray(` (${K})`)),console.log(chalk.green(" ✓ test: ")+chalk.white(s(v,"test.csv"))+chalk.gray(` (${N})`)),console.log(chalk.green(" ✓ format: ")+chalk.white(s(v,"sample_submission.csv"))),console.log(chalk.green(" ✓ notebook: ")+chalk.white(j)+chalk.gray(S?" (kept your existing one)":" (runnable baseline inside)")),(_.licence||_.attribution)&&console.log(chalk.gray(` licence: ${_.licence??"—"} · ${_.attribution??""}`)),console.log(),$(m,_,j),console.log()}finally{l.close()}}function $(o,e,n){console.log(chalk.bold.white(" Your move — 3 steps:")),console.log(chalk.gray(" 1. ")+chalk.bold.cyan(`icoa ipynb ${n}`)+chalk.gray(" → run all (baseline trains)")),console.log(chalk.gray(" 2. it writes ")+chalk.white("submission.csv")+chalk.gray(" → verify with ")+chalk.cyan(`kaggle check ${o}`)),"icoa-holdout"===e?.judge?console.log(chalk.gray(" 3. ")+chalk.bold.cyan(`kaggle submit ${o}`)+chalk.gray(" → scored + ranked live.")):console.log(chalk.gray(" 3. upload it on Kaggle yourself — then beat the baseline.")),console.log(),console.log(chalk.bold.white(" 🤝 Want AI help? Set up your cockpit:")),console.log(chalk.gray(" 1. split the screen: ")+chalk.bold.cyan("Ctrl-b %")+chalk.gray(" (lesson/work left · AI right)")),console.log(chalk.gray(" 2. in the right pane: ")+chalk.bold.cyan("icoa")+chalk.gray(" then type ")+chalk.bold.cyan("ai4ioai")+chalk.gray(" → live Gemma 3")),console.log(chalk.gray(" 3. ask it for feature ideas or bug fixes — then ")+chalk.bold.white("VERIFY")+chalk.gray(" with your val score (a tip that lowers it, drop it).")),console.log(),v(e)}export async function runKaggleGuide(e){console.log();const l=_(e,"guide");if(!l)return;const t=k(l);let i=null;try{i=JSON.parse(n(s(t,"meta.json"),"utf-8"))}catch{i=null}r(`kaggle: guide ${l}`);const a=s(t,"starter.ipynb"),c=o(s(t,"submission.csv"));console.log(chalk.bold.white(` ═══ 📖 ${i?.title??l} — walkthrough ═══`)),console.log(),console.log(chalk.gray(" Where you are: data ")+chalk.green("downloaded ✓")+chalk.gray(" · submission.csv: ")+(c?chalk.green("written ✓"):chalk.yellow("not yet — run the notebook"))),console.log(),$(l,i,a),console.log(),console.log(chalk.gray(" Inside the notebook, ")+chalk.cyan("guide")+chalk.gray(" shows the notebook-level guide.")),console.log()}function _(e,n){if(e)return e;const t=s(process.cwd(),"icoa-kaggle"),i=o(t)?l(t,{withFileTypes:!0}).filter(e=>e.isDirectory()&&o(s(t,e.name,"sample_submission.csv"))).map(o=>o.name):[];if(1===i.length)return i[0];if(0===i.length)return console.log(chalk.yellow(" No downloaded competition found here — run ")+chalk.cyan("kaggle")+chalk.yellow(" first.")),console.log(),null;console.log(chalk.white(" Several competitions downloaded — pick one:"));for(const o of i)console.log(chalk.gray(` kaggle ${n} `)+chalk.cyan(o));return console.log(),null}export async function runKaggleCheck(e){console.log();const l=_(e,"check");if(!l)return;const t=k(l),i=s(t,"submission.csv"),c=s(t,"sample_submission.csv");if(!o(c))return console.log(chalk.yellow(` No data for "${l}" here — run `)+chalk.cyan(`kaggle ${l}`)+chalk.yellow(" first.")),void console.log();if(!o(i))return console.log(chalk.yellow(" No submission.csv yet — open the notebook and run all cells:")),console.log(chalk.gray(" ")+chalk.cyan(`icoa ipynb ${s(t,"starter.ipynb")}`)),void console.log();const g=b(n(i,"utf-8"),n(c,"utf-8"));r(`kaggle: check ${l} → ${g.ok?"ok":`rejected: ${g.problems[0]??"format"}`}`);let u=null;try{u=JSON.parse(n(s(t,"meta.json"),"utf-8"))}catch{u=null}if(g.ok)console.log(chalk.bold.green(" ✓ submission.csv is Kaggle-ready")+chalk.gray(" (matches sample_submission.csv)")),console.log(chalk.gray(" ")+chalk.white(i)),console.log(),v(u),a(t);else{console.log(chalk.bold.red(" ✗ submission.csv would be rejected:"));for(const o of g.problems)console.log(chalk.yellow(` · ${o}`));console.log(),console.log(chalk.gray(" Fix the submission cell in the notebook and re-run it."))}console.log()}export async function runKaggleSubmit(e){console.log();const l=_(e,"submit");if(!l)return;const t=k(l),a=s(t,"submission.csv"),c=s(t,"sample_submission.csv");let u=null;try{u=JSON.parse(n(s(t,"meta.json"),"utf-8"))}catch{u=null}if(!u||!o(c))return console.log(chalk.yellow(` No data for "${l}" here — run `)+chalk.cyan(`kaggle ${l}`)+chalk.yellow(" first.")),void console.log();if("icoa-holdout"!==u.judge)return console.log(chalk.yellow(" This competition is judged on kaggle.com — nothing to submit here.")),console.log(),v(u),void console.log();if(!o(a))return console.log(chalk.yellow(" No submission.csv yet — open the notebook and run all cells:")),console.log(chalk.gray(" ")+chalk.cyan(`icoa ipynb ${s(t,"starter.ipynb")}`)),void console.log();const d=n(a,"utf-8"),y=b(d,n(c,"utf-8"));if(!y.ok){r(`kaggle: submit ${l} → local-reject: ${y.problems[0]??"format"}`),console.log(chalk.bold.red(" ✗ submission.csv would be rejected:"));for(const o of y.problems)console.log(chalk.yellow(` · ${o}`));return console.log(),console.log(chalk.gray(" Fix the submission cell in the notebook and re-run it.")),void console.log()}let p=g()?.token??"";if(!p){const o=i();try{p=(await o.ask(chalk.cyan(" Your learn token: "))??"").trim()}finally{o.close()}if(!p)return console.log(chalk.gray(" A learn token identifies you on the board — ask your coach for one.")),void console.log()}console.log(chalk.gray(" Scoring on the server…"));const f=await async function(o,e){try{const n=await fetch(h()+o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(6e4)});return await n.json()}catch{return null}}(`/api/icoa/kaggle/${l}/submit`,{token:p,submission_csv:d}),w=m(f);if(r(`kaggle: submit ${l} → ${w.ok?`${w.metric}=${w.score}${w.deduped?" (deduped)":""} rank=${w.rank??"-"}`:w.reason??"error"}`),console.log(),w.ok){const o=`${w.metric}: ${Number(w.score).toFixed(4).replace(/\.?0+$/,"")}`;w.deduped?(console.log(chalk.bold.white(` ✓ already scored — ${o}`)),console.log(chalk.gray(" These exact predictions were scored before. Improve the MODEL cell and re-run."))):(console.log(chalk.bold.green(` ✓ scored — ${o}`)),"number"==typeof w.rank&&"number"==typeof w.fieldSize&&console.log(chalk.bold.white(` 🏁 board rank #${w.rank}`)+chalk.gray(` of ${w.fieldSize}`))),w.boardEvent&&console.log(chalk.gray(" standings: ")+chalk.cyan(`board ${w.boardEvent} ${l}`))}else switch(w.reason){case"bad_token":console.log(chalk.bold.red(" ✗ that learn token was not accepted")+chalk.gray(" (invalid or expired)")),console.log(chalk.gray(" Check the token with your coach, then try again."));break;case"format":console.log(chalk.bold.red(" ✗ the server rejected submission.csv:"));for(const o of w.problems??[])console.log(chalk.yellow(` · ${o}`));break;case"event_closed":case"outside_window":console.log(chalk.bold.yellow(" ⏱ the submission window for this competition is closed."));break;case"cap_reached":console.log(chalk.bold.yellow(" ⏱ you have used all your submissions for this competition."));break;case"kaggle_judged":console.log(chalk.yellow(" This competition is judged on kaggle.com — nothing to submit here."));break;default:console.log(chalk.yellow(" The scoring server is not reachable right now — try again in a minute."))}console.log()}export function registerKaggleCommand(o){o.command("kaggle").argument("[action]",'competition id, or "check" / "submit"').argument("[comp]",'competition id (with "check" / "submit")').description("Kaggle-style ML practice — dataset + starter notebook, check & submit").action(async(o,e)=>{"check"===o?await runKaggleCheck(e):"submit"===o?await runKaggleSubmit(e):"guide"===o?await runKaggleGuide(e):await runKaggle(o)}),o.command("guide").argument("[comp]","competition id").description("Show the competition walkthrough (3 steps + AI cockpit)").action(async o=>{await runKaggleGuide(o)})}
1
+ import{existsSync as o,mkdirSync as e,readFileSync as n,readdirSync as l,writeFileSync as t}from"node:fs";import{join as s}from"node:path";import chalk from"chalk";import{makeReplAsker as r}from"../lib/repl-asker.js";import{logCommand as i}from"../lib/logger.js";import{openFile as a}from"../lib/open-file.js";import{serializeIpynb as c}from"../lib/notebook-doc.js";import{loadLearnState as g}from"../lib/learn-state.js";import{buildStarterCells as u,dataFileUrls as d,parseKaggleTaskList as y,parseSubmitResponse as m,validateSubmissionText as b}from"../lib/kaggle.js";function h(){return process.env.ICOA_KAGGLE_SERVER?process.env.ICOA_KAGGLE_SERVER.replace(/\/$/,""):"https://practice.icoa2026.au"}async function p(o){try{const e=await fetch(h()+o,{signal:AbortSignal.timeout(3e4)});return await e.json()}catch{return null}}async function f(o){try{const e=await fetch(o,{signal:AbortSignal.timeout(6e4)});return e.ok?await e.text():null}catch{return null}}function k(){console.log(),console.log(chalk.yellow(" The practice server is not reachable right now.")),console.log(chalk.gray(" Check your connection, or try again later.")),console.log()}function w(o){return s(process.cwd(),"icoa-kaggle",o)}function v(o,e){const n=[];let l="";for(const t of o.split(/\s+/))""!==l&&l.length+1+t.length>e?(n.push(l),l=t):l=""===l?t:`${l} ${t}`;return""!==l&&n.push(l),n}function $(o){if("icoa-holdout"===o?.judge)return console.log(chalk.bold.white(" Submit to ICOA (scored on the server):")),console.log(chalk.gray(" ")+chalk.bold.cyan(`kaggle submit ${o.comp_id}`)),void console.log(chalk.gray(" scores vs the secret test labels → live rank on ")+chalk.cyan("board"));console.log(chalk.bold.white(" Submit on Kaggle (manual, your own account):")),o?.kaggle_url?(console.log(chalk.gray(" 1. open ")+chalk.cyan(o.kaggle_url)),console.log(chalk.gray(" 2. Submit Predictions → upload ")+chalk.white("submission.csv")),console.log(chalk.gray(" 3. read your score, improve the MODEL cell, re-run, repeat."))):(console.log(chalk.gray(" (placeholder dataset — no live Kaggle page yet; when the real")),console.log(chalk.gray(" competition dataset lands, its submission link will appear here.)")))}export async function runKaggle(n){const l=r(),a=async o=>await l.ask(o)??"";try{console.log(),console.log(chalk.bold.cyan(" 📊 KAGGLE PRACTICE")+chalk.gray(" · real ML competitions, a runnable starter notebook")),console.log(chalk.gray(" Small tasks run on your CPU; ")+chalk.magenta("⚡ GPU tasks")+chalk.gray(" run on the ICOA GPU box.")),console.log();const l=await p("/api/icoa/kaggle/tasks");if(!l)return k();const r=y(l);if(0===r.length)return console.log(chalk.yellow(" No practice competitions are live yet — check back soon.")),void console.log();let g=n?r.find(o=>o.comp_id===n):void 0;if(n&&!g&&console.log(chalk.yellow(` No competition called "${n}".`)),!g){console.log(chalk.white(" Live practice competitions:")),r.forEach((o,e)=>{const n=o.needs_gpu?chalk.magenta(" ⚡GPU"):"";console.log(chalk.gray(` ${e+1}. `)+chalk.cyan(o.comp_id)+chalk.gray(` — ${o.title} · ${o.metric}`)+n)});for(let o=0;o<3&&!g;o++){const o=(await a(chalk.cyan(" Pick one (number or id): "))).trim();if(!o)return;const e=Number(o);g=!Number.isNaN(e)&&r[e-1]||r.find(e=>e.comp_id===o.toLowerCase())||void 0,g||console.log(chalk.gray(` Try a number 1-${r.length} or an id like "${r[0].comp_id}".`))}if(!g)return}const m=g.comp_id;if(i(`kaggle: download ${m}`),g.needs_gpu)return void await async function(n,l){const r=w(n);if(console.log(),console.log(chalk.white(` ${l.title}`)+chalk.gray(` · ${l.metric}`)+chalk.bold.magenta(" ⚡ GPU task")),l.description){console.log();for(const o of v(l.description,74))console.log(chalk.white(` ${o}`))}console.log(),console.log(chalk.gray(" Fetching the notebook…")+chalk.gray(" (the audio dataset stays on the GPU box — no download)"));const i=await p(`/api/icoa/kaggle/${n}/starter`),a=i?.data?.starter??i?.starter;if("string"!=typeof a||0===a.length)return k();const c=s(r,"starter.ipynb");let g=!1;try{e(r,{recursive:!0}),o(c)?g=!0:t(c,a),t(s(r,"meta.json"),`${JSON.stringify(l,null,2)}\n`)}catch{return void console.log(chalk.yellow(" Could not write the notebook here — check directory permissions."))}console.log(chalk.green(" ✓ notebook: ")+chalk.white(c)+chalk.gray(g?" (kept your existing one)":" (the real IOAI task inside)")),console.log(chalk.green(" ✓ dataset: ")+chalk.white("already on the GPU box")+chalk.gray(" — no download needed")),console.log(),console.log(chalk.bold.white(" This is a GPU task — 3 steps:")),console.log(chalk.gray(" 1. ")+chalk.bold.cyan(`icoa ipynb ${c}`)),console.log(chalk.gray(" 2. ")+chalk.bold.cyan("gpu key <your-key>")+chalk.gray(" then ")+chalk.bold.cyan("gpu on")+chalk.gray(" (connects you to the box where the audio data lives)")),console.log(chalk.gray(" 3. ")+chalk.bold.cyan("run all")+chalk.gray(" → the notebook trains on the GPU.")),console.log(),l.kaggle_url&&(console.log(chalk.gray(" Submit on Kaggle: ")+chalk.cyan(l.kaggle_url)),console.log())}(m,g);if(console.log(),console.log(chalk.white(` ${g.title}`)+chalk.gray(` · ${g.metric}`)),g.description){console.log();for(const o of v(g.description,74))console.log(chalk.white(` ${o}`))}console.log(),console.log(chalk.gray(" Downloading data…"));let b=null;const h=d(g);if(h){const[o,e,n]=await Promise.all([f(h.train),f(h.test),f(h.sample_submission)]);o&&e&&n&&(b={train:o,test:e,sample_submission:n})}if(!b){const o=await p(`/api/icoa/kaggle/${m}/data`);b=o?.data??o}if(!b?.train||!b?.test||!b?.sample_submission)return k();const $=w(m),j={...g,...b.meta??{}};try{e($,{recursive:!0}),t(s($,"train.csv"),b.train),t(s($,"test.csv"),b.test),t(s($,"sample_submission.csv"),b.sample_submission),t(s($,"meta.json"),`${JSON.stringify(j,null,2)}\n`)}catch{return void console.log(chalk.yellow(" Could not write the data files here — check directory permissions."))}const S=s($,"starter.ipynb");let K=!1;o(S)?K=!0:t(S,c(u($,j)));const x="number"==typeof j.n_train?`${j.n_train} rows`:"labelled",G="number"==typeof j.n_test?`${j.n_test} rows`:"predict these";console.log(chalk.green(" ✓ train: ")+chalk.white(s($,"train.csv"))+chalk.gray(` (${x})`)),console.log(chalk.green(" ✓ test: ")+chalk.white(s($,"test.csv"))+chalk.gray(` (${G})`)),console.log(chalk.green(" ✓ format: ")+chalk.white(s($,"sample_submission.csv"))),console.log(chalk.green(" ✓ notebook: ")+chalk.white(S)+chalk.gray(K?" (kept your existing one)":" (runnable baseline inside)")),(j.licence||j.attribution)&&console.log(chalk.gray(` licence: ${j.licence??"—"} · ${j.attribution??""}`)),console.log(),_(m,j,S),console.log()}finally{l.close()}}function _(o,e,n){console.log(chalk.bold.white(" Your move — 3 steps:")),console.log(chalk.gray(" 1. ")+chalk.bold.cyan(`icoa ipynb ${n}`)+chalk.gray(" → run all (baseline trains)")),console.log(chalk.gray(" 2. it writes ")+chalk.white("submission.csv")+chalk.gray(" → verify with ")+chalk.cyan(`kaggle check ${o}`)),"icoa-holdout"===e?.judge?console.log(chalk.gray(" 3. ")+chalk.bold.cyan(`kaggle submit ${o}`)+chalk.gray(" → scored + ranked live.")):console.log(chalk.gray(" 3. upload it on Kaggle yourself — then beat the baseline.")),console.log(),console.log(chalk.bold.white(" 🤝 Want AI help? Set up your cockpit:")),console.log(chalk.gray(" 1. split the screen: ")+chalk.bold.cyan("Ctrl-b %")+chalk.gray(" (lesson/work left · AI right)")),console.log(chalk.gray(" 2. in the right pane: ")+chalk.bold.cyan("icoa")+chalk.gray(" then type ")+chalk.bold.cyan("ai4ioai")+chalk.gray(" → live Gemma 3")),console.log(chalk.gray(" 3. ask it for feature ideas or bug fixes — then ")+chalk.bold.white("VERIFY")+chalk.gray(" with your val score (a tip that lowers it, drop it).")),console.log(),$(e)}export async function runKaggleGuide(e){console.log();const l=j(e,"guide");if(!l)return;const t=w(l);let r=null;try{r=JSON.parse(n(s(t,"meta.json"),"utf-8"))}catch{r=null}i(`kaggle: guide ${l}`);const a=s(t,"starter.ipynb"),c=o(s(t,"submission.csv"));console.log(chalk.bold.white(` ═══ 📖 ${r?.title??l} — walkthrough ═══`)),console.log(),console.log(chalk.gray(" Where you are: data ")+chalk.green("downloaded ✓")+chalk.gray(" · submission.csv: ")+(c?chalk.green("written ✓"):chalk.yellow("not yet — run the notebook"))),console.log(),_(l,r,a),console.log(),console.log(chalk.gray(" Inside the notebook, ")+chalk.cyan("guide")+chalk.gray(" shows the notebook-level guide.")),console.log()}function j(e,n){if(e)return e;const t=s(process.cwd(),"icoa-kaggle"),r=o(t)?l(t,{withFileTypes:!0}).filter(e=>e.isDirectory()&&o(s(t,e.name,"sample_submission.csv"))).map(o=>o.name):[];if(1===r.length)return r[0];if(0===r.length)return console.log(chalk.yellow(" No downloaded competition found here — run ")+chalk.cyan("kaggle")+chalk.yellow(" first.")),console.log(),null;console.log(chalk.white(" Several competitions downloaded — pick one:"));for(const o of r)console.log(chalk.gray(` kaggle ${n} `)+chalk.cyan(o));return console.log(),null}export async function runKaggleCheck(e){console.log();const l=j(e,"check");if(!l)return;const t=w(l),r=s(t,"submission.csv"),c=s(t,"sample_submission.csv");if(!o(c))return console.log(chalk.yellow(` No data for "${l}" here — run `)+chalk.cyan(`kaggle ${l}`)+chalk.yellow(" first.")),void console.log();if(!o(r))return console.log(chalk.yellow(" No submission.csv yet — open the notebook and run all cells:")),console.log(chalk.gray(" ")+chalk.cyan(`icoa ipynb ${s(t,"starter.ipynb")}`)),void console.log();const g=b(n(r,"utf-8"),n(c,"utf-8"));i(`kaggle: check ${l} → ${g.ok?"ok":`rejected: ${g.problems[0]??"format"}`}`);let u=null;try{u=JSON.parse(n(s(t,"meta.json"),"utf-8"))}catch{u=null}if(g.ok)console.log(chalk.bold.green(" ✓ submission.csv is Kaggle-ready")+chalk.gray(" (matches sample_submission.csv)")),console.log(chalk.gray(" ")+chalk.white(r)),console.log(),$(u),a(t);else{console.log(chalk.bold.red(" ✗ submission.csv would be rejected:"));for(const o of g.problems)console.log(chalk.yellow(` · ${o}`));console.log(),console.log(chalk.gray(" Fix the submission cell in the notebook and re-run it."))}console.log()}export async function runKaggleSubmit(e){console.log();const l=j(e,"submit");if(!l)return;const t=w(l),a=s(t,"submission.csv"),c=s(t,"sample_submission.csv");let u=null;try{u=JSON.parse(n(s(t,"meta.json"),"utf-8"))}catch{u=null}if(!u||!o(c))return console.log(chalk.yellow(` No data for "${l}" here — run `)+chalk.cyan(`kaggle ${l}`)+chalk.yellow(" first.")),void console.log();if("icoa-holdout"!==u.judge)return console.log(chalk.yellow(" This competition is judged on kaggle.com — nothing to submit here.")),console.log(),$(u),void console.log();if(!o(a))return console.log(chalk.yellow(" No submission.csv yet — open the notebook and run all cells:")),console.log(chalk.gray(" ")+chalk.cyan(`icoa ipynb ${s(t,"starter.ipynb")}`)),void console.log();const d=n(a,"utf-8"),y=b(d,n(c,"utf-8"));if(!y.ok){i(`kaggle: submit ${l} → local-reject: ${y.problems[0]??"format"}`),console.log(chalk.bold.red(" ✗ submission.csv would be rejected:"));for(const o of y.problems)console.log(chalk.yellow(` · ${o}`));return console.log(),console.log(chalk.gray(" Fix the submission cell in the notebook and re-run it.")),void console.log()}let p=g()?.token??"";if(!p){const o=r();try{p=(await o.ask(chalk.cyan(" Your learn token: "))??"").trim()}finally{o.close()}if(!p)return console.log(chalk.gray(" A learn token identifies you on the board — ask your coach for one.")),void console.log()}console.log(chalk.gray(" Scoring on the server…"));const f=await async function(o,e){try{const n=await fetch(h()+o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(6e4)});return await n.json()}catch{return null}}(`/api/icoa/kaggle/${l}/submit`,{token:p,submission_csv:d}),k=m(f);if(i(`kaggle: submit ${l} → ${k.ok?`${k.metric}=${k.score}${k.deduped?" (deduped)":""} rank=${k.rank??"-"}`:k.reason??"error"}`),console.log(),k.ok){const o=`${k.metric}: ${Number(k.score).toFixed(4).replace(/\.?0+$/,"")}`;k.deduped?(console.log(chalk.bold.white(` ✓ already scored — ${o}`)),console.log(chalk.gray(" These exact predictions were scored before. Improve the MODEL cell and re-run."))):(console.log(chalk.bold.green(` ✓ scored — ${o}`)),"number"==typeof k.rank&&"number"==typeof k.fieldSize&&console.log(chalk.bold.white(` 🏁 board rank #${k.rank}`)+chalk.gray(` of ${k.fieldSize}`))),k.boardEvent&&console.log(chalk.gray(" standings: ")+chalk.cyan(`board ${k.boardEvent} ${l}`))}else switch(k.reason){case"bad_token":console.log(chalk.bold.red(" ✗ that learn token was not accepted")+chalk.gray(" (invalid or expired)")),console.log(chalk.gray(" Check the token with your coach, then try again."));break;case"format":console.log(chalk.bold.red(" ✗ the server rejected submission.csv:"));for(const o of k.problems??[])console.log(chalk.yellow(` · ${o}`));break;case"event_closed":case"outside_window":console.log(chalk.bold.yellow(" ⏱ the submission window for this competition is closed."));break;case"cap_reached":console.log(chalk.bold.yellow(" ⏱ you have used all your submissions for this competition."));break;case"kaggle_judged":console.log(chalk.yellow(" This competition is judged on kaggle.com — nothing to submit here."));break;default:console.log(chalk.yellow(" The scoring server is not reachable right now — try again in a minute."))}console.log()}export function registerKaggleCommand(o){o.command("kaggle").argument("[action]",'competition id, or "check" / "submit"').argument("[comp]",'competition id (with "check" / "submit")').description("Kaggle-style ML practice — dataset + starter notebook, check & submit").action(async(o,e)=>{"check"===o?await runKaggleCheck(e):"submit"===o?await runKaggleSubmit(e):"guide"===o?await runKaggleGuide(e):await runKaggle(o)}),o.command("guide").argument("[comp]","competition id").description("Show the competition walkthrough (3 steps + AI cockpit)").action(async o=>{await runKaggleGuide(o)})}
@@ -39,6 +39,10 @@ export interface KaggleTask {
39
39
  judge?: string;
40
40
  /** Board event this competition's submissions are recorded under. */
41
41
  board_event?: string | null;
42
+ /** Task-4 Audio Classifier: a GPU task whose (large) dataset already lives on
43
+ * the GPU box. The client does NOT download data locally — it fetches the
44
+ * notebook and points the student at `gpu on`. */
45
+ needs_gpu?: boolean;
42
46
  }
43
47
  export type KaggleMeta = KaggleTask;
44
48
  /** Extract the task list from a server response ({success,data:{tasks}} or bare). */
@@ -1 +1 @@
1
- export function parseKaggleTaskList(e){const t=e??{},r=(t.data??t).tasks;if(!Array.isArray(r))return[];const i=[];for(const e of r){if(!e||"object"!=typeof e)continue;const t=e;"string"==typeof t.comp_id&&""!==t.comp_id&&i.push({comp_id:t.comp_id,title:"string"==typeof t.title?t.title:t.comp_id,metric:"string"==typeof t.metric?t.metric:"accuracy",description:"string"==typeof t.description&&""!==t.description?t.description:void 0,kaggle_url:"string"==typeof t.kaggle_url?t.kaggle_url:null,licence:"string"==typeof t.licence?t.licence:void 0,attribution:"string"==typeof t.attribution?t.attribution:void 0,data_base:"string"==typeof t.data_base&&""!==t.data_base?t.data_base:null,id_col:"string"==typeof t.id_col?t.id_col:void 0,target_col:"string"==typeof t.target_col?t.target_col:void 0,n_train:"number"==typeof t.n_train?t.n_train:void 0,n_test:"number"==typeof t.n_test?t.n_test:void 0,judge:"string"==typeof t.judge&&""!==t.judge?t.judge:void 0,board_event:"string"==typeof t.board_event&&""!==t.board_event?t.board_event:null})}return i}export function dataFileUrls(e){const t=e.data_base;if("string"!=typeof t||""===t)return null;const r=t.replace(/\/$/,"");return{train:`${r}/train.csv`,test:`${r}/test.csv`,sample_submission:`${r}/sample_submission.csv`}}export function buildStarterCells(e,t){const r=t.id_col??"id",i=t.target_col??"target",a=(o=t.metric,/rmse|mse|mae|error/i.test(o));var o;const s="icoa-holdout"===t.judge,n=e=>({kind:"markdown",source:e,result:null}),l=e=>({kind:"code",source:e.join("\n"),result:null}),c=[`# ${t.title}`,"",...t.description?[t.description,""]:[],`Kaggle-style practice — metric: **${t.metric}**.`,"","- `train.csv` — labelled training data",`- \`test.csv\` — rows to predict (no \`${i}\` column)`,"- `sample_submission.csv` — the exact format Kaggle expects","","Run all cells top to bottom: a baseline model trains on your CPU and","`submission.csv` appears next to the data. Then improve the model cell",...s?["and re-run — `icoa kaggle submit` scores it and ranks you on the board."]:["and re-run — your score is whatever Kaggle says it is."],"","**AI help:** split your terminal (`Ctrl-b %`), run `icoa` in the right pane","and type `ai4ioai` for a live Gemma 3. Ask it for feature ideas or bug fixes —","then verify every suggestion with the val score. A tip that lowers it, drop it.",...t.licence?["",`**Data licence:** ${t.licence}`]:[],...t.attribution?[`**Attribution:** ${t.attribution}`]:[]].join("\n"),d=l(["# setup — load the data (leave as is)","import os","import pandas as pd","from pandas.api.types import is_numeric_dtype","from sklearn.model_selection import train_test_split",a?"from sklearn.metrics import mean_squared_error":"from sklearn.metrics import accuracy_score","","# same code everywhere: in a GPU session these env vars point at the","# box's own dataset copy + workspace — locally they are unset.",`DIR = os.environ.get("ICOA_KAGGLE_DATA") or r"${e}"`,'OUT = os.environ.get("ICOA_KAGGLE_OUT") or DIR',...t.data_base?[`DATA_BASE = "${t.data_base.replace(/\/$/,"")}"`,'if not os.path.exists(DIR + "/train.csv"):'," import urllib.request"," os.makedirs(DIR, exist_ok=True)",' for f in ("train.csv", "test.csv", "sample_submission.csv"):',' urllib.request.urlretrieve(DATA_BASE + "/" + f, DIR + "/" + f)']:[],'train = pd.read_csv(DIR + "/train.csv")','test = pd.read_csv(DIR + "/test.csv")',`ID, TARGET = "${r}", "${i}"`,'print(f"train {train.shape} · test {test.shape}")']),m=l(["# FEATURES — turn raw columns into numbers a model can use (improve THIS cell).","# Numbers: fill gaps with the median. Text/category: one-hot the low-cardinality","# ones (<= 20 distinct); drop high-cardinality text like names/IDs (rarely helps","# a baseline). Train and test end up aligned to the exact same columns.","feat = [c for c in train.columns if c not in (ID, TARGET)]","Xtr_raw, Xte_raw = train[feat].copy(), test[feat].copy()","num = [c for c in feat if is_numeric_dtype(Xtr_raw[c])]","cat = [c for c in feat if c not in num and Xtr_raw[c].nunique() <= 20]","for c in num:"," med = Xtr_raw[c].median()"," Xtr_raw[c] = Xtr_raw[c].fillna(med)"," Xte_raw[c] = Xte_raw[c].fillna(med)","X = pd.get_dummies(Xtr_raw[num + cat], columns=cat, dummy_na=True)","X_test = pd.get_dummies(Xte_raw[num + cat], columns=cat, dummy_na=True)",'X, X_test = X.align(X_test, join="left", axis=1, fill_value=0)',"y = train[TARGET]","X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=0)",'print(f"{X.shape[1]} model-ready features "',' f"(dropped {len(feat) - len(num) - len(cat)} high-cardinality/text cols)")']),u=l(a?["# MODEL — a working baseline. Improve THIS cell to climb the Kaggle board.","from sklearn.linear_model import Ridge","model = Ridge()"]:["# MODEL — a working baseline. Improve THIS cell to climb the Kaggle board.","from sklearn.linear_model import LogisticRegression","model = LogisticRegression(max_iter=2000)"]),p=l(["# validate — quick local score on a held-out fifth of train","model.fit(X_tr, y_tr)",a?'print("val RMSE:", round(mean_squared_error(y_val, model.predict(X_val)) ** 0.5, 4))':'print("val accuracy:", round(accuracy_score(y_val, model.predict(X_val)), 4))']),g=l(["# submission — retrain on ALL data, write submission.csv (Kaggle format)","model.fit(X, y)","pred = model.predict(X_test)",'out = OUT + "/submission.csv"',`pd.DataFrame({"${r}": test["${r}"], "${i}": pred}).to_csv(out, index=False)`,'print("wrote", out)',s?`print("score it: icoa kaggle submit ${t.comp_id}")`:'print("format check: icoa kaggle check")']),_=s?["## Submit to ICOA (scored on the server)","",`1. In the ICOA CLI run \`icoa kaggle submit ${t.comp_id}\``,"2. The server scores `submission.csv` against the secret test labels"," and your rank lands on the live board (`icoa board`).","3. Improve the MODEL cell, re-run, submit again — best score counts."]:t.kaggle_url?["## Submit on Kaggle (manual, your own account)","",`1. Open ${t.kaggle_url}`,"2. **Submit Predictions** → upload `submission.csv`","3. Read your leaderboard score, come back, improve the MODEL cell, repeat."]:["## Submit on Kaggle (manual, your own account)","","_This is a placeholder practice dataset — it has no live Kaggle page yet._","When the real competition dataset lands, this cell will link straight to","its Kaggle submission page. The loop is identical: upload `submission.csv`,","read the score, improve the MODEL cell, repeat."];return[n(c),d,m,u,p,g,n(_.join("\n"))]}export function parseSubmitResponse(e){const t=e&&"object"==typeof e?e:{},r=t.data??t,i=r&&"object"==typeof r?r:{};return"number"==typeof i.score&&"string"==typeof i.metric?{ok:!0,score:i.score,metric:i.metric,rank:"number"==typeof i.rank_at_time?i.rank_at_time:null,fieldSize:"number"==typeof i.field_size?i.field_size:null,deduped:!0===i.deduped,boardEvent:"string"==typeof i.board_event?i.board_event:null}:"string"==typeof i.reason&&""!==i.reason?{ok:!1,reason:i.reason,error:"string"==typeof i.error?i.error:void 0,problems:Array.isArray(i.problems)?i.problems.filter(e=>"string"==typeof e):void 0}:{ok:!1,reason:"network"}}export function validateSubmissionText(e,t){const r=[],i=e.trim().split(/\r?\n/),a=t.trim().split(/\r?\n/),o=i[0]??"",s=a[0]??"";o.trim()!==s.trim()&&r.push(`header mismatch: expected "${s}", got "${o}"`);const n=i.slice(1).filter(e=>""!==e.trim()),l=a.slice(1).filter(e=>""!==e.trim());n.length!==l.length&&r.push(`row count mismatch: expected ${l.length}, got ${n.length}`);const c=e=>new Set(e.map(e=>(e.split(",")[0]??"").trim())),d=c(n),m=c(l),u=[...m].filter(e=>!d.has(e)),p=[...d].filter(e=>!m.has(e));if(u.length>0||p.length>0){const e=[];u.length>0&&e.push(`${u.length} missing (e.g. ${u[0]})`),p.length>0&&e.push(`${p.length} unknown (e.g. ${p[0]})`),r.push(`id mismatch vs sample_submission: ${e.join(", ")}`)}for(let e=0;e<n.length;e++)if(n[e].split(",").some(e=>""===e.trim())){r.push(`empty cell in data row ${e+1}`);break}return{ok:0===r.length,problems:r}}
1
+ export function parseKaggleTaskList(e){const t=e??{},r=(t.data??t).tasks;if(!Array.isArray(r))return[];const i=[];for(const e of r){if(!e||"object"!=typeof e)continue;const t=e;"string"==typeof t.comp_id&&""!==t.comp_id&&i.push({comp_id:t.comp_id,title:"string"==typeof t.title?t.title:t.comp_id,metric:"string"==typeof t.metric?t.metric:"accuracy",description:"string"==typeof t.description&&""!==t.description?t.description:void 0,kaggle_url:"string"==typeof t.kaggle_url?t.kaggle_url:null,licence:"string"==typeof t.licence?t.licence:void 0,attribution:"string"==typeof t.attribution?t.attribution:void 0,data_base:"string"==typeof t.data_base&&""!==t.data_base?t.data_base:null,id_col:"string"==typeof t.id_col?t.id_col:void 0,target_col:"string"==typeof t.target_col?t.target_col:void 0,n_train:"number"==typeof t.n_train?t.n_train:void 0,n_test:"number"==typeof t.n_test?t.n_test:void 0,judge:"string"==typeof t.judge&&""!==t.judge?t.judge:void 0,board_event:"string"==typeof t.board_event&&""!==t.board_event?t.board_event:null,needs_gpu:!0===t.needs_gpu})}return i}export function dataFileUrls(e){const t=e.data_base;if("string"!=typeof t||""===t)return null;const r=t.replace(/\/$/,"");return{train:`${r}/train.csv`,test:`${r}/test.csv`,sample_submission:`${r}/sample_submission.csv`}}export function buildStarterCells(e,t){const r=t.id_col??"id",i=t.target_col??"target",a=(o=t.metric,/rmse|mse|mae|error/i.test(o));var o;const s="icoa-holdout"===t.judge,n=e=>({kind:"markdown",source:e,result:null}),l=e=>({kind:"code",source:e.join("\n"),result:null}),c=[`# ${t.title}`,"",...t.description?[t.description,""]:[],`Kaggle-style practice — metric: **${t.metric}**.`,"","- `train.csv` — labelled training data",`- \`test.csv\` — rows to predict (no \`${i}\` column)`,"- `sample_submission.csv` — the exact format Kaggle expects","","Run all cells top to bottom: a baseline model trains on your CPU and","`submission.csv` appears next to the data. Then improve the model cell",...s?["and re-run — `icoa kaggle submit` scores it and ranks you on the board."]:["and re-run — your score is whatever Kaggle says it is."],"","**AI help:** split your terminal (`Ctrl-b %`), run `icoa` in the right pane","and type `ai4ioai` for a live Gemma 3. Ask it for feature ideas or bug fixes —","then verify every suggestion with the val score. A tip that lowers it, drop it.",...t.licence?["",`**Data licence:** ${t.licence}`]:[],...t.attribution?[`**Attribution:** ${t.attribution}`]:[]].join("\n"),d=l(["# setup — load the data (leave as is)","import os","import pandas as pd","from pandas.api.types import is_numeric_dtype","from sklearn.model_selection import train_test_split",a?"from sklearn.metrics import mean_squared_error":"from sklearn.metrics import accuracy_score","","# same code everywhere: in a GPU session these env vars point at the","# box's own dataset copy + workspace — locally they are unset.",`DIR = os.environ.get("ICOA_KAGGLE_DATA") or r"${e}"`,'OUT = os.environ.get("ICOA_KAGGLE_OUT") or DIR',...t.data_base?[`DATA_BASE = "${t.data_base.replace(/\/$/,"")}"`,'if not os.path.exists(DIR + "/train.csv"):'," import urllib.request"," os.makedirs(DIR, exist_ok=True)",' for f in ("train.csv", "test.csv", "sample_submission.csv"):',' urllib.request.urlretrieve(DATA_BASE + "/" + f, DIR + "/" + f)']:[],'train = pd.read_csv(DIR + "/train.csv")','test = pd.read_csv(DIR + "/test.csv")',`ID, TARGET = "${r}", "${i}"`,'print(f"train {train.shape} · test {test.shape}")']),m=l(["# FEATURES — turn raw columns into numbers a model can use (improve THIS cell).","# Numbers: fill gaps with the median. Text/category: one-hot the low-cardinality","# ones (<= 20 distinct); drop high-cardinality text like names/IDs (rarely helps","# a baseline). Train and test end up aligned to the exact same columns.","feat = [c for c in train.columns if c not in (ID, TARGET)]","Xtr_raw, Xte_raw = train[feat].copy(), test[feat].copy()","num = [c for c in feat if is_numeric_dtype(Xtr_raw[c])]","cat = [c for c in feat if c not in num and Xtr_raw[c].nunique() <= 20]","for c in num:"," med = Xtr_raw[c].median()"," Xtr_raw[c] = Xtr_raw[c].fillna(med)"," Xte_raw[c] = Xte_raw[c].fillna(med)","X = pd.get_dummies(Xtr_raw[num + cat], columns=cat, dummy_na=True)","X_test = pd.get_dummies(Xte_raw[num + cat], columns=cat, dummy_na=True)",'X, X_test = X.align(X_test, join="left", axis=1, fill_value=0)',"y = train[TARGET]","X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2, random_state=0)",'print(f"{X.shape[1]} model-ready features "',' f"(dropped {len(feat) - len(num) - len(cat)} high-cardinality/text cols)")']),u=l(a?["# MODEL — a working baseline. Improve THIS cell to climb the Kaggle board.","from sklearn.linear_model import Ridge","model = Ridge()"]:["# MODEL — a working baseline. Improve THIS cell to climb the Kaggle board.","from sklearn.linear_model import LogisticRegression","model = LogisticRegression(max_iter=2000)"]),p=l(["# validate — quick local score on a held-out fifth of train","model.fit(X_tr, y_tr)",a?'print("val RMSE:", round(mean_squared_error(y_val, model.predict(X_val)) ** 0.5, 4))':'print("val accuracy:", round(accuracy_score(y_val, model.predict(X_val)), 4))']),g=l(["# submission — retrain on ALL data, write submission.csv (Kaggle format)","model.fit(X, y)","pred = model.predict(X_test)",'out = OUT + "/submission.csv"',`pd.DataFrame({"${r}": test["${r}"], "${i}": pred}).to_csv(out, index=False)`,'print("wrote", out)',s?`print("score it: icoa kaggle submit ${t.comp_id}")`:'print("format check: icoa kaggle check")']),_=s?["## Submit to ICOA (scored on the server)","",`1. In the ICOA CLI run \`icoa kaggle submit ${t.comp_id}\``,"2. The server scores `submission.csv` against the secret test labels"," and your rank lands on the live board (`icoa board`).","3. Improve the MODEL cell, re-run, submit again — best score counts."]:t.kaggle_url?["## Submit on Kaggle (manual, your own account)","",`1. Open ${t.kaggle_url}`,"2. **Submit Predictions** → upload `submission.csv`","3. Read your leaderboard score, come back, improve the MODEL cell, repeat."]:["## Submit on Kaggle (manual, your own account)","","_This is a placeholder practice dataset — it has no live Kaggle page yet._","When the real competition dataset lands, this cell will link straight to","its Kaggle submission page. The loop is identical: upload `submission.csv`,","read the score, improve the MODEL cell, repeat."];return[n(c),d,m,u,p,g,n(_.join("\n"))]}export function parseSubmitResponse(e){const t=e&&"object"==typeof e?e:{},r=t.data??t,i=r&&"object"==typeof r?r:{};return"number"==typeof i.score&&"string"==typeof i.metric?{ok:!0,score:i.score,metric:i.metric,rank:"number"==typeof i.rank_at_time?i.rank_at_time:null,fieldSize:"number"==typeof i.field_size?i.field_size:null,deduped:!0===i.deduped,boardEvent:"string"==typeof i.board_event?i.board_event:null}:"string"==typeof i.reason&&""!==i.reason?{ok:!1,reason:i.reason,error:"string"==typeof i.error?i.error:void 0,problems:Array.isArray(i.problems)?i.problems.filter(e=>"string"==typeof e):void 0}:{ok:!1,reason:"network"}}export function validateSubmissionText(e,t){const r=[],i=e.trim().split(/\r?\n/),a=t.trim().split(/\r?\n/),o=i[0]??"",s=a[0]??"";o.trim()!==s.trim()&&r.push(`header mismatch: expected "${s}", got "${o}"`);const n=i.slice(1).filter(e=>""!==e.trim()),l=a.slice(1).filter(e=>""!==e.trim());n.length!==l.length&&r.push(`row count mismatch: expected ${l.length}, got ${n.length}`);const c=e=>new Set(e.map(e=>(e.split(",")[0]??"").trim())),d=c(n),m=c(l),u=[...m].filter(e=>!d.has(e)),p=[...d].filter(e=>!m.has(e));if(u.length>0||p.length>0){const e=[];u.length>0&&e.push(`${u.length} missing (e.g. ${u[0]})`),p.length>0&&e.push(`${p.length} unknown (e.g. ${p[0]})`),r.push(`id mismatch vs sample_submission: ${e.join(", ")}`)}for(let e=0;e<n.length;e++)if(n[e].split(",").some(e=>""===e.trim())){r.push(`empty cell in data row ${e+1}`);break}return{ok:0===r.length,problems:r}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.394",
3
+ "version": "2.19.396",
4
4
  "description": "ICOA CLI — The world's first CLI-native cyber & AI security olympiad terminal: AI4CTF (Day 1), CTF4AI (Day 2), VLA4CTF (Pioneer Round — embodied AI)",
5
5
  "type": "module",
6
6
  "bin": {