Skip to content

leepeffer/subrina

Repository files navigation

Sub-Bot

A substitute teacher management app for K-12 school operations. Sub-Bot helps administrators quickly find and assign available substitute teachers when absences are reported.

Features

  • Teacher Profiles - Full schedule grids (8 periods x 5 days), nickname, teacher type, cannot-subject restrictions, availability preferences, max subs per week
  • Absence Reporting - Report same-day or future absences with period and subject details
  • Smart Sub Matching - Ranked algorithm that filters by availability, subject match, workload balance, and teacher preferences
  • AI-Powered CSV Import - Import teacher schedules from per-teacher CSV files using AI (Claude, GPT, Gemini, or OpenRouter), including per-user OpenAI API key/OAuth option, with regex fallback
  • Telegram General AI Agent - Understand absence reports, answer availability queries, and handle safe general Q&A with always-confirm-before-write behavior
  • In-App Subrina Chat - Dedicated /chat experience with persisted history, hybrid streaming (Q&A tokens), and the same operational AI capabilities as Telegram
  • Semester Calendar - Configured with real school dates, period times, and holidays
  • Dashboard - At-a-glance view of today's absences, coverage stats, and quick actions

Tech Stack

  • Next.js 16 (App Router, TypeScript, Tailwind CSS v4)
  • JSON file storage with file locking (no database required)
  • AI parsing via Anthropic, OpenAI, Google Gemini, or OpenRouter SDKs
  • Zod for validation
  • PapaParse for CSV processing
  • date-fns for date utilities

Getting Started

Prerequisites

  • Node.js 18+
  • npm

Installation

git clone https://github.com/leepeffer/sub-bot.git
cd sub-bot
npm install

Configuration

Copy the environment template and add your AI provider API key:

cp .env.local.example .env.local

Edit .env.local:

# Pick a provider: anthropic | openai | gemini | openrouter
AI_PROVIDER=anthropic

# Add the API key for your chosen provider
ANTHROPIC_API_KEY=sk-ant-...

Configure Firebase for both browser auth and server access:

# Firebase Web SDK (client)
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=

# Firebase Admin SDK (server)
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=

Configure sign-in access policy:

# Demo-safe default: deny by default unless explicitly allowlisted.
AUTH_ALLOW_ALL=false
AUTH_ALLOWED_DOMAINS=example.org
AUTH_ALLOWED_EMAILS=admin@example.org

Optional OpenAI OAuth setup (for per-user connect flow in Settings):

OPENAI_OAUTH_CLIENT_ID=
OPENAI_OAUTH_CLIENT_SECRET=
OPENAI_OAUTH_REDIRECT_URI=http://localhost:3000/api/ai/openai/oauth/callback
OPENAI_OAUTH_SCOPES=openid profile email offline_access

Optional Telegram intent AI setup:

# Server defaults (can be overridden in Settings -> Telegram Intent AI)
TELEGRAM_INTENT_AI_ENABLED=false
# Enables multi-intent Telegram routing (availability + Q&A)
TELEGRAM_GENERAL_AGENT_ENABLED=false
# Provider for Telegram message parsing: openrouter | openai | anthropic
TELEGRAM_INTENT_AI_PROVIDER=openrouter
# Optional model override
# TELEGRAM_INTENT_AI_MODEL=x-ai/grok-4.1-fast
# Optional reasoning/thinking level: off | low | medium | high
TELEGRAM_INTENT_AI_REASONING_LEVEL=off
TELEGRAM_INTENT_AI_MIN_CONFIDENCE=0.65
TELEGRAM_INTENT_AI_TIMEOUT_MS=12000
TELEGRAM_INTENT_AI_MAX_OCCURRENCES=20

# Required for strict CLI wrapper mode in Telegram General Agent
TELEGRAM_AGENT_API_TOKEN=sbt_live_...

Optional in-app Subrina chat defaults:

# Independent feature flag for /chat (overridable in Settings UI)
SUBRINA_WEB_CHAT_ENABLED=false
# Retention window for persisted chat history
SUBRINA_CHAT_RETENTION_DAYS=30
# Optional dedicated CLI wrapper auth token for in-app chat
# Falls back to TELEGRAM_AGENT_API_TOKEN if omitted
SUBRINA_AGENT_API_TOKEN=sbt_live_...

Live Demo Safety Mode

