An AI-powered student grant platform. Students upload their CV and transcript, the platform uses Gemini AI to discover grants and auto-draft personalized applications. Stripe handles payouts when grants are awarded.
- AI Grant Discovery - Admin searches the web for grants using Gemini AI with grounding
- Document Parsing - Upload CV/transcript (PDF/DOCX), text extracted automatically
- Bulk Application Drafting - Select multiple grants, AI generates personalized statements
- Smart Matching - Algorithm scores grants 0-100% based on profile fit
- Stripe Payouts - Students receive 90% of award (10% platform fee)
- Email Notifications - Status updates via Resend
- Framework: Rails 7.1 with Hotwire (Turbo + Stimulus)
- Database: PostgreSQL
- Background Jobs: solid_queue (no Redis needed)
- AI: Google Gemini API
- Styling: Tailwind CSS
- Authentication: Devise
- Payments: Stripe Connect Express
- Email: Resend
- Document Parsing: pdf-reader, docx gems
# Install dependencies
bundle install
# Setup database
bin/rails db:create db:migrate db:seed
# Load solid_queue tables
bin/rails runner 'load "db/queue_schema.rb"'
# Add API keys
bin/rails credentials:editAdd your API keys to credentials:
google:
gemini_api_key: your_gemini_api_key
resend:
api_key: your_resend_api_key
stripe:
secret_key: sk_test_xxx
publishable_key: pk_test_xxx# Start server
bin/rails server
# Start background worker (in another terminal)
bin/jobsThe app runs at http://localhost:3000
| Role | Password | |
|---|---|---|
| Admin | admin@grantstream.com | password123 |
| Student | student@example.com | password123 |
- Sign up → Complete profile with academic info
- Upload documents → CV and transcript (PDF/DOCX)
- Browse grants → Filter by major, amount, deadline
- Bulk draft applications → Select grants, click "Draft Applications"
- Review AI drafts → Edit personal statements as needed
- Submit applications → Send to grant providers
- Get awarded → Connect Stripe, request payout
- AI Grant Discovery → Enter search query (e.g., "STEM scholarships California")
- Review results → Approve/reject discovered grants
- Manage grants → Edit details, publish to students
- Review applications → Approve, reject, or award
- Process payments → Mark funds received, trigger payouts
┌─────────────────────────────────────────────────────────────────┐
│ ADMIN BACKOFFICE │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Grant │───▶│ Gemini │───▶│ Review & Approve │ │
│ │ Discovery │ │ Web Search │ │ Grants │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ USER FLOW │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Sign Up + │───▶│ Upload CV │───▶│ Browse Matched │ │
│ │ Profile │ │ + Transcript│ │ Grants │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Request │◀───│ Get Awarded │◀───│ Review & Submit │ │
│ │ Payout │ │ (Admin) │ │ Applications │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Stripe │ │ Gemini Auto-Draft │ │
│ │ Transfer │ │ (Bulk) │ │
│ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
# Admin searches for grants on the web
service = Llm::WebSearchService.new
results = service.search_grants("STEM scholarships for first-generation students")
# => { grants: [...], source_urls: [...] }# Extract structured data from a grant page
service = Llm::GrantExtractorService.new
data = service.extract_from_url("https://example.com/scholarship")
# => { name: "...", amount: 5000, deadline: Date, eligibility: {...} }# Generate personalized statement
service = Llm::ApplicationDrafterService.new
result = service.draft_application(profile: user.profile, grant: grant)
# => { statement: "300-500 word personalized statement...", ... }Supported formats: PDF, DOCX (max 10MB)
# Automatic parsing on upload
profile.cv.attach(file)
# => Triggers DocumentParsingJob
# Extracted text cached in profile
profile.cv_text # => "Full text content..."
profile.cv_parsed? # => trueUses solid_queue with PostgreSQL (no Redis required).
| Job | Purpose |
|---|---|
DocumentParsingJob |
Extract text from CV/transcript |
GrantDiscoveryJob |
Search web for grants |
GrantExtractionJob |
Extract data from grant URLs |
BulkApplicationDraftingJob |
Generate multiple drafts |
PayoutProcessingJob |
Process Stripe transfers |
# Start worker
bin/jobs
# Check job status
bin/rails runner 'puts SolidQueue::Job.count'1. Admin awards application
└── Payment created (status: pending)
2. Admin marks "Funds Received"
└── status: funds_received
└── User notified via email
3. User requests payout
└── status: processing
└── PayoutProcessingJob queued
4. Stripe transfer completes
└── status: completed
└── User notified via email
Fee structure: 10% platform fee, 90% to student
payment.total_amount # $10,000
payment.platform_fee # $1,000
payment.payout_amount # $9,000GET /dashboard # Student dashboard
GET /profile/edit # Edit profile + upload documents
DELETE /profile/remove_cv # Remove CV
DELETE /profile/remove_transcript # Remove transcript
GET /grants # Browse grants (with bulk select)
POST /applications/bulk_draft # AI draft multiple applications
GET /payments # View awarded payments
POST /payments/:id/request_payout # Request Stripe payout
GET /admin/grant_discoveries # AI discovery history
POST /admin/grant_discoveries # Start new AI search
POST /admin/grant_discoveries/:id/approve_grant # Approve found grant
POST /admin/grant_discoveries/:id/reject_grant # Reject found grant
PATCH /admin/payments/:id/mark_funds_received # Mark funds received
POST /admin/payments/:id/process_payout # Process payout
Sent via Resend on these events:
| Event | |
|---|---|
| Application submitted | Confirmation to student |
| Application approved | Good news notification |
| Application rejected | Update with feedback |
| Application awarded | Congratulations + next steps |
| Funds received | Prompt to request payout |
| Payout completed | Transfer confirmation |
| Payout failed | Action required |
| Drafts ready | AI drafts available for review |
The MatchingService scores grants 0-100% based on profile fit:
| Criteria | Weight |
|---|---|
| Major | 25% |
| Citizenship | 20% |
| GPA | 15% |
| Degree Type | 15% |
| Graduation Year | 10% |
| Family Income | 10% |
| First Generation | 5% |
service = MatchingService.new(user.profile)
service.match_score(grant) # => 85
service.eligible?(grant) # => true (score >= 50)
service.recommended_grants(grants) # Top matches# Start everything
bin/rails server # Web server
bin/jobs # Background worker
# Console
bin/rails c
# Run tests
bin/rails test| Service | Get Key | Purpose |
|---|---|---|
| Gemini | Google AI Studio | AI discovery & drafting |
| Resend | Resend Dashboard | Email notifications |
| Stripe | Stripe Dashboard | Payments |
MIT