Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/ci-dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Dashboard CI

on:
push:
branches: [main, rebranding/bishop-state]
paths:
- "codebenders-dashboard/**"
- ".github/workflows/ci-dashboard.yml"
pull_request:
branches: [main, rebranding/bishop-state]
paths:
- "codebenders-dashboard/**"
- ".github/workflows/ci-dashboard.yml"

defaults:
run:
working-directory: codebenders-dashboard

jobs:
ci:
name: Type check · Lint · Build
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: npm install

- name: TypeScript type check
run: npx tsc --noEmit

- name: Lint
run: npm run lint

- name: Build
run: npm run build
env:
# Provide placeholder values so the build doesn't fail on missing env assertions.
# API routes and Supabase calls are opt-in at runtime; they are not executed during build.
NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY: placeholder-anon-key
DB_HOST: localhost
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: postgres
DB_NAME: postgres
44 changes: 44 additions & 0 deletions .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Python CI

on:
push:
branches: [main, rebranding/bishop-state]
paths:
- "ai_model/**"
- "operations/**"
- "requirements.txt"
- ".github/workflows/ci-python.yml"
pull_request:
branches: [main, rebranding/bishop-state]
paths:
- "ai_model/**"
- "operations/**"
- "requirements.txt"
- ".github/workflows/ci-python.yml"

jobs:
ci:
name: Lint · Deps · Syntax check
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Install ruff
run: pip install ruff

- name: Lint (ruff)
run: ruff check ai_model/ operations/

- name: Install dependencies
run: pip install -r requirements.txt

- name: Syntax check — entry points
run: |
python -m py_compile ai_model/complete_ml_pipeline.py
python -m py_compile operations/db_config.py
57 changes: 57 additions & 0 deletions .github/workflows/deploy-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Deploy Preview

on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
deploy:
name: Vercel preview deployment
runs-on: ubuntu-latest
# Only run when Vercel secrets are configured (skips forks / contributors without access)
if: ${{ vars.VERCEL_PROJECT_ID != '' }}

permissions:
pull-requests: write # needed to post the preview URL comment

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install Vercel CLI
run: npm install --global vercel@latest

- name: Pull Vercel environment (preview)
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

- name: Build
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

- name: Deploy
id: deploy
run: |
url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> "$GITHUB_OUTPUT"
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

- name: Post preview URL to PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Preview deployment\n\n🚀 **${{ steps.deploy.outputs.url }}**\n\nDeployed from ${context.sha.slice(0, 7)}.`,
})
47 changes: 47 additions & 0 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Deploy Production

on:
push:
branches: [main]

jobs:
deploy:
name: Vercel production deployment
runs-on: ubuntu-latest
# Only run when Vercel secrets are configured
if: ${{ vars.VERCEL_PROJECT_ID != '' }}

environment:
name: production
url: ${{ steps.deploy.outputs.url }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install Vercel CLI
run: npm install --global vercel@latest

- name: Pull Vercel environment (production)
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

- name: Build
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

- name: Deploy
id: deploy
run: |
url=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> "$GITHUB_OUTPUT"
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
48 changes: 48 additions & 0 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Security Audit

on:
schedule:
- cron: "0 9 * * 1" # Every Monday at 09:00 UTC
push:
branches: [main]
workflow_dispatch:

jobs:
audit-npm:
name: npm audit (dashboard)
runs-on: ubuntu-latest

defaults:
run:
working-directory: codebenders-dashboard

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: npm install

- name: Audit
run: npm audit --audit-level=high

audit-python:
name: pip-audit (ML pipeline)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Install pip-audit
run: pip install pip-audit

- name: Audit
run: pip-audit -r requirements.txt
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Desktop.ini
*.csv
*.xlsx
*.xls
*.pptx
*.json
!package.json
!tsconfig.json
Expand Down Expand Up @@ -158,6 +159,12 @@ docker-compose.override.yml
# Git worktrees
.worktrees/

# Large presentation/doc files
docs/AI-Powered-Student-Success-Analytics.pptx
docs/Copy-of-AI-Powered-Student-Success-Analytics.pdf
docs/CodeBenders-PRD_Student_Success_Analytics.pdf
DOCUMENTATION_ISSUES.md

# Misc
.cache/
*.seed
Expand Down
17 changes: 14 additions & 3 deletions ai_model/complete_ml_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,17 @@ def assign_credential_type(row):
else:
return 2 # Default to Associate's (most common at community colleges)

# No credential completed
# Priority 5: No completion data — fall back to credential type sought as proxy
# (represents "what credential is this student on track for")
credential_sought = str(row.get('Credential_Type_Sought_Year_1', ''))
if credential_sought in ['01', '02', '03', 'C1', 'C2']:
return 1 # Certificate-track
elif credential_sought in ['A', '04', '05']:
return 2 # Associate-track
elif credential_sought in ['B', '06', '07', '08']:
return 3 # Bachelor-track

# No credential completed or sought
return 0 # No credential

df['target_credential_type'] = df.apply(assign_credential_type, axis=1)
Expand Down Expand Up @@ -646,6 +656,7 @@ def assign_alert_level(risk_score):
n_estimators=50,
max_depth=5,
min_samples_split=30,
class_weight='balanced',
random_state=42,
n_jobs=-1
)
Expand Down Expand Up @@ -734,7 +745,7 @@ def assign_alert_level(risk_score):
# Only include students who attempted gateway math (not NaN)
gateway_math_raw = df['CompletedGatewayMathYear1']
valid_idx = gateway_math_raw.notna()
y_gateway_math = (gateway_math_raw[valid_idx] == 'C').astype(int)
y_gateway_math = (gateway_math_raw[valid_idx] == 'Y').astype(int)
X_gateway_math = X_gateway_math_clean[valid_idx]

print(f"\nDataset size: {len(X_gateway_math):,} students")
Expand Down Expand Up @@ -845,7 +856,7 @@ def assign_alert_level(risk_score):
# Only include students who attempted gateway English (not NaN)
gateway_english_raw = df['CompletedGatewayEnglishYear1']
valid_idx = gateway_english_raw.notna()
y_gateway_english = (gateway_english_raw[valid_idx] == 'C').astype(int)
y_gateway_english = (gateway_english_raw[valid_idx] == 'Y').astype(int)
X_gateway_english = X_gateway_english_clean[valid_idx]

print(f"\nDataset size: {len(X_gateway_english):,} students")
Expand Down
10 changes: 10 additions & 0 deletions codebenders-dashboard/app/actions/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use server"

import { createClient } from "@/lib/supabase/server"
import { redirect } from "next/navigation"

export async function signOut() {
const supabase = await createClient()
await supabase.auth.signOut()
redirect("/login")
}
Loading
Loading