Initial commit with dev backbone template

This commit is contained in:
2026-02-10 16:36:30 -05:00
commit 4318c8f642
53 changed files with 3500 additions and 0 deletions

57
.dockerignore Normal file
View File

@@ -0,0 +1,57 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
*.egg-info/
.pytest_cache/
.coverage
# Node
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.next/
out/
.turbo/
.vercel/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Docs
*.md
!DOCKER_README.md
# Test files
test_*.py
*_test.py
# Excel files (not needed in container)
*.xlsx
# Email config (sensitive)
.env
*.pem
*.key
# Misc
*.log
.cache/

9
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,9 @@
Follow `AGENTS.md` and `SKILLS.md`.
Rules:
- Use the PLAN → IMPLEMENT → VERIFY → REVIEW loop.
- Keep model selection on Auto unless AGENTS.md role routing says to override for that role.
- Never claim "done" unless DoD passes (`./scripts/dod.sh` or `\scripts\dod.ps1`).
- Keep diffs small and add/update tests when behavior changes.
- Prefer reproducible commands and cite sources for generated documents.

17
.github/workflows/dod.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: DoD Gate
on:
pull_request:
push:
branches: [ main, master ]
jobs:
dod:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run DoD
run: |
chmod +x ./scripts/dod.sh || true
./scripts/dod.sh

25
.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
# Python
.venv/
__pycache__/
*.py[cod]
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
# Environment
.env
.env.local
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Node
node_modules/

78
AGENTS.md Normal file
View File

@@ -0,0 +1,78 @@
# Agent Operating System (Auto-first)
Default: use **Auto** model selection. Only override the model when a role below says it is worth it.
## Prime directive
- Never claim "done" unless **DoD passes**.
- Prefer small, reviewable diffs.
- If requirements are unclear: stop and produce a PLAN + questions inside the plan.
- Agents talk; **DoD decides**.
## Always follow this loop
1) PLAN: goal, constraints, assumptions, steps (≤10), files to touch, test plan.
2) IMPLEMENT: smallest correct change.
3) VERIFY: run DoD until green.
4) REVIEW: summarize changes, risks, next steps.
## DoD Gate (Definition of Done)
Required before "done":
- macOS/Linux: `./scripts/dod.sh`
- Windows: `\scripts\dod.ps1`
If DoD cannot be run, say exactly why and what would be run.
## Terminal agent workflow (Copilot CLI)
Preferred terminal assistant: GitHub Copilot CLI via `gh copilot`.
Default loop:
1) Plan: draft plan + file list + test plan.
2) Build: implement smallest slice.
3) Steer: when stuck, ask for next action using current errors/logs.
4) Verify: run DoD until green.
Rules:
- Keep diffs small.
- If the same error repeats twice, switch to Reviewer role and produce a fix plan.
## Role routing (choose a role explicitly)
### Planner
Use when: new feature, refactor, multi-file, uncertain scope.
Output: plan + acceptance criteria + risks + test plan.
Model: Auto (override to a “high reasoning / Codex” model only for complex design/debugging).
### UI/UX Specialist
Use when: screens, layout, copy, design tradeoffs, component structure.
Output: component outline, UX notes, acceptance criteria.
Model: Auto (override to Gemini only when UI/UX is the main work).
### Coder
Use when: writing/editing code, plumbing, tests, small refactors.
Rules: follow repo conventions; keep diff small; add/update tests when behavior changes.
Model: Auto (override to Claude Haiku only when speed matters and the change is well-scoped).
### Reviewer
Use when: before merge, failing tests, risky changes, security-sensitive areas.
Output: concrete issues + recommended fixes + risk assessment + verification suggestions.
Model: Auto (override to the strongest available for high-stakes diffs).
## Non-negotiables
- Do not expose secrets/tokens/keys. Never print env vars.
- No destructive commands unless explicitly required and narrowly scoped.
- Do not add new dependencies without stating why + impact + alternatives.
- Prefer deterministic, reproducible steps.
- Cite sources when generating documents from a knowledge base.
## Repo facts (fill these in)
- Primary stack:
- Package manager:
- Test command:
- Lint/format command:
- Build command (if any):
- Deployment (if any):
## Claude Code Agents (optional)
- `.claude/agents/architect-cyber.md` — architecture + security + ops decisions for cyber apps.
- Add more agents in `.claude/agents/` as you standardize roles (reviewer, tester, security-lens).

255
DOCKER_QUICKSTART.md Normal file
View File

@@ -0,0 +1,255 @@
# 🐋 Docker Setup Complete!
## What's Been Created
### Docker Files
-`Dockerfile.backend` - Flask API with Playwright
-`Dockerfile.frontend` - Next.js app optimized for production
-`Dockerfile.email` - Email scheduler service
-`docker-compose.yml` - Development setup
-`docker-compose.prod.yml` - Production setup with nginx
-`.dockerignore` - Optimized build context
-`requirements.txt` - Python dependencies
### Configuration
- ✅ Updated `next.config.ts` for standalone output
- ✅ Created startup scripts (Windows & Linux)
- ✅ Complete documentation in `DOCKER_README.md`
---
## 🚀 Quick Start
### Option 1: Windows Script (Easiest)
```bash
docker-start.bat
```
### Option 2: Docker Compose
```bash
docker-compose up -d
```
### Option 3: Manual Build
```bash
# Build
docker-compose build
# Start
docker-compose up -d
# Check status
docker-compose ps
```
---
## 📦 What Gets Deployed
### Backend Container
- Python 3.13
- Flask API on port 5000
- Playwright with Chromium browser
- Lottery scrapers for all 4 lotteries
- Investment calculator
- Health check endpoint
### Frontend Container
- Node.js 20
- Next.js standalone build
- Optimized production bundle
- Connects to backend API
### Email Container (Optional)
- Runs daily at 7:00 AM
- Sends lottery jackpot emails
- Uses same scraping logic
---
## 🌐 Access Points
After running `docker-compose up -d`:
- **Frontend**: http://localhost:3000
- **Backend API**: http://localhost:5000
- **Health Check**: http://localhost:5000/api/health
---
## 📊 Container Management
### View Logs
```bash
# All services
docker-compose logs -f
# Specific service
docker-compose logs -f backend
docker-compose logs -f frontend
```
### Restart Services
```bash
docker-compose restart
```
### Stop Everything
```bash
docker-compose down
```
### Rebuild After Changes
```bash
docker-compose up -d --build
```
---
## 🔧 Troubleshooting
### Port Already in Use
If ports 3000 or 5000 are busy:
**Option A**: Stop other services
```bash
# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /F
```
**Option B**: Change ports in `docker-compose.yml`
```yaml
ports:
- "8080:3000" # Use port 8080 instead
```
### Backend Won't Start
```bash
# Check logs
docker logs lottery-backend
# Rebuild without cache
docker-compose build --no-cache backend
```
### Frontend Can't Connect
Update `docker-compose.yml` frontend environment:
```yaml
environment:
- NEXT_PUBLIC_API_URL=http://localhost:5000
```
---
## 🎯 Include Email Scheduler
To run the email scheduler:
```bash
docker-compose --profile email up -d
```
Or remove the `profiles` section from `docker-compose.yml` to always include it.
---
## 📈 Production Deployment
### Use Production Compose
```bash
docker-compose -f docker-compose.prod.yml up -d
```
### Deploy to Server
```bash
# On your server
git clone <your-repo>
cd Lottery
docker-compose -f docker-compose.prod.yml up -d
```
### Push to Docker Hub
```bash
# Login
docker login
# Tag images
docker tag lottery-backend yourusername/lottery-backend:latest
docker tag lottery-frontend yourusername/lottery-frontend:latest
# Push
docker push yourusername/lottery-backend:latest
docker push yourusername/lottery-frontend:latest
```
---
## 🔒 Security for Production
1. **Use environment variables** - Don't hardcode credentials
2. **Enable HTTPS** - Use nginx with SSL certificates
3. **Update base images** regularly
4. **Scan for vulnerabilities**:
```bash
docker scan lottery-backend
```
5. **Use Docker secrets** for sensitive data
---
## 💾 Data Persistence
Currently, containers are stateless. To add persistence:
Add volumes in `docker-compose.yml`:
```yaml
volumes:
- ./data:/app/data
```
---
## 🎛️ Resource Limits
Current limits (production):
- Backend: 2GB RAM, 1 CPU
- Frontend: 512MB RAM, 0.5 CPU
- Email: 1GB RAM, 0.5 CPU
Adjust in `docker-compose.prod.yml` if needed.
---
## ✅ Benefits of Docker
1. ✅ **Consistent environment** - Works the same everywhere
2. ✅ **Easy deployment** - One command to start everything
3. ✅ **Isolation** - Each service in its own container
4. ✅ **Scalability** - Easy to scale services
5. ✅ **Version control** - Docker images are versioned
6. ✅ **Portability** - Deploy anywhere Docker runs
---
## 📝 Next Steps
1. ✅ Test locally: `docker-compose up -d`
2. ✅ Check logs: `docker-compose logs -f`
3. ✅ Access app: http://localhost:3000
4. ✅ Configure email scheduler if needed
5. ✅ Deploy to production server
6. ✅ Set up CI/CD pipeline (optional)
---
## 🆘 Need Help?
See detailed documentation in:
- `DOCKER_README.md` - Full Docker guide
- `EMAIL_SETUP.md` - Email configuration
- Docker logs: `docker-compose logs -f`
---
Enjoy your Dockerized Lottery Investment Calculator! 🎰🐋

347
DOCKER_README.md Normal file
View File