For temporary public demos, use these environment flags:

DEMO_READ_ONLY=true
DEMO_DISABLE_AI=true
DEMO_DISABLE_TELEGRAM=true
DEMO_DISABLE_TOKEN_MANAGEMENT=true

Recommended deployment posture:

  • Use a separate Firebase demo project (not production).
  • Keep allowlist auth enabled (AUTH_ALLOW_ALL=false + explicit allowlist).
  • Use read-only mode so authenticated users cannot mutate data.

Telegram intent provider/key resolution order:

  1. Saved Settings UI configuration (/settings -> Telegram Intent AI) + provider key
  2. Env defaults (TELEGRAM_INTENT_AI_PROVIDER, then AI_PROVIDER) + provider key
  3. Regex parser fallback (no LLM)

Subrina in-app chat settings resolution order:

  1. Saved Settings UI configuration (/settings -> Subrina In-App Chat)
  2. Env defaults (SUBRINA_WEB_CHAT_ENABLED, SUBRINA_CHAT_RETENTION_DAYS)
  3. Chat disabled (if not enabled by either)

Note: AI is optional. If no provider is configured, CSV import falls back to a regex-based parser.

Production Secrets (Firebase Hosting + SSR Function)

This project currently deploys with Firebase Hosting + frameworksBackend (SSR Cloud Function), not Firebase App Hosting.

For Telegram runtime secrets, use Google Cloud Secret Manager with these exact secret ids:

  • telegram-bot-token
  • telegram-webhook-secret

The app reads those secrets directly at runtime via Secret Manager API.

Grant your SSR runtime service account access to both secrets (roles/secretmanager.secretAccessor). Without this IAM binding, Telegram setup/webhook calls will fail with permission errors.

