From 73ed93fe688a140d825f175a0271cca85931f443 Mon Sep 17 00:00:00 2001 From: Yashaswini K P Date: Mon, 15 Jun 2026 14:47:05 +0530 Subject: [PATCH 1/6] feat: implement separate daily inactivity analysis script and workflow --- .github/workflows/analyze-inactivity.yml | 60 +++++++++++++++ scripts/analyze-inactivity.js | 97 ++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 .github/workflows/analyze-inactivity.yml create mode 100644 scripts/analyze-inactivity.js diff --git a/.github/workflows/analyze-inactivity.yml b/.github/workflows/analyze-inactivity.yml new file mode 100644 index 00000000..47a20a3e --- /dev/null +++ b/.github/workflows/analyze-inactivity.yml @@ -0,0 +1,60 @@ +name: Daily Inactivity Analysis + +on: + schedule: + - cron: "0 0 * * *" # Triggers at 00:00 UTC every day + workflow_dispatch: # Allows clicking a button in GitHub to test run it anytime + +jobs: + analyze: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code Repository + uses: actions/checkout@v4 + with: + path: code-repo + + # Checks out the separate repository containing your user files and JSON assets + - name: Checkout Data Repository + uses: actions/checkout@v4 + with: + repository: "codepvg/leetcode-ranking-data" + token: ${{ secrets.DATA_REPO_TOKEN }} + path: data-repo + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: "code-repo/package-lock.json" + + - name: Install Dependencies + run: | + cd code-repo + npm install + + # Executes your new tracking file and safely maps its destination to the data path + - name: Run Inactivity Analysis Script + env: + DATA_DIR: "${{ github.workspace }}/data-repo" + run: | + cd code-repo + node scripts/analyze-inactivity.js + + # Stages, commits, and pushes the newly generated file directly back into the data repo + - name: Commit and Push to Data Repo + run: | + cd data-repo + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + git add inactive-users.json + + if git diff --staged --quiet; then + echo "No changes detected in inactivity data." + else + git commit -m "chore: update daily inactive-users.json snapshot" + git push + fi diff --git a/scripts/analyze-inactivity.js b/scripts/analyze-inactivity.js new file mode 100644 index 00000000..3f268c9b --- /dev/null +++ b/scripts/analyze-inactivity.js @@ -0,0 +1,97 @@ +"use strict"; + +const axios = require("axios"); +const fs = require("fs"); +const path = require("path"); + +const THRESHOLD_DAYS = 90; +const MS_IN_A_DAY = 1000 * 60 * 60 * 24; + +async function fetchData(url) { + try { + const res = await axios.get(url, { timeout: 15000 }); + return res.data; + } catch (err) { + console.error(`API failed for ${url}: ${err.message}`); + return null; + } +} + +(async () => { + const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "..", "data"); + console.log(`Using data directory: ${DATA_DIR}`); + + console.log("Loading users..."); + const userFilePath = path.join(DATA_DIR, "users.json"); + let users = []; + try { + const rawData = fs.readFileSync(userFilePath, "utf8"); + users = JSON.parse(rawData); + console.log(`Loaded ${users.length} users from users.json`); + } catch (err) { + console.error("Failed to load users.json: ", err.message); + process.exit(1); + } + + const baseUrl = "https://leetcode-api-dun.vercel.app/"; + const inactiveUsers = []; + const neverActiveUsers = []; + const now = new Date(); + + console.log(" "); + console.log("Starting inactivity analysis..."); + + for (const user of users) { + const profile = await fetchData(baseUrl + user.id); + if (!profile) { + console.log(`${user.name}: skipped (API error)`); + continue; + } + + const calendar = profile.submissionCalendar; + const timestamps = calendar ? Object.keys(calendar).map(Number) : []; + + // Case 1: Checking for users with absolutely no history (0 score baseline profiles) + if (timestamps.length === 0) { + console.log(`${user.name}: Never Active`); + neverActiveUsers.push(user.id); + continue; + } + + // Case 2: Extracting the absolute latest submission unix timestamp marker + const latestTimestampSeconds = Math.max(...timestamps); + const lastActiveDate = new Date(latestTimestampSeconds * 1000); + + // Compute day delta between execution runtime and user's last platform interaction + const diffTime = Math.abs(now - lastActiveDate); + const diffDays = Math.floor(diffTime / MS_IN_A_DAY); + + if (diffDays > THRESHOLD_DAYS) { + console.log(`${user.name}: Inactive (${diffDays} days ago)`); + inactiveUsers.push(user.id); + } else { + console.log(`${user.name}: Active (${diffDays} days ago)`); + } + } + + console.log("..."); + console.log(" "); + + // Formatting output schema matching maintainer's blueprint specification + const outputData = { + generatedAt: now.toISOString(), + thresholdDays: THRESHOLD_DAYS, + inactiveUsers: inactiveUsers.sort(), + neverActiveUsers: neverActiveUsers.sort(), + }; + + console.log("Writing inactivity analysis data to inactive-users.json..."); + const outputPath = path.join(DATA_DIR, "inactive-users.json"); + try { + fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2), "utf8"); + console.log("Inactivity analysis data saved successfully"); + } catch (err) { + console.error("Failed to write inactive-users.json: ", err.message); + process.exit(1); + } +})(); From 744bc51a4cf48f10c5a8821026970afa5dab6815 Mon Sep 17 00:00:00 2001 From: Yashaswini K P Date: Tue, 16 Jun 2026 21:38:47 +0530 Subject: [PATCH 2/6] feat: add daily inactivity analysis ignoring zero-score users --- .github/workflows/analyze-inactivity.yml | 7 ++--- scripts/analyze-inactivity.js | 37 ++++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.github/workflows/analyze-inactivity.yml b/.github/workflows/analyze-inactivity.yml index 47a20a3e..1e659ed9 100644 --- a/.github/workflows/analyze-inactivity.yml +++ b/.github/workflows/analyze-inactivity.yml @@ -2,8 +2,8 @@ name: Daily Inactivity Analysis on: schedule: - - cron: "0 0 * * *" # Triggers at 00:00 UTC every day - workflow_dispatch: # Allows clicking a button in GitHub to test run it anytime + - cron: "0 0 * * *" + workflow_dispatch: jobs: analyze: @@ -15,7 +15,6 @@ jobs: with: path: code-repo - # Checks out the separate repository containing your user files and JSON assets - name: Checkout Data Repository uses: actions/checkout@v4 with: @@ -35,7 +34,6 @@ jobs: cd code-repo npm install - # Executes your new tracking file and safely maps its destination to the data path - name: Run Inactivity Analysis Script env: DATA_DIR: "${{ github.workspace }}/data-repo" @@ -43,7 +41,6 @@ jobs: cd code-repo node scripts/analyze-inactivity.js - # Stages, commits, and pushes the newly generated file directly back into the data repo - name: Commit and Push to Data Repo run: | cd data-repo diff --git a/scripts/analyze-inactivity.js b/scripts/analyze-inactivity.js index 3f268c9b..cd2a0ba5 100644 --- a/scripts/analyze-inactivity.js +++ b/scripts/analyze-inactivity.js @@ -21,68 +21,69 @@ async function fetchData(url) { const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "..", "data"); console.log(`Using data directory: ${DATA_DIR}`); - console.log("Loading users..."); - const userFilePath = path.join(DATA_DIR, "users.json"); + console.log("Loading leaderboard users..."); + const overallFilePath = path.join(DATA_DIR, "overall.json"); let users = []; try { - const rawData = fs.readFileSync(userFilePath, "utf8"); + const rawData = fs.readFileSync(overallFilePath, "utf8"); users = JSON.parse(rawData); - console.log(`Loaded ${users.length} users from users.json`); + console.log(`Loaded ${users.length} users from overall.json`); } catch (err) { - console.error("Failed to load users.json: ", err.message); + console.error("Failed to load overall.json: ", err.message); process.exit(1); } const baseUrl = "https://leetcode-api-dun.vercel.app/"; const inactiveUsers = []; - const neverActiveUsers = []; const now = new Date(); console.log(" "); console.log("Starting inactivity analysis..."); for (const user of users) { - const profile = await fetchData(baseUrl + user.id); + if (!user.totalSolved || user.totalSolved === 0) { + console.log( + `${user.username || user.name || user.id}: skipped (0 solved / never active)`, + ); + continue; + } + + const username = user.username || user.id; + const profile = await fetchData(baseUrl + username); if (!profile) { - console.log(`${user.name}: skipped (API error)`); + console.log(`${username}: skipped (API error)`); continue; } const calendar = profile.submissionCalendar; const timestamps = calendar ? Object.keys(calendar).map(Number) : []; - // Case 1: Checking for users with absolutely no history (0 score baseline profiles) if (timestamps.length === 0) { - console.log(`${user.name}: Never Active`); - neverActiveUsers.push(user.id); + console.log(`${username}: skipped (no submission calendar)`); continue; } - // Case 2: Extracting the absolute latest submission unix timestamp marker const latestTimestampSeconds = Math.max(...timestamps); const lastActiveDate = new Date(latestTimestampSeconds * 1000); - // Compute day delta between execution runtime and user's last platform interaction const diffTime = Math.abs(now - lastActiveDate); const diffDays = Math.floor(diffTime / MS_IN_A_DAY); if (diffDays > THRESHOLD_DAYS) { - console.log(`${user.name}: Inactive (${diffDays} days ago)`); - inactiveUsers.push(user.id); + console.log(`${username}: Inactive (${diffDays} days ago)`); + inactiveUsers.push(username); } else { - console.log(`${user.name}: Active (${diffDays} days ago)`); + console.log(`${username}: Active (${diffDays} days ago)`); } } console.log("..."); console.log(" "); - // Formatting output schema matching maintainer's blueprint specification const outputData = { generatedAt: now.toISOString(), thresholdDays: THRESHOLD_DAYS, inactiveUsers: inactiveUsers.sort(), - neverActiveUsers: neverActiveUsers.sort(), }; console.log("Writing inactivity analysis data to inactive-users.json..."); From a81f1d44c095393a092c2ad4c34144f0fa2f7474 Mon Sep 17 00:00:00 2001 From: Yashaswini K P Date: Thu, 18 Jun 2026 14:44:36 +0530 Subject: [PATCH 3/6] feat: complete inactivity analysis logic and update cron schedule --- .github/workflows/analyze-inactivity.yml | 7 ++++--- scripts/analyze-inactivity.js | 9 ++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/analyze-inactivity.yml b/.github/workflows/analyze-inactivity.yml index 1e659ed9..ad585556 100644 --- a/.github/workflows/analyze-inactivity.yml +++ b/.github/workflows/analyze-inactivity.yml @@ -1,8 +1,9 @@ -name: Daily Inactivity Analysis +name: Scheduled Inactivity Analysis on: schedule: - - cron: "0 0 * * *" + # Triggers the inactivity scan at 05:45, 11:45, 17:45, and 23:45 + - cron: "45 5,11,17,23 * * *" workflow_dispatch: jobs: @@ -52,6 +53,6 @@ jobs: if git diff --staged --quiet; then echo "No changes detected in inactivity data." else - git commit -m "chore: update daily inactive-users.json snapshot" + git commit -m "chore: update scheduled inactive-users.json snapshot" git push fi diff --git a/scripts/analyze-inactivity.js b/scripts/analyze-inactivity.js index cd2a0ba5..8c3dad5e 100644 --- a/scripts/analyze-inactivity.js +++ b/scripts/analyze-inactivity.js @@ -41,14 +41,12 @@ async function fetchData(url) { console.log("Starting inactivity analysis..."); for (const user of users) { + const username = user.username || user.id; if (!user.totalSolved || user.totalSolved === 0) { - console.log( - `${user.username || user.name || user.id}: skipped (0 solved / never active)`, - ); + inactiveUsers.push(username); continue; } - const username = user.username || user.id; const profile = await fetchData(baseUrl + username); if (!profile) { console.log(`${username}: skipped (API error)`); @@ -59,7 +57,8 @@ async function fetchData(url) { const timestamps = calendar ? Object.keys(calendar).map(Number) : []; if (timestamps.length === 0) { - console.log(`${username}: skipped (no submission calendar)`); + console.log(`${username}: Inactive (no submission calendar)`); + inactiveUsers.push(username); continue; } From 3d961abe8a75b0e7d6c88fb2a9e55d35ef7be555 Mon Sep 17 00:00:00 2001 From: Yashaswini K P Date: Thu, 18 Jun 2026 21:27:30 +0530 Subject: [PATCH 4/6] fix: switch inactivity tracking source to users.json and fix live profile validation --- scripts/analyze-inactivity.js | 52 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/scripts/analyze-inactivity.js b/scripts/analyze-inactivity.js index 8c3dad5e..48fd66a7 100644 --- a/scripts/analyze-inactivity.js +++ b/scripts/analyze-inactivity.js @@ -7,6 +7,19 @@ const path = require("path"); const THRESHOLD_DAYS = 90; const MS_IN_A_DAY = 1000 * 60 * 60 * 24; +function atomicWrite(filePath, data) { + const dir = path.dirname(filePath); + const ext = path.extname(filePath); + const base = path.basename(filePath, ext); + const tmpPath = path.join( + dir, + `${base}.tmp.${process.pid}.${Date.now()}${ext}`, + ); + + fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8"); + fs.renameSync(tmpPath, filePath); +} + async function fetchData(url) { try { const res = await axios.get(url, { timeout: 15000 }); @@ -21,15 +34,15 @@ async function fetchData(url) { const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, "..", "data"); console.log(`Using data directory: ${DATA_DIR}`); - console.log("Loading leaderboard users..."); - const overallFilePath = path.join(DATA_DIR, "overall.json"); + console.log("Loading master user list..."); + const userFilePath = path.join(DATA_DIR, "users.json"); let users = []; try { - const rawData = fs.readFileSync(overallFilePath, "utf8"); + const rawData = fs.readFileSync(userFilePath, "utf8"); users = JSON.parse(rawData); - console.log(`Loaded ${users.length} users from overall.json`); + console.log(`Loaded ${users.length} users from users.json`); } catch (err) { - console.error("Failed to load overall.json: ", err.message); + console.error("Failed to load users.json: ", err.message); process.exit(1); } @@ -38,14 +51,10 @@ async function fetchData(url) { const now = new Date(); console.log(" "); - console.log("Starting inactivity analysis..."); + console.log("Starting daily full sync inactivity analysis..."); for (const user of users) { - const username = user.username || user.id; - if (!user.totalSolved || user.totalSolved === 0) { - inactiveUsers.push(username); - continue; - } + const username = user.id; const profile = await fetchData(baseUrl + username); if (!profile) { @@ -53,11 +62,22 @@ async function fetchData(url) { continue; } + const easySolved = profile.easySolved || 0; + const mediumSolved = profile.mediumSolved || 0; + const hardSolved = profile.hardSolved || 0; + const totalSolved = easySolved + mediumSolved + hardSolved; + + if (totalSolved === 0) { + console.log(`💤 ${username}: Inactive (0 questions solved)`); + inactiveUsers.push(username); + continue; + } + const calendar = profile.submissionCalendar; const timestamps = calendar ? Object.keys(calendar).map(Number) : []; if (timestamps.length === 0) { - console.log(`${username}: Inactive (no submission calendar)`); + console.log(`💤 ${username}: Inactive (no submission calendar history)`); inactiveUsers.push(username); continue; } @@ -69,10 +89,10 @@ async function fetchData(url) { const diffDays = Math.floor(diffTime / MS_IN_A_DAY); if (diffDays > THRESHOLD_DAYS) { - console.log(`${username}: Inactive (${diffDays} days ago)`); + console.log(`💤 ${username}: Inactive (${diffDays} days ago)`); inactiveUsers.push(username); } else { - console.log(`${username}: Active (${diffDays} days ago)`); + console.log(`✅ ${username}: Active (${diffDays} days ago)`); } } @@ -88,8 +108,8 @@ async function fetchData(url) { console.log("Writing inactivity analysis data to inactive-users.json..."); const outputPath = path.join(DATA_DIR, "inactive-users.json"); try { - fs.writeFileSync(outputPath, JSON.stringify(outputData, null, 2), "utf8"); - console.log("Inactivity analysis data saved successfully"); + atomicWrite(outputPath, outputData); + console.log("Inactivity analysis data updated successfully!"); } catch (err) { console.error("Failed to write inactive-users.json: ", err.message); process.exit(1); From 9d64ac0e93031677a31cd6c1577b0696fc194f87 Mon Sep 17 00:00:00 2001 From: Yashaswini K P Date: Thu, 18 Jun 2026 22:36:07 +0530 Subject: [PATCH 5/6] Refactor inactivity analysis logic Removed inactive user check based on solved questions. --- scripts/analyze-inactivity.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/scripts/analyze-inactivity.js b/scripts/analyze-inactivity.js index 48fd66a7..2ab291b2 100644 --- a/scripts/analyze-inactivity.js +++ b/scripts/analyze-inactivity.js @@ -61,18 +61,7 @@ async function fetchData(url) { console.log(`${username}: skipped (API error)`); continue; } - - const easySolved = profile.easySolved || 0; - const mediumSolved = profile.mediumSolved || 0; - const hardSolved = profile.hardSolved || 0; - const totalSolved = easySolved + mediumSolved + hardSolved; - - if (totalSolved === 0) { - console.log(`💤 ${username}: Inactive (0 questions solved)`); - inactiveUsers.push(username); - continue; - } - + const calendar = profile.submissionCalendar; const timestamps = calendar ? Object.keys(calendar).map(Number) : []; From f546cd8c7e39782cef6e0f5158768f419f827851 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Jun 2026 17:08:19 +0000 Subject: [PATCH 6/6] style: auto-format code with Prettier (/format) --- scripts/analyze-inactivity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analyze-inactivity.js b/scripts/analyze-inactivity.js index 2ab291b2..397ced1e 100644 --- a/scripts/analyze-inactivity.js +++ b/scripts/analyze-inactivity.js @@ -61,7 +61,7 @@ async function fetchData(url) { console.log(`${username}: skipped (API error)`); continue; } - + const calendar = profile.submissionCalendar; const timestamps = calendar ? Object.keys(calendar).map(Number) : [];