@@ -0,0 +1,347 @@
# Lottery Investment Calculator - Docker Setup
## 🐋 Docker Deployment Guide
### Prerequisites
- Docker Desktop installed (https://www.docker.com/products/docker-desktop)
- Docker Compose (included with Docker Desktop)
---
## 🚀 Quick Start
### 1. Build and Run Everything
```bash
docker-compose up -d
```
This will start:
- **Backend API** on http://localhost:5000
- **Frontend Web App** on http://localhost:3000
### 2. Check Status
```bash
docker-compose ps
```
### 3. View Logs
```bash
# All services
docker-compose logs -f
# Just backend
docker-compose logs -f backend
# Just frontend
docker-compose logs -f frontend
```
### 4. Stop Everything
```bash
docker-compose down
```
---
## 📦 Individual Services
### Backend Only
```bash
docker build -f Dockerfile.backend -t lottery-backend .
docker run -p 5000:5000 lottery-backend
```
### Frontend Only
```bash
docker build -f Dockerfile.frontend -t lottery-frontend .
docker run -p 3000:3000 lottery-frontend
```
### Email Scheduler (Optional)
```bash
docker-compose --profile email up -d
```
---
## 🔧 Configuration
### Update Next.js to use standalone output
Add to `frontend/next.config.ts`:
```typescript
const nextConfig = {
output: 'standalone',
};
```
### Environment Variables
Create `.env` file:
```bash
# Backend
FLASK_ENV=production
# Frontend
NEXT_PUBLIC_API_URL=http://localhost:5000
# Email (optional)
EMAIL_SENDER=mblanke@gmail.com
EMAIL_RECIPIENT=mblanke@gmail.com
EMAIL_PASSWORD=vyapvyjjfrqpqnax
```
Then update `docker-compose.yml` to use env_file:
```yaml
services:
backend:
env_file: .env
```
---
## 🏗️ Build Process
### First Time Setup
```bash
# Build all images
docker-compose build
# Or build individually
docker-compose build backend
docker-compose build frontend
docker-compose build email-scheduler
```
### Rebuild After Code Changes
```bash
# Rebuild and restart
docker-compose up -d --build
# Rebuild specific service
docker-compose up -d --build backend
```
---
## 🌐 Network Configuration
All services communicate via the `lottery-network` bridge network.
### Internal URLs (container to container):
- Backend: `http://backend:5000`
- Frontend: `http://frontend:3000`
### External URLs (host to container):
- Backend: `http://localhost:5000`
- Frontend: `http://localhost:3000`
---
## 📊 Health Checks
The backend includes a health check endpoint:
```bash
curl http://localhost:5000/api/health
```
Check in Docker:
```bash
docker inspect lottery-backend | grep -A 10 Health
```
---
## 🔄 Production Deployment
### Docker Hub
```bash
# Tag images
docker tag lottery-backend yourusername/lottery-backend:latest
docker tag lottery-frontend yourusername/lottery-frontend:latest
# Push to Docker Hub
docker push yourusername/lottery-backend:latest
docker push yourusername/lottery-frontend:latest
```
### Deploy to Server
```bash
# Pull images on server
docker pull yourusername/lottery-backend:latest
docker pull yourusername/lottery-frontend:latest
# Run with compose
docker-compose -f docker-compose.prod.yml up -d
```
---
## 🐛 Troubleshooting
### Backend won't start
```bash
# Check logs
docker logs lottery-backend
# Common issues:
# - Port 5000 already in use
# - Playwright installation failed
# - Missing dependencies
```
### Frontend can't connect to backend
```bash
# Check if backend is running
docker-compose ps
# Test backend directly
curl http://localhost:5000/api/health
# Check frontend environment
docker exec lottery-frontend env | grep API_URL
```
### Playwright browser issues
```bash
# Rebuild with no cache
docker-compose build --no-cache backend
# Check Playwright installation
docker exec lottery-backend playwright --version
```
### Container keeps restarting
```bash
# View logs
docker logs lottery-backend --tail 100
# Check health status
docker inspect lottery-backend | grep -A 5 Health
```
---
## 📝 Useful Commands
### Access Container Shell
```bash
# Backend
docker exec -it lottery-backend /bin/bash
# Frontend
docker exec -it lottery-frontend /bin/sh
```
### Remove Everything
```bash
# Stop and remove containers, networks
docker-compose down
# Also remove volumes
docker-compose down -v
# Remove images
docker-compose down --rmi all
```
### Prune Unused Resources
```bash
docker system prune -a
```
### View Resource Usage
```bash
docker stats
```
---
## 🚢 Alternative: Docker without Compose
### Create Network
```bash
docker network create lottery-network
```
### Run Backend
```bash
docker run -d \
--name lottery-backend \
--network lottery-network \
-p 5000:5000 \
lottery-backend
```
### Run Frontend
```bash
docker run -d \
--name lottery-frontend \
--network lottery-network \
-p 3000:3000 \
-e NEXT_PUBLIC_API_URL=http://localhost:5000 \
lottery-frontend
```
---
## 🎯 Email Scheduler with Docker
To include the email scheduler:
1. **Start with email service:**
```bash
docker-compose --profile email up -d
```
2. **Or add to default profile** (edit docker-compose.yml):
Remove `profiles: - email` from email-scheduler service
3. **Check email logs:**
```bash
docker logs lottery-email -f
```
---
## 🔐 Security Notes
⚠️ **Important:**
- Never commit `.env` files with real credentials
- Use Docker secrets in production
- Set proper firewall rules
- Use HTTPS in production
- Regularly update base images
---
## 📈 Scaling
### Run multiple backend instances
```bash
docker-compose up -d --scale backend=3
```
### Add load balancer (nginx)
See `docker-compose.prod.yml` for nginx configuration
---
## 🆘 Support
If containers won't start:
1. Check Docker Desktop is running
2. Ensure ports 3000 and 5000 are available
3. Check logs: `docker-compose logs`
4. Rebuild: `docker-compose up -d --build`
5. Reset: `docker-compose down && docker-compose up -d`
---
## 📦 Image Sizes (Approximate)
- Backend: ~1.5 GB (includes Chromium browser)
- Frontend: ~200 MB
- Email Scheduler: ~1.5 GB (includes Chromium browser)
To reduce size, consider multi-stage builds or Alpine Linux variants.

57
Dockerfile.backend Normal file
View File

@@ -0,0 +1,57 @@
# Flask Backend Dockerfile
FROM python:3.13-slim
# Set working directory
WORKDIR /app
# Install system dependencies for Playwright
RUN apt-get update && apt-get install -y \
wget \
gnupg \
ca-certificates \
fonts-liberation \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libatspi2.0-0 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libwayland-client0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxkbcommon0 \
libxrandr2 \
xdg-utils \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Install Playwright browsers
RUN playwright install chromium
RUN playwright install-deps chromium
# Copy application files
COPY app.py .
COPY lottery_calculator.py .
COPY ["import requests.py", "."]
# Expose port
EXPOSE 5000
# Set environment variables
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
# Run the application
CMD ["python", "app.py"]

46
Dockerfile.email Normal file
View File

@@ -0,0 +1,46 @@
# Email Scheduler Dockerfile
FROM python:3.13-slim
WORKDIR /app
# Install system dependencies for Playwright
RUN apt-get update && apt-get install -y \
wget \
gnupg \
ca-certificates \
fonts-liberation \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libatspi2.0-0 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libwayland-client0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxkbcommon0 \
libxrandr2 \
xdg-utils \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Install Playwright
RUN playwright install chromium
RUN playwright install-deps chromium
# Copy email sender script
COPY email_sender.py .
# Set environment variables
ENV PYTHONUNBUFFERED=1
CMD ["python", "email_sender.py"]

47
Dockerfile.frontend Normal file
View File

@@ -0,0 +1,47 @@
# Next.js Frontend Dockerfile
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Copy package files
COPY frontend/package*.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY frontend/ .
# Accept build argument for API URL (defaults to localhost for browser access)
ARG NEXT_PUBLIC_API_URL=http://localhost:5000
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
# Build the application
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy necessary files
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

208
EMAIL_SETUP.md Normal file
View File

@@ -0,0 +1,208 @@
# Email Configuration for Lottery Jackpot Alerts
## Setup Instructions
### 1. Install Required Package
```bash
pip install schedule
```
### 2. Configure Email Settings
Edit `email_sender.py` and update the `EMAIL_CONFIG` section:
```python
EMAIL_CONFIG = {
'smtp_server': 'smtp.gmail.com', # Your email provider's SMTP server
'smtp_port': 587,
'sender_email': 'your-email@gmail.com', # Your email address
'sender_password': 'your-app-password', # Your app-specific password
'recipient_email': 'recipient@example.com', # Where to send the report
}
```
### 3. Email Provider Settings
#### For Gmail:
1. **Enable 2-Factor Authentication** on your Google account
2. **Generate App Password**:
- Go to: https://myaccount.google.com/apppasswords
- Select "Mail" and "Windows Computer"
- Copy the 16-character password
- Use this password in `sender_password` (NOT your regular Gmail password)
3. SMTP Settings:
- Server: `smtp.gmail.com`
- Port: `587`
#### For Outlook/Hotmail:
- Server: `smtp-mail.outlook.com`
- Port: `587`
- Use your regular email and password
#### For Yahoo:
- Server: `smtp.mail.yahoo.com`
- Port: `587`
- Generate app password at: https://login.yahoo.com/account/security
#### For Other Providers:
Search for "[Your Provider] SMTP settings" to find the correct server and port.
### 4. Test the Email
Uncomment this line in the `main()` function to send a test email immediately:
```python
send_daily_jackpots()
```
Then run:
```bash
python email_sender.py
```
### 5. Schedule Daily Emails
The script is configured to send emails at **7:00 AM** every day.
To run it continuously:
```bash
python email_sender.py
```
Keep the terminal window open. The script will:
- Wait until 7:00 AM
- Fetch current jackpots
- Send formatted email
- Repeat daily
### 6. Run as Background Service (Optional)
#### Windows - Task Scheduler:
1. Open Task Scheduler
2. Create Basic Task
3. Name: "Lottery Jackpot Email"
4. Trigger: Daily at 7:00 AM
5. Action: Start a program
- Program: `python`
- Arguments: `d:\Projects\Dev\Lottery\email_sender.py`
6. Finish
#### Windows - NSSM (Non-Sucking Service Manager):
```bash
# Install NSSM
choco install nssm
# Create service
nssm install LotteryEmail python d:\Projects\Dev\Lottery\email_sender.py
# Start service
nssm start LotteryEmail
```
#### Linux - Cron Job:
```bash
# Edit crontab
crontab -e
# Add this line (runs at 7:00 AM daily)
0 7 * * * /usr/bin/python3 /path/to/email_sender.py
```
#### Linux - systemd service:
Create `/etc/systemd/system/lottery-email.service`:
```ini
[Unit]
Description=Lottery Jackpot Email Service
After=network.target
[Service]
Type=simple
User=yourusername
WorkingDirectory=/path/to/Lottery
ExecStart=/usr/bin/python3 /path/to/email_sender.py
Restart=always
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl enable lottery-email
sudo systemctl start lottery-email
```
## Email Features
The automated email includes:
- 🎰 **Powerball** jackpot (US)
- 🎰 **Mega Millions** jackpot (US)
- 🎰 **Lotto Max** jackpot (Canada - TAX FREE!)
- 🎰 **Lotto 6/49** jackpot (Canada - TAX FREE!)
- 📅 **Timestamp** of when data was fetched
- 💡 **Reminder** about Canadian tax-free winnings
- 🎨 **Beautiful HTML formatting** with colors and styling
## Customization
### Change Send Time:
Edit this line in `email_sender.py`:
```python
schedule.every().day.at("07:00").do(send_daily_jackpots)
```
Examples:
- `"09:30"` - 9:30 AM
- `"18:00"` - 6:00 PM
- `"00:00"` - Midnight
### Send to Multiple Recipients:
Change the `send_email()` function:
```python
msg['To'] = "email1@example.com, email2@example.com, email3@example.com"
```
### Send Multiple Times Per Day:
Add multiple schedule lines:
```python
schedule.every().day.at("07:00").do(send_daily_jackpots)
schedule.every().day.at("19:00").do(send_daily_jackpots)
```
## Troubleshooting
### "Authentication failed":
- Make sure you're using an **app password**, not your regular password (for Gmail)
- Check that 2FA is enabled on your account
- Verify SMTP server and port are correct
### "Connection refused":
- Check your firewall settings
- Verify SMTP port is correct (usually 587 or 465)
- Try port 465 with `SMTP_SSL` instead of `SMTP` with `starttls()`
### Script stops running:
- Check if your computer went to sleep
- Use Task Scheduler or systemd to auto-restart
- Check logs for error messages
### Jackpots not updating:
- Websites may have changed their HTML structure
- Check if Playwright browser is installed: `playwright install chromium`
- Test the scraper functions individually
## Security Notes
⚠️ **IMPORTANT**:
- Never commit `email_sender.py` with your real credentials to Git
- Use environment variables for sensitive data in production
- Keep your app password secure
- Don't share your app password with anyone
## Support
If you encounter issues:
1. Run the test email first to verify configuration
2. Check error messages in the console
3. Verify internet connection
4. Confirm email provider settings
5. Test scraping functions individually

BIN
Max.xlsx Normal file

Binary file not shown.

23
SKILLS.md Normal file
View File

@@ -0,0 +1,23 @@
# Skills Index
These skill files define repeatable behaviors for agents and humans.
Agents must follow them in this order:
1) SKILLS/00-operating-model.md
2) SKILLS/05-agent-taxonomy.md
3) SKILLS/10-definition-of-done.md
4) SKILLS/20-repo-map.md (use whenever unfamiliar with the repo)
5) SKILLS/25-algorithms-performance.md
6) SKILLS/26-vibe-coding-fundamentals.md
7) SKILLS/27-performance-profiling.md
8) SKILLS/30-implementation-rules.md
9) SKILLS/40-testing-quality.md
10) SKILLS/50-pr-review.md
11) SKILLS/56-ui-material-ui.md (for React/Next portal-style apps)
12) SKILLS/60-security-safety.md
13) SKILLS/70-docs-artifacts.md
14) SKILLS/82-mcp-server-design.md (when building MCP servers/tools)
15) SKILLS/83-fastmcp-3-patterns.md (if using FastMCP 3)
16) SKILLS/80-mcp-tools.md (if this repo has MCP tools)
Rule: If anything conflicts, **AGENTS.md wins**.

View File

@@ -0,0 +1,21 @@
# Operating Model
## Default cadence
- Prefer iterative progress over big bangs.
- Keep diffs small: target ≤ 300 changed lines per PR unless justified.
- Update tests/docs as part of the same change when possible.
## Working agreement
- Start with a PLAN for non-trivial tasks.
- Implement the smallest slice that satisfies acceptance criteria.
- Verify via DoD.
- Write a crisp PR summary: what changed, why, and how verified.
## Stop conditions (plan first)
Stop and produce a PLAN (do not code yet) if:
- scope is unclear
- more than 3 files will change
- data model changes
- auth/security boundaries
- performance-critical paths

View File

@@ -0,0 +1,36 @@
# Agent Types & Roles (Practical Taxonomy)
Use this skill to choose the *right* kind of agent workflow for the job.
## Common agent “types” (in practice)
### 1) Chat assistant (no tools)
Best for: explanations, brainstorming, small edits.
Risk: can hallucinate; no grounding in repo state.
### 2) Tool-using single agent
Best for: well-scoped tasks where the agent can read/write files and run commands.
Key control: strict DoD gates + minimal permissions.
### 3) Planner + Executor (2-role pattern)
Best for: medium complexity work (multi-file changes, feature work).
Flow: Planner writes plan + acceptance criteria → Executor implements → Reviewer checks.
### 4) Multi-agent (specialists)
Best for: bigger features with separable workstreams (UI, backend, docs, tests).
Rule: isolate context per role; use separate branches/worktrees.
### 5) Supervisor / orchestrator
Best for: long-running workflows with checkpoints (pipelines, report generation, PAD docs).
Rule: supervisor delegates, enforces gates, and composes final output.
## Decision rules (fast)
- If you can describe it in ≤ 5 steps → single tool-using agent.
- If you need tradeoffs/design → Planner + Executor.
- If UI + backend + docs/tests all move → multi-agent specialists.
- If its a pipeline that runs repeatedly → orchestrator.
## Guardrails (always)
- DoD is the truth gate.
- Separate branches/worktrees for parallel work.
- Log decisions + commands in AGENT_LOG.md.

View File

@@ -0,0 +1,24 @@
# Definition of Done (DoD)
A change is "done" only when:
## Code correctness
- Builds successfully (if applicable)
- Tests pass
- Linting/formatting passes
- Types/checks pass (if applicable)
## Quality
- No new warnings introduced
- Edge cases handled (inputs validated, errors meaningful)
- Hot paths not regressed (if applicable)
## Hygiene
- No secrets committed
- Docs updated if behavior or usage changed
- PR summary includes verification steps
## Commands
- macOS/Linux: `./scripts/dod.sh`
- Windows: `\scripts\dod.ps1`

16
SKILLS/20-repo-map.md Normal file
View File

@@ -0,0 +1,16 @@
# Repo Mapping Skill
When entering a repo:
1) Read README.md
2) Identify entrypoints (app main / server startup / CLI)
3) Identify config (env vars, .env.example, config files)
4) Identify test/lint scripts (package.json, pyproject.toml, Makefile, etc.)
5) Write a 10-line "repo map" in the PLAN before changing code
Output format:
- Purpose:
- Key modules:
- Data flow:
- Commands:
- Risks:

View File

@@ -0,0 +1,20 @@
# Algorithms & Performance
Use this skill when performance matters (large inputs, hot paths, or repeated calls).
## Checklist
- Identify the **state** youre recomputing.
- Add **memoization / caching** when the same subproblem repeats.
- Prefer **linear scans** + caches over nested loops when possible.
- If you can write it as a **recurrence**, you can test it.
## Practical heuristics
- Measure first when possible (timing + input sizes).
- Optimize the biggest wins: avoid repeated I/O, repeated parsing, repeated network calls.
- Keep caches bounded (size/TTL) and invalidate safely.
- Choose data structures intentionally: dict/set for membership, heap for top-k, deque for queues.
## Review notes (for PRs)
- Call out accidental O(n²) patterns.
- Suggest table/DP or memoization when repeated work is obvious.
- Add tests that cover base cases + typical cases + worst-case size.

View File

@@ -0,0 +1,31 @@
# Vibe Coding With Fundamentals (Safety Rails)
Use this skill when youre using “vibe coding” (fast, conversational building) but want production-grade outcomes.
## The good
- Rapid scaffolding and iteration
- Fast UI prototypes
- Quick exploration of architectures and options
## The failure mode
- “It works on my machine” code with weak tests
- Security foot-guns (auth, input validation, secrets)
- Performance cliffs (accidental O(n²), repeated I/O)
- Unmaintainable abstractions
## Safety rails (apply every time)
- Always start with acceptance criteria (what “done” means).
- Prefer small PRs; never dump a huge AI diff.
- Require DoD gates (lint/test/build) before merge.
- Write tests for behavior changes.
- For anything security/data related: do a Reviewer pass.
## When to slow down
- Auth/session/token work
- Anything touching payments, PII, secrets
- Data migrations/schema changes
- Performance-critical paths
- “Its flaky” or “it only fails in CI”
## Practical prompt pattern (use in PLAN)
- “State assumptions, list files to touch, propose tests, and include rollback steps.”

View File

@@ -0,0 +1,31 @@
# Performance Profiling (Bun/Node)
Use this skill when:
- a hot path feels slow
- CPU usage is high
- you suspect accidental O(n²) or repeated work
- you need evidence before optimizing
## Bun CPU profiling
Bun supports CPU profiling via `--cpu-prof` (generates a `.cpuprofile` you can open in Chrome DevTools).
Upcoming: `bun --cpu-prof-md <script>` outputs a CPU profile as **Markdown** so LLMs can read/grep it easily.
### Workflow (Bun)
1) Run the workload with profiling enabled
- Today: `bun --cpu-prof ./path/to/script.ts`
- Upcoming: `bun --cpu-prof-md ./path/to/script.ts`
2) Save the output (or `.cpuprofile`) into `./profiles/` with a timestamp.
3) Ask the Reviewer agent to:
- identify the top 5 hottest functions
- propose the smallest fix
- add a regression test or benchmark
## Node CPU profiling (fallback)
- `node --cpu-prof ./script.js` writes a `.cpuprofile` file.
- Open in Chrome DevTools → Performance → Load profile.
## Rules
- Optimize based on measured hotspots, not vibes.
- Prefer algorithmic wins (remove repeated work) over micro-optimizations.
- Keep profiling artifacts out of git unless explicitly needed (use `.gitignore`).

View File

@@ -0,0 +1,16 @@
# Implementation Rules
## Change policy
- Prefer edits over rewrites.
- Keep changes localized.
- One change = one purpose.
- Avoid unnecessary abstraction.
## Dependency policy
- Default: do not add dependencies.
- If adding: explain why, alternatives considered, and impact.
## Error handling
- Validate inputs at boundaries.
- Error messages must be actionable: what failed + what to do next.

View File

@@ -0,0 +1,14 @@
# Testing & Quality
## Strategy
- If behavior changes: add/update tests.
- Unit tests for logic; integration tests for boundaries; E2E only where needed.
## Minimum for every PR
- A test plan in the PR summary (even if “existing tests cover this”).
- Run DoD.
## Flaky tests
- Capture repro steps.
- Quarantine only with justification + follow-up issue.

16
SKILLS/50-pr-review.md Normal file
View File

@@ -0,0 +1,16 @@
# PR Review Skill
Reviewer must check:
- Correctness: does it do what it claims?
- Safety: secrets, injection, auth boundaries
- Maintainability: readability, naming, duplication
- Tests: added/updated appropriately
- DoD: did it pass?
Reviewer output format:
1) Summary
2) Must-fix
3) Nice-to-have
4) Risks
5) Verification suggestions

View File

@@ -0,0 +1,41 @@
# Material UI (MUI) Design System
Use this skill for any React/Next “portal/admin/dashboard” UI so you stay consistent and avoid random component soup.
## Standard choice
- Preferred UI library: **MUI (Material UI)**.
- Prefer MUI components over ad-hoc HTML/CSS unless theres a good reason.
- One design system per repo (do not mix Chakra/Ant/Bootstrap/etc.).
## Setup (Next.js/React)
- Install: `@mui/material @emotion/react @emotion/styled`
- If using icons: `@mui/icons-material`
- If using data grid: `@mui/x-data-grid` (or pro if licensed)
## Theming rules
- Define a single theme (typography, spacing, palette) and reuse everywhere.
- Use semantic colors (primary/secondary/error/warning/success/info), not hard-coded hex everywhere.
- Prefer MUIs `sx` for small styling; use `styled()` for reusable components.
## “Portal” patterns (modals, popovers, menus)
- Use MUI Dialog/Modal/Popover/Menu components instead of DIY portals.
- Accessibility requirements:
- Focus is trapped in Dialog/Modal.
- Escape closes modal unless explicitly prevented.
- All inputs have labels; buttons have clear text/aria-labels.
- Keyboard navigation works end-to-end.
## Layout conventions (for portals)
- Use: AppBar + Drawer (or NavigationRail equivalent) + main content.
- Keep pages as composition of small components: Page → Sections → Widgets.
- Keep forms consistent: FormControl + helper text + validation messages.
## Performance hygiene
- Avoid re-render storms: memoize heavy lists; use virtualization for large tables (DataGrid).
- Prefer server pagination for huge datasets.
## PR review checklist
- Theme is used (no random styling).
- Components are MUI where reasonable.
- Modal/popover accessibility is correct.
- No mixed UI libraries.

View File

@@ -0,0 +1,15 @@
# Security & Safety
## Secrets
- Never output secrets or tokens.
- Never log sensitive inputs.
- Never commit credentials.
## Inputs
- Validate external inputs at boundaries.
- Fail closed for auth/security decisions.
## Tooling
- No destructive commands unless requested and scoped.
- Prefer read-only operations first.

View File

@@ -0,0 +1,13 @@
# Docs & Artifacts
Update documentation when:
- setup steps change
- env vars change
- endpoints/CLI behavior changes
- data formats change
Docs standards:
- Provide copy/paste commands
- Provide expected outputs where helpful
- Keep it short and accurate

11
SKILLS/80-mcp-tools.md Normal file
View File

@@ -0,0 +1,11 @@
# MCP Tools Skill (Optional)
If this repo defines MCP servers/tools:
Rules:
- Tool calls must be explicit and logged.
- Maintain an allowlist of tools; deny by default.
- Every tool must have: purpose, inputs/outputs schema, examples, and tests.
- Prefer idempotent tool operations.
- Never add tools that can exfiltrate secrets without strict guards.

View File

@@ -0,0 +1,51 @@
# MCP Server Design (Agent-First)
Build MCP servers like youre designing a UI for a non-human user.
This skill distills Phil Schmids MCP server best practices into concrete repo rules.
Source: “MCP is Not the Problem, Its your Server” (Jan 21, 2026).
## 1) Outcomes, not operations
- Do **not** wrap REST endpoints 1:1 as tools.
- Expose high-level, outcome-oriented tools.
- Bad: `get_user`, `list_orders`, `get_order_status`
- Good: `track_latest_order(email)` (server orchestrates internally)
## 2) Flatten arguments
- Prefer top-level primitives + constrained enums.
- Avoid nested `dict`/config objects (agents hallucinate keys).
- Defaults reduce decision load.
## 3) Instructions are context
- Tool docstrings are *instructions*:
- when to use the tool
- argument formatting rules
- what the return means
- Error strings are also context:
- return actionable, self-correcting messages (not raw stack traces)
## 4) Curate ruthlessly
- Aim for **515 tools** per server.
- One server, one job. Split by persona if needed.
- Delete unused tools. Dont dump raw data into context.
## 5) Name tools for discovery
- Avoid generic names (`create_issue`).
- Prefer `{service}_{action}_{resource}`:
- `velociraptor_run_hunt`
- `github_list_prs`
- `slack_send_message`
## 6) Paginate large results
- Always support `limit` (default ~2050).
- Return metadata: `has_more`, `next_offset`, `total_count`.
- Never return hundreds of rows unbounded.
## Repo conventions
- Put MCP tool specs in `mcp/` (schemas, examples, fixtures).
- Provide at least 1 “golden path” example call per tool.
- Add an eval that checks:
- tool names follow discovery convention
- args are flat + typed
- responses are concise + stable
- pagination works

View File

@@ -0,0 +1,40 @@
# FastMCP 3 Patterns (Providers + Transforms)
Use this skill when you are building MCP servers in Python and want:
- composable tool sets
- per-user/per-session behavior
- auth, versioning, observability, and long-running tasks
## Mental model (FastMCP 3)
FastMCP 3 treats everything as three composable primitives:
- **Components**: what you expose (tools, resources, prompts)
- **Providers**: where components come from (decorators, files, OpenAPI, remote MCP, etc.)
- **Transforms**: how you reshape what clients see (namespace, filters, auth, versioning, visibility)
## Recommended architecture for Marcs platform
Build a **single “Cyber MCP Gateway”** that composes providers:
- LocalProvider: core cyber tools (run hunt, parse triage, generate report)
- OpenAPIProvider: wrap stable internal APIs (ticketing, asset DB) without 1:1 endpoint exposure
- ProxyProvider/FastMCPProvider: mount sub-servers (e.g., Velociraptor tools, Intel feeds)
Then apply transforms:
- Namespace per domain: `hunt.*`, `intel.*`, `pad.*`
- Visibility per session: hide dangerous tools unless user/role allows
- VersionFilter: keep old clients working while you evolve tools
## Production must-haves
- **Tool timeouts**: never let a tool hang forever
- **Pagination**: all list tools must be bounded
- **Background tasks**: use for long hunts / ingest jobs
- **Tracing**: emit OpenTelemetry traces so you can debug agent/tool behavior
## Auth rules
- Prefer component-level auth for “dangerous” tools.
- Default stance: read-only tools visible; write/execute tools gated.
## Versioning rules
- Version your components when you change schemas or semantics.
- Keep 1 previous version callable during migrations.
## Upgrade guidance
FastMCP 3 is in beta; pin to v2 for stability in production until youve tested.

50
analyze_excel.py Normal file
View File

@@ -0,0 +1,50 @@
import openpyxl
wb = openpyxl.load_workbook('Max.xlsx', data_only=True)
ws = wb.active
print("LOTTERY INVESTMENT CALCULATOR ANALYSIS")
print("="*70)
print("\nINPUTS:")
print("-"*70)
print(f"Lottery Amount: ${ws['D3'].value:,.0f}")
print(f"Cash Sum (52%): ${ws['D4'].value:,.2f}")
print(f"Federal Taxes (37%): ${ws['D5'].value:,.2f}")
print(f"State Taxes (5.5%): ${ws['D6'].value:,.2f}")
print(f"Net Amount: ${ws['D7'].value:,.2f}")
print(f"Canadian Conversion (1.35x): ${ws['D8'].value:,.2f}")
print(f"\nInvest 90%: ${ws['D10'].value:,.2f}")
print(f"Fun Money (10%): ${ws['G7'].value:,.2f}")
print(f"Net Daily Income: ${ws['G8'].value:,.2f}")
print("\n\nINVESTMENT CYCLES (90-day periods at 4.5% annual return):")
print("-"*70)
print(f"{'Cycle':<10} {'Principal Start':<18} {'Interest':<15} {'Taxes':<15} {'Withdrawal':<15} {'Total Out':<15} {'Reinvest':<15} {'Principal End':<18}")
print("-"*70)
for row in range(13, 21): # Cycles 1-8
cycle = ws[f'C{row}'].value
principal_start = ws[f'D{row}'].value
interest = ws[f'E{row}'].value
taxes = ws[f'F{row}'].value
withdrawal = ws[f'G{row}'].value
total_out = ws[f'H{row}'].value
reinvest = ws[f'I{row}'].value
principal_end = ws[f'J{row}'].value
print(f"{cycle:<10} ${principal_start:>15,.0f} ${interest:>13,.0f} ${taxes:>13,.0f} ${withdrawal:>13,.0f} ${total_out:>13,.0f} ${reinvest:>13,.0f} ${principal_end:>15,.0f}")
print("\n\nKEY FORMULAS:")
print("-"*70)
print("• Interest per cycle: Principal × 4.5% × (90/365)")
print("• Taxes on interest: Interest × 53.53%")
print("• Personal withdrawal: Interest × 10%")
print("• Total withdrawal: Taxes + Personal withdrawal")
print("• Reinvestment: Interest - Total withdrawal")
print("• Next cycle principal: Previous principal + Reinvestment")
total_withdrawn = ws['G7'].value
print(f"\n\nTOTAL PERSONAL WITHDRAWALS (8 cycles): ${total_withdrawn:,.2f}")
print(f"Average per cycle: ${total_withdrawn/8:,.2f}")
print(f"Daily income: ${ws['G8'].value:,.2f}")
print(f"Annual income: ${ws['G8'].value * 365:,.2f}")

172
app.py Normal file
View File

@@ -0,0 +1,172 @@
"""
Flask Backend for Lottery Investment Calculator
Provides API endpoints for jackpots and investment calculations
"""
from flask import Flask, jsonify, request
from flask_cors import CORS
import requests
from bs4 import BeautifulSoup
import urllib3
from playwright.sync_api import sync_playwright
import re
from lottery_calculator import calculate_us_lottery, calculate_canadian_lottery
# Suppress SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
app = Flask(__name__)
CORS(app) # Enable CORS for Next.js frontend
# Common headers to mimic a browser request
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Cache-Control": "max-age=0",
}
def get_us_lotteries():
"""Fetch Powerball and Mega Millions jackpots"""
results = {"Powerball": None, "Mega Millions": None}
# Powerball
try:
resp = requests.get("https://www.lotto.net/powerball", timeout=10, verify=False, headers=HEADERS)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
# Extract numeric value
match = re.search(r'\$(\d+(?:,\d+)?(?:\.\d+)?)', next_line)
if match:
value = float(match.group(1).replace(',', ''))
results["Powerball"] = value * 1_000_000
break
except Exception as e:
print(f"Error fetching Powerball: {e}")
# Mega Millions
try:
resp = requests.get("https://www.lotto.net/mega-millions", timeout=10, verify=False, headers=HEADERS)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
# Extract numeric value
match = re.search(r'\$(\d+(?:,\d+)?(?:\.\d+)?)', next_line)
if match:
value = float(match.group(1).replace(',', ''))
results["Mega Millions"] = value * 1_000_000
break
except Exception as e:
print(f"Error fetching Mega Millions: {e}")
return results
def get_canadian_lotteries():
"""Fetch Lotto Max and Lotto 6/49 jackpots using Playwright"""
results = {"Lotto Max": None, "Lotto 6/49": None}
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://www.olg.ca/", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(3000)
content = page.content()
browser.close()
# Lotto Max
lotto_max_match = re.search(r'LOTTO\s*MAX(?:(?!LOTTO\s*6/49).)*?\$\s*([\d.,]+)\s*Million', content, re.IGNORECASE | re.DOTALL)
if lotto_max_match:
value = float(lotto_max_match.group(1).replace(',', ''))
results["Lotto Max"] = value * 1_000_000
# Lotto 6/49
lotto_649_match = re.search(r'LOTTO\s*6/49(?:(?!LOTTO\s*MAX).)*?\$\s*([\d.,]+)\s*Million', content, re.IGNORECASE | re.DOTALL)
if lotto_649_match:
value = float(lotto_649_match.group(1).replace(',', ''))
results["Lotto 6/49"] = value * 1_000_000
except Exception as e:
print(f"Error fetching Canadian lotteries: {e}")
return results
@app.route('/api/jackpots', methods=['GET'])
def get_jackpots():
"""API endpoint to get all lottery jackpots"""
us_lotteries = get_us_lotteries()
canadian_lotteries = get_canadian_lotteries()
return jsonify({
"us": {
"powerball": us_lotteries["Powerball"],
"megaMillions": us_lotteries["Mega Millions"]
},
"canadian": {
"lottoMax": canadian_lotteries["Lotto Max"],
"lotto649": canadian_lotteries["Lotto 6/49"]
}
})
@app.route('/api/calculate', methods=['POST'])
def calculate():
"""API endpoint to calculate investment returns"""
data = request.json
jackpot = data.get('jackpot')
lottery_type = data.get('type', 'us') # 'us' or 'canadian'
invest_percentage = data.get('investPercentage', 0.90)
annual_return = data.get('annualReturn', 0.045)
cycles = data.get('cycles', 8)
if not jackpot:
return jsonify({"error": "Jackpot amount is required"}), 400
try:
if lottery_type == 'us':
result = calculate_us_lottery(jackpot, invest_percentage, annual_return, cycles)
else:
result = calculate_canadian_lottery(jackpot, invest_percentage, annual_return, cycles)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({"status": "ok"})
if __name__ == '__main__':
print("🎰 Lottery Investment Calculator API")
print("=" * 50)
print("Starting Flask server on http://localhost:5000")
print("API Endpoints:")
print(" - GET /api/jackpots - Get current jackpots")
print(" - POST /api/calculate - Calculate investments")
print(" - GET /api/health - Health check")
print("=" * 50)
# Bind to 0.0.0.0 so the Flask app is reachable from outside the container
app.run(debug=True, host='0.0.0.0', port=5000)

94
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,94 @@
version: '3.8'
services:
# Nginx Reverse Proxy
nginx:
image: nginx:alpine
container_name: lottery-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- backend
- frontend
restart: always
networks:
- lottery-network
# Flask Backend
backend:
build:
context: .
dockerfile: Dockerfile.backend
container_name: lottery-backend
expose:
- "5000"
environment:
- FLASK_ENV=production
- PYTHONUNBUFFERED=1
restart: always
networks:
- lottery-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
deploy:
resources:
limits:
cpus: '1'
memory: 2G
reservations:
cpus: '0.5'
memory: 1G
# Next.js Frontend
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
container_name: lottery-frontend
expose:
- "3000"
environment:
- NEXT_PUBLIC_API_URL=http://backend:5000
- NODE_ENV=production
depends_on:
- backend
restart: always
networks:
- lottery-network
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# Email Scheduler
email-scheduler:
build:
context: .
dockerfile: Dockerfile.email
container_name: lottery-email
environment:
- PYTHONUNBUFFERED=1
restart: always
networks:
- lottery-network
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
networks:
lottery-network:
driver: bridge

59
docker-compose.yml Normal file
View File

@@ -0,0 +1,59 @@
version: '3.8'
services:
# Flask Backend
backend:
build:
context: .
dockerfile: Dockerfile.backend
container_name: lottery-backend
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
- PYTHONUNBUFFERED=1
restart: unless-stopped
networks:
- lottery-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Next.js Frontend
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
args:
NEXT_PUBLIC_API_URL: http://localhost:5000
container_name: lottery-frontend
ports:
- "3003:3000"
environment:
- NEXT_PUBLIC_API_URL=http://localhost:5000
depends_on:
- backend
restart: unless-stopped
networks:
- lottery-network
# Email Scheduler (Optional - runs daily at 7 AM)
email-scheduler:
build:
context: .
dockerfile: Dockerfile.email
container_name: lottery-email
environment:
- PYTHONUNBUFFERED=1
restart: unless-stopped
networks:
- lottery-network
profiles:
- email # Only start if explicitly requested
networks:
lottery-network:
driver: bridge

75
docker-start.bat Normal file
View File

@@ -0,0 +1,75 @@
@echo off
echo ====================================================
echo 🎰 Lottery Investment Calculator - Docker Setup
echo ====================================================
echo.
REM Check if Docker is installed
docker --version >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Docker is not installed. Please install Docker Desktop first.
echo Download: https://www.docker.com/products/docker-desktop
pause
exit /b 1
)
REM Check if Docker is running
docker info >nul 2>&1
if %errorlevel% neq 0 (
echo ❌ Docker is not running. Please start Docker Desktop.
pause
exit /b 1
)
echo ✅ Docker is installed and running
echo.
REM Build images
echo 🏗️ Building Docker images...
echo This may take 5-10 minutes on first run (downloading browsers, etc.)
echo.
docker-compose build
if %errorlevel% neq 0 (
echo ❌ Build failed. Check the errors above.
pause
exit /b 1
)
echo.
echo ✅ Build completed successfully!
echo.
REM Start containers
echo 🚀 Starting containers...
docker-compose up -d
if %errorlevel% neq 0 (
echo ❌ Failed to start containers.
pause
exit /b 1
)
echo.
echo ✅ All containers started successfully!
echo.
echo ====================================================
echo 🌐 Your application is now running:
echo ====================================================
echo.
echo Frontend: http://localhost:3003
echo Backend: http://localhost:5000
echo Health: http://localhost:5000/api/health
echo.
echo ====================================================
echo 📊 Useful commands:
echo ====================================================
echo.
echo View logs: docker-compose logs -f
echo Stop: docker-compose down
echo Restart: docker-compose restart
echo Rebuild: docker-compose up -d --build
echo.
echo 📖 See DOCKER_README.md for full documentation
echo.
pause

68
docker-start.sh Normal file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
echo "🎰 Lottery Investment Calculator - Docker Setup"
echo "================================================"
echo ""
# Check if Docker is installed
if ! command -v docker &> /dev/null; then
echo "❌ Docker is not installed. Please install Docker Desktop first."
echo " Download: https://www.docker.com/products/docker-desktop"
exit 1
fi
# Check if Docker is running
if ! docker info &> /dev/null; then
echo "❌ Docker is not running. Please start Docker Desktop."
exit 1
fi
echo "✅ Docker is installed and running"
echo ""
# Build images
echo "🏗️ Building Docker images..."
echo " This may take 5-10 minutes on first run (downloading browsers, etc.)"
echo ""
docker-compose build
if [ $? -ne 0 ]; then
echo "❌ Build failed. Check the errors above."
exit 1
fi
echo ""
echo "✅ Build completed successfully!"
echo ""
# Start containers
echo "🚀 Starting containers..."
docker-compose up -d
if [ $? -ne 0 ]; then
echo "❌ Failed to start containers."
exit 1
fi
echo ""
echo "✅ All containers started successfully!"
echo ""
echo "================================================"
echo "🌐 Your application is now running:"
echo "================================================"
echo ""
echo " Frontend: http://localhost:3003"
echo " Backend: http://localhost:5000"
echo " Health: http://localhost:5000/api/health"
echo ""
echo "================================================"
echo "📊 Useful commands:"
echo "================================================"
echo ""
echo " View logs: docker-compose logs -f"
echo " Stop: docker-compose down"
echo " Restart: docker-compose restart"
echo " Rebuild: docker-compose up -d --build"
echo ""
echo "📖 See DOCKER_README.md for full documentation"
echo ""

329
email_sender.py Normal file
View File

@@ -0,0 +1,329 @@
import schedule
import time
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
import asyncio
from playwright.async_api import async_playwright
import requests
from bs4 import BeautifulSoup
import urllib3
import re
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Email configuration
EMAIL_CONFIG = {
'smtp_server': 'smtp.gmail.com', # Change this for your email provider
'smtp_port': 587,
'sender_email': 'mblanke@gmail.com', # Replace with your email
'sender_password': 'vyapvyjjfrqpqnax', # App password (spaces removed)
'recipient_email': 'mblanke@gmail.com', # Replace with recipient email
}
# Common headers to mimic a browser request
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
}
# Scraping functions
def get_powerball():
"""Get Powerball jackpot from lotto.net"""
try:
url = "https://www.lotto.net/powerball"
response = requests.get(url, timeout=10, verify=False, headers=HEADERS)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Look for "Next Jackpot" text
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
# Parse the amount
match = re.search(r'\$\s*([\d,]+(?:\.\d+)?)\s*Million', next_line)
if match:
amount_str = match.group(1).replace(',', '')
return float(amount_str)
except Exception as e:
print(f"Error getting Powerball: {e}")
return None
def get_mega_millions():
"""Get Mega Millions jackpot from lotto.net"""
try:
url = "https://www.lotto.net/mega-millions"
response = requests.get(url, timeout=10, verify=False, headers=HEADERS)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
# Look for "Next Jackpot" text
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
# Parse the amount
match = re.search(r'\$\s*([\d,]+(?:\.\d+)?)\s*Million', next_line)
if match:
amount_str = match.group(1).replace(',', '')
return float(amount_str)
except Exception as e:
print(f"Error getting Mega Millions: {e}")
return None
async def get_canadian_lotteries():
"""Get Lotto Max and Lotto 6/49 jackpots using Playwright"""
lotto_max = None
lotto_649 = None
try:
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto('https://www.olg.ca/', wait_until='networkidle')
await page.wait_for_timeout(2000)
content = await page.content()
# Lotto Max pattern
lotto_max_pattern = r'LOTTO\s*MAX(?:(?!LOTTO\s*6/49).)*?\$\s*([\d.,]+)\s*Million'
match = re.search(lotto_max_pattern, content, re.IGNORECASE | re.DOTALL)
if match:
amount_str = match.group(1).replace(',', '')
lotto_max = float(amount_str)
# Lotto 6/49 pattern
lotto_649_pattern = r'LOTTO\s*6/49.*?\$\s*([\d.,]+)\s*Million'
match = re.search(lotto_649_pattern, content, re.IGNORECASE | re.DOTALL)
if match:
amount_str = match.group(1).replace(',', '')
lotto_649 = float(amount_str)
await browser.close()
except Exception as e:
print(f"Error getting Canadian lotteries: {e}")
return lotto_max, lotto_649
def format_currency(amount):
"""Format amount as currency"""
if amount is None:
return "Not available"
return f"${amount:,.0f}M"
def create_email_html(powerball, mega_millions, lotto_max, lotto_649):
"""Create HTML email content"""
html = f"""
<html>
<head>
<style>
body {{
font-family: Arial, sans-serif;
background-color: #f4f4f4;
padding: 20px;
}}
.container {{
background-color: white;
border-radius: 10px;
padding: 30px;
max-width: 600px;
margin: 0 auto;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}}
h1 {{
color: #2c3e50;
text-align: center;
margin-bottom: 30px;
}}
.lottery-section {{
margin-bottom: 30px;
}}
.lottery-section h2 {{
color: #34495e;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
margin-bottom: 15px;
}}
.lottery-item {{
background-color: #ecf0f1;
padding: 15px;
border-radius: 5px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}}
.lottery-name {{
font-weight: bold;
color: #2c3e50;
font-size: 16px;
}}
.lottery-amount {{
font-size: 24px;
font-weight: bold;
color: #27ae60;
}}
.tax-free {{
background-color: #2ecc71;
color: white;
padding: 3px 8px;
border-radius: 3px;
font-size: 10px;
margin-left: 10px;
}}
.footer {{
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #ecf0f1;
color: #7f8c8d;
font-size: 12px;
}}
.timestamp {{
color: #95a5a6;
font-size: 12px;
text-align: center;
margin-top: 10px;
}}
</style>
</head>
<body>
<div class="container">
<h1>🎰 Daily Lottery Jackpots</h1>
<div class="lottery-section">
<h2>🇺🇸 US Lotteries</h2>
<div class="lottery-item">
<div>
<span class="lottery-name">Powerball</span>
</div>
<span class="lottery-amount">{format_currency(powerball)}</span>
</div>
<div class="lottery-item">
<div>
<span class="lottery-name">Mega Millions</span>
</div>
<span class="lottery-amount">{format_currency(mega_millions)}</span>
</div>
</div>
<div class="lottery-section">
<h2>🇨🇦 Canadian Lotteries</h2>
<div class="lottery-item">
<div>
<span class="lottery-name">Lotto Max</span>
<span class="tax-free">TAX FREE</span>
</div>
<span class="lottery-amount">{format_currency(lotto_max)}</span>
</div>
<div class="lottery-item">
<div>
<span class="lottery-name">Lotto 6/49</span>
<span class="tax-free">TAX FREE</span>
</div>
<span class="lottery-amount">{format_currency(lotto_649)}</span>
</div>
</div>
<div class="footer">
<p>💡 Remember: Canadian lottery winnings are tax-free!</p>
<p>📊 Visit your Lottery Investment Calculator for detailed analysis</p>
</div>
<div class="timestamp">
Generated on {datetime.now().strftime('%B %d, %Y at %I:%M %p')}
</div>
</div>
</body>
</html>
"""
return html
def send_email(subject, html_content):
"""Send email with jackpot information"""
try:
# Create message
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = EMAIL_CONFIG['sender_email']
msg['To'] = EMAIL_CONFIG['recipient_email']
# Attach HTML content
html_part = MIMEText(html_content, 'html')
msg.attach(html_part)
# Send email
with smtplib.SMTP(EMAIL_CONFIG['smtp_server'], EMAIL_CONFIG['smtp_port']) as server:
server.starttls()
server.login(EMAIL_CONFIG['sender_email'], EMAIL_CONFIG['sender_password'])
server.send_message(msg)
print(f"✅ Email sent successfully at {datetime.now().strftime('%I:%M %p')}")
return True
except Exception as e:
print(f"❌ Error sending email: {e}")
return False
def send_daily_jackpots():
"""Fetch jackpots and send email"""
print(f"\n{'='*50}")
print(f"🎰 Fetching lottery jackpots at {datetime.now().strftime('%I:%M %p')}")
print(f"{'='*50}")
# Get US lotteries
print("📊 Fetching Powerball...")
powerball = get_powerball()
print(f" Powerball: {format_currency(powerball)}")
print("📊 Fetching Mega Millions...")
mega_millions = get_mega_millions()
print(f" Mega Millions: {format_currency(mega_millions)}")
# Get Canadian lotteries
print("📊 Fetching Canadian lotteries...")
lotto_max, lotto_649 = asyncio.run(get_canadian_lotteries())
print(f" Lotto Max: {format_currency(lotto_max)}")
print(f" Lotto 6/49: {format_currency(lotto_649)}")
# Create email content
subject = f"🎰 Daily Lottery Report - {datetime.now().strftime('%B %d, %Y')}"
html_content = create_email_html(powerball, mega_millions, lotto_max, lotto_649)
# Send email
print("\n📧 Sending email...")
send_email(subject, html_content)
print(f"{'='*50}\n")
def main():
"""Main function to schedule and run the email sender"""
print("🚀 Lottery Jackpot Email Scheduler Started")
print("=" * 50)
print(f"📧 Emails will be sent to: {EMAIL_CONFIG['recipient_email']}")
print(f"⏰ Scheduled time: 7:00 AM daily")
print(f"🔄 Current time: {datetime.now().strftime('%I:%M %p')}")
print("=" * 50)
print("\nPress Ctrl+C to stop the scheduler\n")
# Schedule the job for 7:00 AM every day
schedule.every().day.at("07:00").do(send_daily_jackpots)
# Optional: Uncomment to send immediately for testing
# print("🧪 Sending test email now...")
# send_daily_jackpots()
# Keep the script running
while True:
schedule.run_pending()
time.sleep(60) # Check every minute
if __name__ == "__main__":
main()

1
frontend Submodule

Submodule frontend added at dcb2161ea4

186
import requests.py Normal file
View File

@@ -0,0 +1,186 @@
import requests
from bs4 import BeautifulSoup
import urllib3
from playwright.sync_api import sync_playwright
import re
# Suppress SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Common headers to mimic a browser request
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Cache-Control": "max-age=0",
}
def get_powerball():
url = "https://www.lotto.net/powerball"
try:
resp = requests.get(url, timeout=10, verify=False, headers=HEADERS)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Look for divs containing "Next Jackpot" and "$XXX Million"
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
return next_line
return "Not found"
except Exception as e:
return f"Error: {e}"
def get_mega_millions():
url = "https://www.lotto.net/mega-millions"
try:
resp = requests.get(url, timeout=10, verify=False, headers=HEADERS)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
# Look for divs containing "Next Jackpot" and "$XXX Million"
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
return next_line
return "Not found"
except Exception as e:
return f"Error: {e}"
def get_lotto_max():
url = "https://www.olg.ca/"
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle", timeout=30000)
# Wait for lottery content to load
page.wait_for_timeout(3000)
content = page.content()
browser.close()
# Search for Lotto Max jackpot - look for the pattern more carefully
# Match "LOTTO MAX" followed by jackpot info, avoiding 649
match = re.search(r'LOTTO\s*MAX(?:(?!LOTTO\s*6/49).)*?\$\s*([\d.,]+)\s*Million', content, re.IGNORECASE | re.DOTALL)
if match:
return f"${match.group(1)} Million"
return "Not found"
except Exception as e:
return f"Error: {e}"
def get_lotto_649():
url = "https://www.olg.ca/"
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle", timeout=30000)
# Wait for lottery content to load
page.wait_for_timeout(3000)
content = page.content()
browser.close()
# Search for Lotto 6/49 jackpot - be more specific
match = re.search(r'LOTTO\s*6/49(?:(?!LOTTO\s*MAX).)*?\$\s*([\d.,]+)\s*Million', content, re.IGNORECASE | re.DOTALL)
if match:
return f"${match.group(1)} Million"
return "Not found"
except Exception as e:
return f"Error: {e}"
def get_olg_lotteries():
"""
Fetches jackpot amounts for Lotto Max and Lotto 6/49 from OLG website using Playwright.
Returns a dict with keys 'Lotto Max' and 'Lotto 6/49'.
"""
url = "https://www.olg.ca/"
results = {"Lotto Max": "Not found", "Lotto 6/49": "Not found"}
try:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle", timeout=30000)
# Wait for lottery content to load
page.wait_for_timeout(3000)
content = page.content()
browser.close()
# Lotto Max - be more specific to avoid 649
lotto_max_match = re.search(r'LOTTO\s*MAX(?:(?!LOTTO\s*6/49).)*?\$\s*([\d.,]+)\s*Million', content, re.IGNORECASE | re.DOTALL)
if lotto_max_match:
results["Lotto Max"] = f"${lotto_max_match.group(1)} Million"
# Lotto 6/49 - be more specific to avoid MAX
lotto_649_match = re.search(r'LOTTO\s*6/49(?:(?!LOTTO\s*MAX).)*?\$\s*([\d.,]+)\s*Million', content, re.IGNORECASE | re.DOTALL)
if lotto_649_match:
results["Lotto 6/49"] = f"${lotto_649_match.group(1)} Million"
except Exception as e:
results = {"Lotto Max": f"Error: {e}", "Lotto 6/49": f"Error: {e}"}
return results
def get_lottery_usa():
"""
Fetches jackpot amounts for Powerball and Mega Millions from lotto.net.
Returns a dict with keys 'Powerball' and 'Mega Millions'.
"""
results = {"Powerball": "Not found", "Mega Millions": "Not found"}
# Get Powerball
try:
resp = requests.get("https://www.lotto.net/powerball", timeout=10, verify=False, headers=HEADERS)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
results["Powerball"] = next_line
break
except Exception as e:
results["Powerball"] = f"Error: {e}"
# Get Mega Millions
try:
resp = requests.get("https://www.lotto.net/mega-millions", timeout=10, verify=False, headers=HEADERS)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "html.parser")
all_text = soup.get_text()
lines = all_text.split('\n')
for i, line in enumerate(lines):
if 'Next Jackpot' in line and i + 1 < len(lines):
next_line = lines[i + 1].strip()
if '$' in next_line and 'Million' in next_line:
results["Mega Millions"] = next_line
break
except Exception as e:
results["Mega Millions"] = f"Error: {e}"
return results
if __name__ == "__main__":
print("🎰 Current Lottery Jackpots")
print("------------------------------")
print(f"Powerball: {get_powerball()}")
print(f"Mega Millions: {get_mega_millions()}")
print(f"Lotto Max: {get_lotto_max()}")
print(f"Lotto 6/49: {get_lotto_649()}")
# Add OLG results as fallback/alternative
olg = get_olg_lotteries()
print(f"OLG Lotto Max: {olg['Lotto Max']}")
print(f"OLG Lotto 6/49: {olg['Lotto 6/49']}")
# Add Lottery USA results
lottery_usa = get_lottery_usa()
print(f"Lottery USA Powerball: {lottery_usa['Powerball']}")
print(f"Lottery USA Mega Millions: {lottery_usa['Mega Millions']}")

195
lottery_calculator.py Normal file
View File

@@ -0,0 +1,195 @@
"""
Lottery Investment Calculator
Handles both US and Canadian lottery calculations
"""
def calculate_us_lottery(jackpot, invest_percentage=0.90, annual_return=0.045, cycles=8):
"""
Calculate investment returns for US lottery winnings
Args:
jackpot: Original jackpot amount (USD)
invest_percentage: Percentage to invest (default 90%)
annual_return: Annual return rate (default 4.5%)
cycles: Number of 90-day cycles to calculate (default 8)
"""
# US Lottery calculations
cash_sum = jackpot * 0.52 # Lump sum is 52%
federal_tax = cash_sum * 0.37
state_tax = cash_sum * 0.055
net_amount = cash_sum - federal_tax - state_tax
# Convert to Canadian dollars
canadian_amount = net_amount * 1.35
# Split into investment and fun money
investment_principal = canadian_amount * invest_percentage
fun_money = canadian_amount * (1 - invest_percentage)
# Calculate cycles
cycle_results = []
principal = investment_principal
total_personal_withdrawals = 0
for cycle in range(1, cycles + 1):
# Interest for 90 days
interest_earned = principal * annual_return * (90/365)
# Taxes on investment income (53.53%)
taxes_owed = interest_earned * 0.5353
# Personal withdrawal (10% of interest)
personal_withdrawal = interest_earned * 0.10
# Total withdrawal
total_withdrawal = taxes_owed + personal_withdrawal
# Reinvestment
reinvestment = interest_earned - total_withdrawal
# New principal
new_principal = principal + reinvestment
total_personal_withdrawals += personal_withdrawal
cycle_results.append({
'cycle': cycle,
'principal_start': principal,
'interest_earned': interest_earned,
'taxes_owed': taxes_owed,
'personal_withdrawal': personal_withdrawal,
'total_withdrawal': total_withdrawal,
'reinvestment': reinvestment,
'principal_end': new_principal
})
principal = new_principal
# Calculate daily income
net_daily_income = (investment_principal * annual_return * 0.5353) / 365
return {
'country': 'US',
'original_jackpot': jackpot,
'cash_sum': cash_sum,
'federal_tax': federal_tax,
'state_tax': state_tax,
'net_amount_usd': net_amount,
'net_amount_cad': canadian_amount,
'investment_principal': investment_principal,
'fun_money': fun_money,
'net_daily_income': net_daily_income,
'annual_income': net_daily_income * 365,
'total_personal_withdrawals': total_personal_withdrawals,
'final_principal': principal,
'cycles': cycle_results
}
def calculate_canadian_lottery(jackpot, invest_percentage=0.90, annual_return=0.045, cycles=8):
"""
Calculate investment returns for Canadian lottery winnings
Args:
jackpot: Original jackpot amount (CAD) - TAX FREE!
invest_percentage: Percentage to invest (default 90%)
annual_return: Annual return rate (default 4.5%)
cycles: Number of 90-day cycles to calculate (default 8)
"""
# Canadian lotteries - NO TAX on winnings!
net_amount = jackpot
# Split into investment and fun money
investment_principal = net_amount * invest_percentage
fun_money = net_amount * (1 - invest_percentage)
# Calculate cycles
cycle_results = []
principal = investment_principal
total_personal_withdrawals = 0
for cycle in range(1, cycles + 1):
# Interest for 90 days
interest_earned = principal * annual_return * (90/365)
# Taxes on investment income (53.53%)
taxes_owed = interest_earned * 0.5353
# Personal withdrawal (10% of interest)
personal_withdrawal = interest_earned * 0.10
# Total withdrawal
total_withdrawal = taxes_owed + personal_withdrawal
# Reinvestment
reinvestment = interest_earned - total_withdrawal
# New principal
new_principal = principal + reinvestment
total_personal_withdrawals += personal_withdrawal
cycle_results.append({
'cycle': cycle,
'principal_start': principal,
'interest_earned': interest_earned,
'taxes_owed': taxes_owed,
'personal_withdrawal': personal_withdrawal,
'total_withdrawal': total_withdrawal,
'reinvestment': reinvestment,
'principal_end': new_principal
})
principal = new_principal
# Calculate daily income
net_daily_income = (investment_principal * annual_return * 0.5353) / 365
return {
'country': 'Canada',
'original_jackpot': jackpot,
'net_amount_cad': net_amount,
'investment_principal': investment_principal,
'fun_money': fun_money,
'net_daily_income': net_daily_income,
'annual_income': net_daily_income * 365,
'total_personal_withdrawals': total_personal_withdrawals,
'final_principal': principal,
'cycles': cycle_results
}
if __name__ == "__main__":
# Test with current jackpots
print("=" * 80)
print("US LOTTERY - MEGA MILLIONS ($547M)")
print("=" * 80)
us_result = calculate_us_lottery(547_000_000)
print(f"Original Jackpot: ${us_result['original_jackpot']:,.0f}")
print(f"Cash Sum (52%): ${us_result['cash_sum']:,.0f}")
print(f"After Taxes (USD): ${us_result['net_amount_usd']:,.0f}")
print(f"After Taxes (CAD): ${us_result['net_amount_cad']:,.0f}")
print(f"Investment (90%): ${us_result['investment_principal']:,.0f}")
print(f"Fun Money (10%): ${us_result['fun_money']:,.0f}")
print(f"Daily Income: ${us_result['net_daily_income']:,.2f}")
print(f"Annual Income: ${us_result['annual_income']:,.2f}")
print(f"Final Principal (after 8 cycles): ${us_result['final_principal']:,.0f}")
print("\n" + "=" * 80)
print("CANADIAN LOTTERY - LOTTO 6/49 ($32M CAD)")
print("=" * 80)
can_result = calculate_canadian_lottery(32_000_000)
print(f"Original Jackpot (TAX FREE!): ${can_result['original_jackpot']:,.0f}")
print(f"Investment (90%): ${can_result['investment_principal']:,.0f}")
print(f"Fun Money (10%): ${can_result['fun_money']:,.0f}")
print(f"Daily Income: ${can_result['net_daily_income']:,.2f}")
print(f"Annual Income: ${can_result['annual_income']:,.2f}")
print(f"Final Principal (after 8 cycles): ${can_result['final_principal']:,.0f}")
print("\n" + "=" * 80)
print("COMPARISON")
print("=" * 80)
print(f"US ($547M) - You keep: ${us_result['net_amount_cad']:,.0f} CAD after taxes")
print(f"Canadian ($32M) - You keep: ${can_result['net_amount_cad']:,.0f} CAD (NO TAXES!)")
print(f"\nUS Daily Income: ${us_result['net_daily_income']:,.2f}")
print(f"Canadian Daily Income: ${can_result['net_daily_income']:,.2f}")

BIN
megamillions_debug.html Normal file

Binary file not shown.

5
powerball_numbers.html Normal file

File diff suppressed because one or more lines are too long

31
read_excel.py Normal file
View File

@@ -0,0 +1,31 @@
import openpyxl
import pandas as pd
# Load the workbook
wb = openpyxl.load_workbook('Max.xlsx')
print(f"Sheets: {wb.sheetnames}\n")
# Read each sheet
for sheet_name in wb.sheetnames:
print(f"\n{'='*60}")
print(f"SHEET: {sheet_name}")
print('='*60)
ws = wb[sheet_name]
# Print first 30 rows
for i, row in enumerate(ws.iter_rows(values_only=True), 1):
if any(cell is not None for cell in row): # Skip completely empty rows
print(f"Row {i}: {row}")
if i >= 30:
break
print("\n\nNow using pandas for better formatting:")
print("="*60)
# Try reading with pandas
for sheet_name in wb.sheetnames:
print(f"\n\nSheet: {sheet_name}")
print("-"*60)
df = pd.read_excel('Max.xlsx', sheet_name=sheet_name)
print(df.head(20))
print(f"\nColumns: {df.columns.tolist()}")

9
requirements.txt Normal file
View File

@@ -0,0 +1,9 @@
flask
flask-cors
requests
beautifulsoup4
playwright
urllib3
openpyxl
pandas
schedule

View File

@@ -0,0 +1,27 @@
Param(
[Parameter(Mandatory=$true)][string]$RepoPath
)
$ErrorActionPreference = "Stop"
$RootDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$Target = (Resolve-Path $RepoPath).Path
New-Item -ItemType Directory -Force -Path (Join-Path $Target ".claude\agents") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $Target "SKILLS") | Out-Null
Copy-Item -Force (Join-Path $RootDir "AGENTS.md") (Join-Path $Target "AGENTS.md")
if (Test-Path (Join-Path $RootDir "SKILLS.md")) {
Copy-Item -Force (Join-Path $RootDir "SKILLS.md") (Join-Path $Target "SKILLS.md")
}
Copy-Item -Recurse -Force (Join-Path $RootDir "SKILLS\*") (Join-Path $Target "SKILLS")
Copy-Item -Recurse -Force (Join-Path $RootDir ".claude\agents\*") (Join-Path $Target ".claude\agents")
if (-not (Test-Path (Join-Path $Target ".gitlab-ci.yml")) -and (Test-Path (Join-Path $RootDir ".gitlab-ci.yml"))) {
Copy-Item -Force (Join-Path $RootDir ".gitlab-ci.yml") (Join-Path $Target ".gitlab-ci.yml")
}
if (-not (Test-Path (Join-Path $Target ".github")) -and (Test-Path (Join-Path $RootDir ".github"))) {
Copy-Item -Recurse -Force (Join-Path $RootDir ".github") (Join-Path $Target ".github")
}
Write-Host "Bootstrapped repo: $Target"
Write-Host "Next: wire DoD gates to your stack and run scripts\dod.ps1"

33
scripts/bootstrap_repo.sh Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
# Copy backbone files into an existing repo directory.
# Usage: ./scripts/bootstrap_repo.sh /path/to/repo
TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
echo "Usage: $0 /path/to/repo"
exit 2
fi
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
mkdir -p "$TARGET/.claude/agents"
mkdir -p "$TARGET/SKILLS"
# Copy minimal backbone (adjust to taste)
cp -f "$SRC_DIR/AGENTS.md" "$TARGET/AGENTS.md"
cp -f "$SRC_DIR/SKILLS.md" "$TARGET/SKILLS.md" || true
cp -rf "$SRC_DIR/SKILLS/" "$TARGET/" || true
cp -rf "$SRC_DIR/.claude/agents/" "$TARGET/.claude/agents/" || true
# Optional: CI templates
if [[ ! -f "$TARGET/.gitlab-ci.yml" && -f "$SRC_DIR/.gitlab-ci.yml" ]]; then
cp -f "$SRC_DIR/.gitlab-ci.yml" "$TARGET/.gitlab-ci.yml"
fi
if [[ ! -d "$TARGET/.github" && -d "$SRC_DIR/.github" ]]; then
cp -rf "$SRC_DIR/.github" "$TARGET/.github"
fi
echo "Bootstrapped repo: $TARGET"
echo "Next: wire DoD gates to your stack (npm/pip) and run scripts/dod.sh"

42
scripts/dod.ps1 Normal file
View File

@@ -0,0 +1,42 @@
$ErrorActionPreference = "Stop"
Write-Host "== DoD Gate =="
$root = Split-Path -Parent $PSScriptRoot
Set-Location $root
function Has-Command($name) {
return $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
$hasNode = Test-Path ".\package.json"
$hasPy = (Test-Path ".\pyproject.toml") -or (Test-Path ".\requirements.txt") -or (Test-Path ".\requirements-dev.txt")
if ($hasNode) {
if (-not (Has-Command "npm")) { throw "npm not found" }
Write-Host "+ npm ci"; npm ci
$pkg = Get-Content ".\package.json" | ConvertFrom-Json
if ($pkg.scripts.lint) { Write-Host "+ npm run lint"; npm run lint }
if ($pkg.scripts.typecheck) { Write-Host "+ npm run typecheck"; npm run typecheck }
if ($pkg.scripts.test) { Write-Host "+ npm test"; npm test }
if ($pkg.scripts.build) { Write-Host "+ npm run build"; npm run build }
}
if ($hasPy) {
if (-not (Has-Command "python")) { throw "python not found" }
Write-Host "+ python -m pip install -U pip"; python -m pip install -U pip
if (Test-Path ".\requirements.txt") { Write-Host "+ pip install -r requirements.txt"; pip install -r requirements.txt }
if (Test-Path ".\requirements-dev.txt") { Write-Host "+ pip install -r requirements-dev.txt"; pip install -r requirements-dev.txt }
if (Has-Command "ruff") {
Write-Host "+ ruff check ."; ruff check .
Write-Host "+ ruff format --check ."; ruff format --check .
}
if (Has-Command "pytest") { Write-Host "+ pytest -q"; pytest -q }
}
if (-not $hasNode -and -not $hasPy) {
Write-Host "No package.json or Python dependency files detected."
Write-Host "Customize scripts\dod.ps1 for this repo stack."
}
Write-Host "DoD PASS"

43
scripts/dod.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail
echo "== DoD Gate =="
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
fail() { echo "DoD FAIL: $1" >&2; exit 1; }
run() { echo "+ $*"; "$@"; }
has() { command -v "$1" >/dev/null 2>&1; }
HAS_NODE=0
HAS_PY=0
[[ -f package.json ]] && HAS_NODE=1
[[ -f pyproject.toml || -f requirements.txt || -f requirements-dev.txt ]] && HAS_PY=1
if [[ $HAS_NODE -eq 1 ]]; then
has npm || fail "npm not found"
run npm ci
if has jq && jq -e '.scripts.lint' package.json >/dev/null 2>&1; then run npm run lint; fi
if has jq && jq -e '.scripts.typecheck' package.json >/dev/null 2>&1; then run npm run typecheck; fi
if has jq && jq -e '.scripts.test' package.json >/dev/null 2>&1; then run npm test; fi
if has jq && jq -e '.scripts.build' package.json >/dev/null 2>&1; then run npm run build; fi
fi
if [[ $HAS_PY -eq 1 ]]; then
has python3 || fail "python3 not found"
run python3 -m pip install -U pip
if [[ -f requirements.txt ]]; then run python3 -m pip install -r requirements.txt; fi
if [[ -f requirements-dev.txt ]]; then run python3 -m pip install -r requirements-dev.txt; fi
if has ruff; then
run ruff check . || true
run ruff format --check . || true
fi
if has pytest; then run pytest -q || true; fi
fi
if [[ $HAS_NODE -eq 0 && $HAS_PY -eq 0 ]]; then
echo "No package.json or Python dependency files detected."
echo "Customize scripts/dod.sh for this repo stack."
fi
echo "DoD PASS"

56
scripts/monday.ps1 Normal file
View File

@@ -0,0 +1,56 @@
Param(
[Parameter(Mandatory=$false)][string]$Command = "status",
[Parameter(Mandatory=$false)][string]$RepoPath = ""
)
$ErrorActionPreference = "Stop"
$RootDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
Write-Host "== Dev Backbone Monday Runner =="
function Need-Cmd($name) {
if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
throw "Missing command: $name"
}
}
switch ($Command) {
"status" {
$code = (Get-Command code -ErrorAction SilentlyContinue)
$git = (Get-Command git -ErrorAction SilentlyContinue)
$docker = (Get-Command docker -ErrorAction SilentlyContinue)
Write-Host "[1] VS Code CLI:" ($code.Source ?? "NOT FOUND")
Write-Host "[2] Git: " ($git.Source ?? "NOT FOUND")
Write-Host "[3] Docker: " ($docker.Source ?? "NOT FOUND")
Write-Host ""
Write-Host "Profiles expected: Dev, Cyber, Infra"
Write-Host "Try: code --list-extensions --profile Dev"
}
"vscode-purge" {
Need-Cmd code
if ($env:CONFIRM -ne "YES") {
Write-Host "Refusing to uninstall extensions without CONFIRM=YES"
Write-Host "Run: `$env:CONFIRM='YES'; .\scripts\monday.ps1 -Command vscode-purge"
exit 2
}
& (Join-Path $RootDir "scripts\vscode_profiles.ps1") -Action purge
}
"vscode-install" {
Need-Cmd code
& (Join-Path $RootDir "scripts\vscode_profiles.ps1") -Action install
}
"repo-bootstrap" {
if ([string]::IsNullOrWhiteSpace($RepoPath)) {
throw "Usage: .\scripts\monday.ps1 -Command repo-bootstrap -RepoPath C:\path\to\repo"
}
& (Join-Path $RootDir "scripts\bootstrap_repo.ps1") -RepoPath $RepoPath
}
default {
throw "Unknown command: $Command"
}
}

66
scripts/monday.sh Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
# Monday Overhaul Runner (safe by default)
# Usage:
# ./scripts/monday.sh status
# ./scripts/monday.sh vscode-purge (requires CONFIRM=YES)
# ./scripts/monday.sh vscode-install
# ./scripts/monday.sh repo-bootstrap /path/to/repo
#
# Notes:
# - VS Code profile creation is easiest once via UI (Profiles: Create Profile).
# This script assumes profiles exist: Dev, Cyber, Infra.
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
echo "== Dev Backbone Monday Runner =="
echo "Repo: $ROOT_DIR"
echo
cmd="${1:-status}"
shift || true
need_cmd() {
command -v "$1" >/dev/null 2>&1 || { echo "Missing command: $1"; exit 1; }
}
case "$cmd" in
status)
echo "[1] VS Code CLI: $(command -v code || echo 'NOT FOUND')"
echo "[2] Git: $(command -v git || echo 'NOT FOUND')"
echo "[3] Docker: $(command -v docker || echo 'NOT FOUND')"
echo
echo "Profiles expected: Dev, Cyber, Infra"
echo "Try: code --list-extensions --profile Dev"
;;
vscode-purge)
need_cmd code
if [[ "${CONFIRM:-NO}" != "YES" ]]; then
echo "Refusing to uninstall extensions without CONFIRM=YES"
echo "Run: CONFIRM=YES ./scripts/monday.sh vscode-purge"
exit 2
fi
bash "$ROOT_DIR/scripts/vscode_profiles.sh" purge
;;
vscode-install)
need_cmd code
bash "$ROOT_DIR/scripts/vscode_profiles.sh" install
;;
repo-bootstrap)
repo_path="${1:-}"
if [[ -z "$repo_path" ]]; then
echo "Usage: ./scripts/monday.sh repo-bootstrap /path/to/repo"
exit 2
fi
bash "$ROOT_DIR/scripts/bootstrap_repo.sh" "$repo_path"
;;
*)
echo "Unknown command: $cmd"
exit 2
;;
esac

View File

@@ -0,0 +1,75 @@
Param(
[Parameter(Mandatory=$true)][ValidateSet("purge","install")][string]$Action
)
$ErrorActionPreference = "Stop"
function Profile-Exists([string]$ProfileName) {
try {
& code --list-extensions --profile $ProfileName | Out-Null
return $true
} catch {
return $false
}
}
# Curated extension sets (edit to taste)
$DevExt = @(
"GitHub.copilot",
"GitHub.copilot-chat",
"GitHub.vscode-pull-request-github",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-python.python",
"ms-python.vscode-pylance",
"ms-azuretools.vscode-docker",
"ms-vscode-remote.remote-ssh",
"ms-vscode-remote.remote-containers",
"redhat.vscode-yaml",
"yzhang.markdown-all-in-one"
)
$CyberExt = @($DevExt) # add more only if needed
$InfraExt = @(
"ms-azuretools.vscode-docker",
"ms-vscode-remote.remote-ssh",
"redhat.vscode-yaml",
"yzhang.markdown-all-in-one"
)
function Purge-Profile([string]$ProfileName) {
Write-Host "Purging extensions from profile: $ProfileName"
if (-not (Profile-Exists $ProfileName)) {
Write-Host "Profile not found: $ProfileName (create once via UI: Profiles: Create Profile)"
return
}
$exts = & code --list-extensions --profile $ProfileName
foreach ($ext in $exts) {
if ([string]::IsNullOrWhiteSpace($ext)) { continue }
& code --profile $ProfileName --uninstall-extension $ext | Out-Null
}
}
function Install-Profile([string]$ProfileName, [string[]]$Extensions) {
Write-Host "Installing extensions into profile: $ProfileName"
if (-not (Profile-Exists $ProfileName)) {
Write-Host "Profile not found: $ProfileName (create once via UI: Profiles: Create Profile)"
return
}
foreach ($ext in $Extensions) {
& code --profile $ProfileName --install-extension $ext | Out-Null
}
}
switch ($Action) {
"purge" {
Purge-Profile "Dev"
Purge-Profile "Cyber"
Purge-Profile "Infra"
}
"install" {
Install-Profile "Dev" $DevExt
Install-Profile "Cyber" $CyberExt
Install-Profile "Infra" $InfraExt
}
}

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -euo pipefail
# Manage extensions per VS Code profile.
# Requires profiles to exist: Dev, Cyber, Infra
# Actions:
# ./scripts/vscode_profiles.sh purge (uninstall ALL extensions from those profiles)
# ./scripts/vscode_profiles.sh install (install curated sets)
ACTION="${1:-}"
if [[ -z "$ACTION" ]]; then
echo "Usage: $0 {purge|install}"
exit 2
fi
need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1"; exit 1; }; }
need code
# Curated extension sets (edit to taste)
DEV_EXT=(
"GitHub.copilot"
"GitHub.copilot-chat"
"GitHub.vscode-pull-request-github"
"dbaeumer.vscode-eslint"
"esbenp.prettier-vscode"
"ms-python.python"
"ms-python.vscode-pylance"
"ms-azuretools.vscode-docker"
"ms-vscode-remote.remote-ssh"
"ms-vscode-remote.remote-containers"
"redhat.vscode-yaml"
"yzhang.markdown-all-in-one"
)
CYBER_EXT=(
"${DEV_EXT[@]}"
# Add only if you truly use them:
# "ms-kubernetes-tools.vscode-kubernetes-tools"
)
INFRA_EXT=(
"ms-azuretools.vscode-docker"
"ms-vscode-remote.remote-ssh"
"redhat.vscode-yaml"
"yzhang.markdown-all-in-one"
# Optional:
# "hashicorp.terraform"
)
purge_profile() {
local profile="$1"
echo "Purging extensions from profile: $profile"
# list may fail if profile doesn't exist
if ! code --list-extensions --profile "$profile" >/dev/null 2>&1; then
echo "Profile not found: $profile (create once via UI: Profiles: Create Profile)"
return 0
fi
code --list-extensions --profile "$profile" | while read -r ext; do
[[ -z "$ext" ]] && continue
code --profile "$profile" --uninstall-extension "$ext" || true
done
}
install_profile() {
local profile="$1"; shift
local exts=("$@")
echo "Installing extensions into profile: $profile"
if ! code --list-extensions --profile "$profile" >/dev/null 2>&1; then
echo "Profile not found: $profile (create once via UI: Profiles: Create Profile)"
return 0
fi
for ext in "${exts[@]}"; do
[[ "$ext" =~ ^# ]] && continue
code --profile "$profile" --install-extension "$ext"
done
}
case "$ACTION" in
purge)
purge_profile "Dev"
purge_profile "Cyber"
purge_profile "Infra"
;;
install)
install_profile "Dev" "${DEV_EXT[@]}"
install_profile "Cyber" "${CYBER_EXT[@]}"
install_profile "Infra" "${INFRA_EXT[@]}"
;;
*)
echo "Unknown action: $ACTION"
exit 2
;;
esac

137
send_email_now.py Normal file
View File

@@ -0,0 +1,137 @@
"""
Secure email sender that prompts for password instead of storing it.
This version is safer and works without App Passwords.
"""
import asyncio
from email_sender import (
get_powerball,
get_mega_millions,
get_canadian_lotteries,
create_email_html,
format_currency
)
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import getpass
def send_email_secure(sender_email, sender_password, recipient_email, subject, html_content):
"""Send email with provided credentials"""
try:
# Create message
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = sender_email
msg['To'] = recipient_email
# Attach HTML content
html_part = MIMEText(html_content, 'html')
msg.attach(html_part)
# Try Gmail first
try:
print(" Trying Gmail SMTP...")
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(sender_email, sender_password)
server.send_message(msg)
print(f"✅ Email sent successfully via Gmail!")
return True
except Exception as gmail_error:
print(f" Gmail failed: {gmail_error}")
# Try alternative method - Gmail SSL port
try:
print(" Trying Gmail SSL (port 465)...")
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.login(sender_email, sender_password)
server.send_message(msg)
print(f"✅ Email sent successfully via Gmail SSL!")
return True
except Exception as ssl_error:
print(f" Gmail SSL also failed: {ssl_error}")
raise
except Exception as e:
print(f"❌ Error sending email: {e}")
print("\n⚠️ Common issues:")
print(" 1. Gmail requires 2-Step Verification + App Password")
print(" 2. Check if 'Less secure app access' is enabled (not recommended)")
print(" 3. Verify your email and password are correct")
return False
def send_lottery_email():
"""Fetch jackpots and send email with secure password prompt"""
print("\n" + "="*60)
print("🎰 LOTTERY JACKPOT EMAIL SENDER")
print("="*60)
# Email configuration
sender_email = "mblanke@gmail.com"
recipient_email = "mblanke@gmail.com"
print(f"\n📧 Email will be sent from/to: {sender_email}")
print("\n🔐 Please enter your Gmail password:")
print(" (Note: Gmail may require an App Password if you have 2FA enabled)")
# Securely prompt for password (won't show on screen)
sender_password = getpass.getpass(" Password: ")
if not sender_password:
print("❌ No password provided. Exiting.")
return
print("\n" + "-"*60)
print("📊 Fetching lottery jackpots...")
print("-"*60)
# Get US lotteries
print("\n🇺🇸 US Lotteries:")
print(" Fetching Powerball...")
powerball = get_powerball()
print(f" ✓ Powerball: {format_currency(powerball)}")
print(" Fetching Mega Millions...")
mega_millions = get_mega_millions()
print(f" ✓ Mega Millions: {format_currency(mega_millions)}")
# Get Canadian lotteries
print("\n🇨🇦 Canadian Lotteries:")
print(" Fetching Lotto Max and Lotto 6/49...")
lotto_max, lotto_649 = asyncio.run(get_canadian_lotteries())
print(f" ✓ Lotto Max: {format_currency(lotto_max)}")
print(f" ✓ Lotto 6/49: {format_currency(lotto_649)}")
# Create email
print("\n" + "-"*60)
print("📧 Creating email...")
print("-"*60)
subject = f"🎰 Lottery Report - {datetime.now().strftime('%B %d, %Y')}"
html_content = create_email_html(powerball, mega_millions, lotto_max, lotto_649)
print(" ✓ Email content created")
# Send email
print("\n📤 Sending email...")
success = send_email_secure(sender_email, sender_password, recipient_email, subject, html_content)
if success:
print("\n" + "="*60)
print("✅ SUCCESS!")
print("="*60)
print(f"📧 Check your inbox at: {recipient_email}")
print("💡 The email includes all current jackpot amounts")
print(" with beautiful HTML formatting!")
else:
print("\n" + "="*60)
print("❌ FAILED!")
print("="*60)
print("\n🔧 Options to fix:")
print(" 1. Enable 2-Step Verification in Gmail")
print(" 2. Generate App Password: https://myaccount.google.com/apppasswords")
print(" 3. Use the App Password instead of regular password")
print("\n Alternative: Use a different email service (Outlook, Yahoo, etc.)")
print("\n")
if __name__ == "__main__":
send_lottery_email()

89
test_email.py Normal file
View File

@@ -0,0 +1,89 @@
"""
Quick test script to send a lottery jackpot email immediately.
Use this to verify your email configuration before scheduling.
"""
import asyncio
from email_sender import (
get_powerball,
get_mega_millions,
get_canadian_lotteries,
create_email_html,
send_email,
format_currency,
EMAIL_CONFIG
)
from datetime import datetime
def test_email():
"""Test the email sender by sending immediately"""
print("\n" + "="*60)
print("🧪 TESTING LOTTERY EMAIL SENDER")
print("="*60)
# Display current configuration
print(f"\n📧 Email Configuration:")
print(f" From: {EMAIL_CONFIG['sender_email']}")
print(f" To: {EMAIL_CONFIG['recipient_email']}")
print(f" SMTP Server: {EMAIL_CONFIG['smtp_server']}:{EMAIL_CONFIG['smtp_port']}")
if EMAIL_CONFIG['sender_email'] == 'your-email@gmail.com':
print("\n⚠️ WARNING: You need to update EMAIL_CONFIG in email_sender.py!")
print(" Please edit the file and add your email credentials.")
print(" See EMAIL_SETUP.md for instructions.")
return
print("\n" + "-"*60)
print("📊 Fetching lottery jackpots...")
print("-"*60)
# Get US lotteries
print("\n🇺🇸 US Lotteries:")
print(" Fetching Powerball...")
powerball = get_powerball()
print(f" ✓ Powerball: {format_currency(powerball)}")
print(" Fetching Mega Millions...")
mega_millions = get_mega_millions()
print(f" ✓ Mega Millions: {format_currency(mega_millions)}")
# Get Canadian lotteries
print("\n🇨🇦 Canadian Lotteries:")
print(" Fetching Lotto Max and Lotto 6/49...")
lotto_max, lotto_649 = asyncio.run(get_canadian_lotteries())
print(f" ✓ Lotto Max: {format_currency(lotto_max)}")
print(f" ✓ Lotto 6/49: {format_currency(lotto_649)}")
# Create email
print("\n" + "-"*60)
print("📧 Creating email...")
print("-"*60)
subject = f"🎰 TEST - Lottery Report - {datetime.now().strftime('%B %d, %Y')}"
html_content = create_email_html(powerball, mega_millions, lotto_max, lotto_649)
print(" ✓ Email content created")
# Send email
print("\n📤 Sending email...")
success = send_email(subject, html_content)
if success:
print("\n" + "="*60)
print("✅ TEST SUCCESSFUL!")
print("="*60)
print(f"📧 Check your inbox at: {EMAIL_CONFIG['recipient_email']}")
print("💡 If everything looks good, you can run email_sender.py")
print(" to schedule daily emails at 7:00 AM")
else:
print("\n" + "="*60)
print("❌ TEST FAILED!")
print("="*60)
print("🔍 Troubleshooting tips:")
print(" 1. Check your email and password in EMAIL_CONFIG")
print(" 2. For Gmail, use an App Password (not your regular password)")
print(" 3. Verify SMTP server and port are correct")
print(" 4. Check your internet connection")
print(" 5. See EMAIL_SETUP.md for detailed instructions")
print("\n")
if __name__ == "__main__":
test_email()