Required production environment config (set in GitHub for deploy):

  • Repository variable: APP_BASE_URL (for example https://sub-bot-413a6.web.app)
  • Repository variable: FIREBASE_PROJECT_ID (demo project id for temporary public demo)
  • Repository variable: AUTH_ALLOW_ALL (default false)
  • Repository variable: AUTH_ALLOWED_DOMAINS (comma-separated)
  • Repository variable: AUTH_ALLOWED_EMAILS (comma-separated)
  • Repository variable: DEMO_READ_ONLY (recommended true)
  • Repository variable: DEMO_DISABLE_AI (recommended true)
  • Repository variable: DEMO_DISABLE_TELEGRAM (recommended true)
  • Repository variable: DEMO_DISABLE_TOKEN_MANAGEMENT (recommended true)
  • Repository secret: TELEGRAM_WEBHOOK_ADMIN_EMAILS (comma-separated admin emails)
  • Optional repository variable: SCHOOL_TIMEZONE (default America/Sao_Paulo)
  • For Telegram AI parser in production, also set at least one provider key secret:
    • OPENROUTER_API_KEY (recommended), or OPENAI_API_KEY, or ANTHROPIC_API_KEY
  • For strict Telegram CLI write/read tool mode, set secret:
    • TELEGRAM_AGENT_API_TOKEN
  • For strict in-app Subrina chat CLI write/read tool mode (optional dedicated token):
    • SUBRINA_AGENT_API_TOKEN (falls back to TELEGRAM_AGENT_API_TOKEN)

Then deploy:

npx firebase-tools@latest deploy --force --project <your-firebase-project-id>

Note: apphosting.yaml is present in this repo, but it does not control the current Hosting+SSR runtime.

Run

npm run dev

Open http://localhost:3000.

Project Structure

sub-bot/
├── data/                           # JSON data files (teachers, absences, semester config)
├── src/
│   ├── app/
│   │   ├── page.tsx                # Dashboard
│   │   ├── teachers/               # Teacher list, profiles, edit, import
│   │   │   ├── import-schedule/    # AI-powered per-teacher CSV import
│   │   │   └── import/             # Bulk CSV import (tabular format)
│   │   ├── absences/               # Absence reporting and sub assignment
│   │   ├── calendar/               # Semester calendar view
│   │   ├── chat/                   # In-app Subrina chat interface
│   │   ├── settings/               # Semester config, period times, holidays
│   │   └── api/                    # REST API routes
│   │       ├── teachers/           # CRUD + import endpoints
│   │       ├── absences/           # CRUD + assignment
│   │       ├── matching/           # Sub matching algorithm
│   │       ├── semester/           # Semester config
│   │       └── stats/              # Dashboard statistics
│   ├── components/                 # Reusable React components
│   ├── lib/
│   │   ├── ai/                     # AI provider abstraction (4 providers)
│   │   ├── matching.ts             # Sub matching & ranking algorithm
│   │   ├── schedule-csv-parser.ts  # Regex fallback CSV parser
│   │   ├── data.ts                 # JSON file persistence layer
│   │   ├── dates.ts                # School date utilities
│   │   └── validation.ts           # Zod schemas
│   └── types/index.ts              # TypeScript interfaces
└── .env.local                      # API keys (gitignored)

Sub Matching Algorithm

When an absence is reported, Sub-Bot finds and ranks available substitutes:

Hard filters (disqualify a teacher):

  1. Not the absent teacher
  2. Not also absent that day
  3. Not teaching during the requested period(s)
  4. Not already assigned as sub elsewhere
  5. Must not be restricted from the needed subject
  6. Must not exceed weekly sub limit

Soft scoring (rank remaining candidates from a base of 100):

Factor Impact
Prefers this slot +15/period
Prefers NOT this slot -30/period
Weekly sub count -10/sub
Semester sub count -1/sub
Is a TA -5
Already subbing adjacent period +20 (continuity)

AI CSV Import

Teacher schedule CSVs are expected as one file per teacher in a grid format. Sub-Bot supports 4 AI providers to intelligently parse these, with automatic fallback to regex:

AI configured? ──YES──> Call provider ──> Validate ──> Confidence >= 50%? ──YES──> Use AI result
      │                                                       │
      NO                                                     NO
      │                                                       │
      └──────────────────> Regex fallback <───────────────────┘

Configure in .env.local:

  • anthropic - Claude (default: claude-sonnet-4-20250514)
  • openai - GPT (default: gpt-4o)
  • gemini - Gemini (default: gemini-2.0-flash)
  • openrouter - Any model via OpenRouter (default: anthropic/claude-sonnet-4)

Credential precedence for schedule import:

  1. Signed-in user's OpenAI credential from Settings (API key or OAuth)
  2. Server-level provider from AI_PROVIDER + provider API key
  3. Regex fallback parser

Telegram General AI Agent

Telegram now uses an AI-first multi-intent router with strict JSON schema validation and normalization:

Telegram message -> AI parser (optional) -> schema validation -> normalize dates/periods
                 -> intent routing (report_absence | availability_query | general_qna | unknown)
                 -> confidence + required fields check -> accept
                 -> else regex fallback for operational intents -> accept/clarify

Supported patterns:

  • Single date: "Maria is out periods 1,2,3 tomorrow"
  • Time range: "Maria absent 09:00-11:10 2026-03-02"
  • Date range: "Maria periods 2-4 from 2026-03-02 to 2026-03-06"
  • Recurring: "Maria absent all day every Monday until 2026-04-30"
  • Availability: "Who's available to cover sixth period today?"

Behavior:

  • report_absence: always confirms before write, then upserts absence and suggests substitutes.
  • availability_query: returns ranked free teachers for requested date/period.
  • general_qna: answers in chat mode with no writes/tool side effects.
  • Single-date absence keeps interactive substitute assignment buttons in Telegram.
  • Multi-date absence creates/updates each date and returns substitute suggestions per date in a digest.
  • Existing teacher/date absences are upserted (periods merged) instead of duplicated.
  • In General Agent mode, operational actions prefer the strict internal CLI gateway (node dist-cli/cli/index.js ... --json).
  • Default OpenRouter model for Telegram intent parsing is x-ai/grok-4.1-fast when no model override is set.

In-App Subrina Chat

  • Route: /chat (authenticated users only)
  • Backed by one active thread per user, stored in Firestore with message retention
  • Operational actions always require explicit confirmation before write operations
  • Q&A responses stream tokens in real time; operational actions return final structured responses
  • Uses the same intent parsing/provider configuration as Telegram AI settings

Current Semester

Default demo semester configuration:

  • Dates: January 22 - June 11, 2026
  • Periods: 8 per day (8:13 AM - 3:25 PM)
  • Holidays: 11 days (Carnaval, Good Friday, Tiradentes, Labor Day, Corpus Christi, etc.)

License

No license file is included yet. Add one before public redistribution if needed.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages