Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Brief description of changes
- [ ] No new warnings generated
- [ ] Tests added/updated and passing
- [ ] Dependencies updated (if applicable)
- [ ] Any route taking a user/student/`*_id` (path, query, or body) calls the object-access helper (`assert_can_access_student`) or is intentionally public

## Related Issues

Expand Down
22 changes: 6 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,6 @@ ELO_MIN_RATING=400
ELO_MAX_RATING=2000

# External Services (Optional)
RAILS_APP_URL=https://your-rails-app.com
WEBHOOK_SECRET=your-webhook-secret
```

Expand Down Expand Up @@ -411,24 +410,15 @@ python run_server.py

### Authentication

The API supports two authentication methods:

1. **User Authentication (self-hosted JWT)**
- Required for user-facing endpoints
- Include the token in the Authorization header:
```
Authorization: Bearer <your-jwt-token>
```

2. **Service-to-Service Authentication (API Key)**
- Required for service-to-service calls
- Include the API key in the X-API-Key header:
```
X-API-Key: <your-api-key>
```
All endpoints use self-hosted JWT authentication. Include the token in the Authorization header:
```
Authorization: Bearer <your-jwt-token>
```

**Note:** Health check endpoints (`/`, `/health`) do not require authentication.

There is no `X-API-Key` service-to-service auth in this codebase — it was documented but never implemented. If a service-to-service integration (e.g. a future Rails caller) is built later, it must use a scoped identity that still passes the object-ownership check (see `_docs/active/API_CONTRACTS.md`).

### Error Handling

The API returns standardized error responses:
Expand Down
29 changes: 14 additions & 15 deletions _docs/DEMO_USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The AI Study Companion is a **persistent AI agent** that lives between tutoring
- **Conversational Q&A**: Answers questions with full conversation context
- **Smart Suggestions**: Prevents churn by suggesting next learning paths
- **Proactive Engagement**: Nudges students at risk of churning
- **Seamless Integration**: RESTful API ready for Rails/React platform
- **Seamless Integration**: RESTful API used by the React platform (JWT auth); a Rails caller is aspirational and not implemented

---

Expand All @@ -35,7 +35,7 @@ All accounts use password: `demo123`

### **Opening (1 minute)**

> "I'm going to show you the AI Study Companion - a persistent AI agent that lives between tutoring sessions. It remembers previous lessons, assigns adaptive practice, answers questions conversationally, and drives students back to human tutors when needed. Everything integrates seamlessly with our existing Rails/React platform via RESTful APIs."
> "I'm going to show you the AI Study Companion - a persistent AI agent that lives between tutoring sessions. It remembers previous lessons, assigns adaptive practice, answers questions conversationally, and drives students back to human tutors when needed. Everything integrates seamlessly with our React platform via RESTful APIs."

**Key Points:**
- Persistent AI companion (not just a chatbot)
Expand Down Expand Up @@ -396,18 +396,18 @@ POST /api/v1/goals/{goal_id}/reset

---

## 🔗 Rails/React Platform Integration
## 🔗 React Platform Integration

### **How It Integrates**

The AI Study Companion is built as a **standalone FastAPI service** that integrates with your existing Rails/React platform via RESTful APIs.
The AI Study Companion is built as a **standalone FastAPI service** that integrates with the React frontend via RESTful APIs, using self-hosted JWT (not AWS Cognito). A Rails backend integration (sections below marked NOT IMPLEMENTED) is aspirational only — there is no Rails app in this codebase.

#### **1. Authentication Integration**

```javascript
// React Frontend
// Uses existing AWS Cognito JWT tokens
const token = await getCognitoToken(); // Your existing auth
// Uses self-hosted JWT tokens issued by this service
const token = getStoredAuthToken(); // Your existing auth
fetch('https://api.pennygadget.ai/v1/progress/user123', {
headers: {
'Authorization': `Bearer ${token}`
Expand All @@ -416,15 +416,14 @@ fetch('https://api.pennygadget.ai/v1/progress/user123', {
```

**Backend Support:**
- Accepts AWS Cognito JWT tokens
- Validates tokens using `python-jose`
- Issues and validates self-hosted JWT tokens (HS256)
- Extracts user info from token claims
- Development mode supports mock tokens for testing

#### **2. Session Summary Integration**
#### **2. Session Summary Integration (NOT IMPLEMENTED — aspirational, future integration only)**

```ruby
# Rails Backend
# Rails Backend (aspirational, not implemented — no Rails app exists in this codebase)
# After a tutoring session completes
def create_session_summary(session)
response = HTTParty.post(
Expand All @@ -449,7 +448,7 @@ def create_session_summary(session)
end
```

**What This Enables:**
**What This Would Enable (if built):**
- Automatic AI summaries after each session
- Summaries stored in AI Companion database
- Accessible via API for display in React frontend
Expand Down Expand Up @@ -554,7 +553,7 @@ async function askQuestion(query) {
- Follow-up question support
- Persistent conversation history

#### **6. Webhook Integration (Event-Driven)**
#### **6. Webhook Integration (Event-Driven) (NOT IMPLEMENTED — aspirational, future integration only)**

```ruby
# Rails Backend
Expand Down Expand Up @@ -598,7 +597,7 @@ end
- Automatic updates in Rails app
- Event history and retry logic

#### **7. LMS Integration (Canvas/Blackboard)**
#### **7. LMS Integration (Canvas/Blackboard) (NOT IMPLEMENTED — aspirational, future integration only)**

```ruby
# Rails Backend
Expand Down Expand Up @@ -771,7 +770,7 @@ python scripts/verify_demo_users.py
6. **Visual Progress Tracking**: Interactive pie chart on dashboard with goal names and completion percentages
7. **Goal-Focused Practice**: Practice dropdown only shows subjects from goals; auto-creates goals if none exist
8. **Rich Q&A Formatting**: Markdown rendering for code blocks, lists, headings, and formatted explanations
9. **Seamless Integration**: RESTful API ready for Rails/React
9. **Seamless Integration**: RESTful API used by React (JWT auth); Rails is aspirational, not implemented
10. **Proactive Engagement**: Nudges at-risk students automatically
11. **Cross-Subject Learning**: Builds comprehensive learning paths
12. **Math Accuracy**: SymPy for reliable math problem generation
Expand All @@ -786,7 +785,7 @@ python scripts/verify_demo_users.py
All demo accounts are pre-configured and ready. The system demonstrates:
- ✅ All retention enhancement requirements
- ✅ Complete feature set
- ✅ Rails/React integration examples
- ✅ React integration examples (Rails integration examples are aspirational, not implemented)
- ✅ Real-world use cases

**Start with the Quick Demo Script (15 minutes) and expand as needed!**
17 changes: 9 additions & 8 deletions _docs/active/API_CONTRACTS.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# 🔌 API Contracts
**Product:** AI Study Companion MVP
**Integration:** Rails/React Application
**Integration:** React Application
**Version:** 1.0.0

---

## Overview

This document defines the REST API contracts for integrating the AI Study Companion service with the existing Rails/React platform. All endpoints use JSON for request/response bodies.
This document defines the REST API contracts for the AI Study Companion service. All endpoints use JSON for request/response bodies. The real, implemented caller is the React frontend. A Rails backend integration is an aspirational future possibility only — it was never built, and the code below that describes it was never implemented.

**Base URL:** `https://api.pennygadget.ai/v1` (or configured environment variable)

**Authentication:**
- **Service-to-Service:** API Key in `X-API-Key` header
- **User Requests:** JWT token from AWS Cognito in `Authorization: Bearer <token>` header
- **All endpoints:** JWT token in `Authorization: Bearer <token>` header (self-hosted JWT, not AWS Cognito)
- **Not implemented:** The `X-API-Key` service-to-service header described elsewhere in this document was documented from the founding commit but never implemented in code. `settings.ai_service_api_key` has been removed. If a service-to-service integration is built later, it must use a scoped identity that still passes the object-ownership check (see #67/#68).

---

Expand Down Expand Up @@ -496,7 +496,9 @@ Get multi-goal progress dashboard data.

---

## Rails Integration Examples
## Rails Integration Examples (NOT IMPLEMENTED — aspirational, future integration only)

The Ruby examples below describe a Rails service-to-service caller using an `X-API-Key` header. This was never built: there is no Rails app in this codebase, and the backend does not check `X-API-Key` at all. Kept here only as a sketch of what a future service-to-service integration might look like; any real implementation must use a scoped identity that still passes the object-ownership check (#67/#68), not a shared static API key.

### Ruby Client Class

Expand Down Expand Up @@ -687,15 +689,14 @@ export const useAIQuery = () => {

## Rate Limiting

- **Service-to-Service:** 1000 requests/minute per API key
- **User Requests:** 100 requests/minute per user
- **Headers:** `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`

---

## Webhooks (Optional - POST-MVP)
## Webhooks (Optional - POST-MVP, NOT IMPLEMENTED)

For real-time updates, the service can send webhooks to Rails app:
Aspirational, not implemented. For real-time updates, the service could in the future send webhooks to a Rails app:

```
POST https://your-rails-app.com/webhooks/ai-service
Expand Down
20 changes: 10 additions & 10 deletions _docs/active/IMPLEMENTATION_PRIORITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,29 +267,29 @@ def calculate_performance(answer, correct_answer, time_taken, hints_used):

---

### 🟢 **PRIORITY 9: Rails/React Integration Points**
**Question #2: Separate Service Integrating with Rails App**
### 🟢 **PRIORITY 9: React Integration Points**
**Question #2: Separate Service, React as the Real Caller (Rails is Aspirational, Not Implemented)**

**Decision:** RESTful API with clear contracts
**Decision:** RESTful API with clear contracts. The React frontend is the actual, implemented caller. A Rails app was planned early on but never built — there is no Rails app in this codebase.

**Reasoning:**
- **Separation of Concerns:** Microservice architecture
- **Technology Flexibility:** Can use Python/Node.js for AI features
- **Scalability:** Independent scaling of AI service vs Rails app
- **Scalability:** Independent scaling of AI service vs any future caller

**API Contract Design:**
```
POST /api/v1/transcripts # Rails → AI Service (session complete)
GET /api/v1/summaries/:user_id # Rails → AI Service (fetch summaries)
POST /api/v1/practice/assign # Rails → AI Service (request practice)
POST /api/v1/transcripts # React → AI Service (session complete)
GET /api/v1/summaries/:user_id # React → AI Service (fetch summaries)
POST /api/v1/practice/assign # React → AI Service (request practice)
POST /api/v1/qa/query # React → AI Service (student query)
GET /api/v1/progress/:user_id # React → AI Service (dashboard)
POST /api/v1/overrides # Rails → AI Service (tutor override)
POST /api/v1/overrides # React → AI Service (tutor override)
```

**Authentication:**
- API keys for service-to-service (Rails → AI Service)
- JWT tokens for user-facing requests (React → AI Service)
- JWT tokens for user-facing requests (React → AI Service) — this is what's implemented.
- Service-to-service (e.g. a future Rails caller): not implemented. If built later, it must use a scoped identity that still passes the object-ownership check (#67/#68), not a shared static API key.

---

Expand Down
12 changes: 6 additions & 6 deletions _docs/active/PROJECT_STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,13 @@ LOG_LEVEL=INFO

---

## Integration with Rails App
## Integration with Rails App (NOT IMPLEMENTED — aspirational, future integration only)

### API Contract
The Rails app will call this service via REST API:
There is no Rails app in this codebase. A Rails caller was documented from early planning but never built; the real, implemented caller is the React frontend using JWT. The snippet below is kept only as a sketch of what a future service-to-service integration might look like.

### API Contract
```ruby
# Rails example
# Rails example (aspirational, not implemented)
class AIServiceClient
BASE_URL = ENV['AI_SERVICE_URL']

Expand All @@ -342,8 +342,8 @@ end
```

### Authentication
- Service-to-service: API keys
- User requests: JWT tokens from Cognito
- User requests: self-hosted JWT tokens (`Authorization: Bearer <jwt>`) — this is what's actually implemented.
- Service-to-service: not implemented. If built later, it must use a scoped identity that still passes the object-ownership check (#67/#68), not a shared static API key.

---

Expand Down
26 changes: 5 additions & 21 deletions _docs/guides/FRONTEND_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,22 @@ npm install axios

## 🔐 **Authentication**

### **AWS Cognito Integration**
### **JWT Authentication**

The API uses JWT tokens from AWS Cognito for user authentication.
All endpoints use self-hosted JWT tokens (not AWS Cognito) for authentication:

```javascript
// Get token from Cognito
import { Auth } from 'aws-amplify';

async function getAuthToken() {
try {
const session = await Auth.currentSession();
return session.getIdToken().getJwtToken();
} catch (error) {
console.error('Error getting token:', error);
return null;
}
return localStorage.getItem('authToken');
}

// Store token
localStorage.setItem('authToken', token);
```

### **Service-to-Service Authentication**

For service-to-service requests (e.g., from Rails backend), use API key:
### **Service-to-Service Authentication (NOT IMPLEMENTED)**

```javascript
const headers = {
'X-API-Key': process.env.REACT_APP_API_KEY,
};
```
There is no `X-API-Key` service-to-service auth in this codebase — it was documented but never implemented, and the backend does not check this header. If a service-to-service integration (e.g. a future Rails backend) is built later, it must use a scoped identity that still passes the object-ownership check (see `_docs/active/API_CONTRACTS.md`, #67/#68).

---

Expand Down Expand Up @@ -387,7 +372,6 @@ Create `.env` file:

```env
REACT_APP_API_URL=http://localhost:8000/api/v1
REACT_APP_API_KEY=your-api-key-here
```

---
Expand Down
4 changes: 3 additions & 1 deletion examples/api-client/apiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ function getAuthToken() {
}

/**
* Get API key for service-to-service requests
* Get API key for service-to-service requests.
* NOTE: The backend does not implement X-API-Key auth (documented but never
* built — see _docs/active/API_CONTRACTS.md). This fallback is inert.
*/
function getApiKey() {
return process.env.REACT_APP_API_KEY || '';
Expand Down
2 changes: 2 additions & 0 deletions examples/api-client/apiClientAxios.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const apiClient = axios.create({
apiClient.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken');
// NOTE: The backend does not implement X-API-Key auth (documented but
// never built — see _docs/active/API_CONTRACTS.md). This fallback is inert.
const apiKey = process.env.REACT_APP_API_KEY;

if (token) {
Expand Down
21 changes: 21 additions & 0 deletions migrations/004_add_parent_student_assignments.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- Add Parent-Student Assignments Table
-- Migration 004: parent<->student linkage for scoped parent dashboard access (#68)
-- Links are admin/seed-provisioned; there is no self-service linking endpoint.

CREATE TABLE IF NOT EXISTS parent_student_assignments (
parent_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
student_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed')),

created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (parent_id, student_id)
);

CREATE INDEX IF NOT EXISTS idx_psa_parent ON parent_student_assignments(parent_id);
CREATE INDEX IF NOT EXISTS idx_psa_student ON parent_student_assignments(student_id);
CREATE INDEX IF NOT EXISTS idx_psa_status ON parent_student_assignments(status);

CREATE TRIGGER update_parent_student_assignments_updated_at BEFORE UPDATE ON parent_student_assignments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
[pytest]
testpaths = tests
# Per-test timeout so a hung test fails fast with a traceback instead of
# stalling CI indefinitely. 120s is well above the whole suite's runtime.
timeout = 120
timeout_method = thread
markers =
eval: live eval cases (needs OPENROUTER_API_KEY; run with -m eval)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pytest==7.4.3
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-mock==3.12.0
pytest-timeout==2.2.0
httpx==0.25.2 # For testing FastAPI

# Development
Expand Down
Loading
Loading