Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- k12/img_generator.py +18 -3
- k12/sheerid_verifier.py +3 -1
- k12/template_payroll.html +8 -8
- k12/template_staff.html +1 -1
- web/api/preview.py +6 -3
- web/api/verify.py +3 -1
- web/static/index.html +7 -0
- web/static/js/app.js +6 -1
k12/img_generator.py
CHANGED
|
@@ -48,7 +48,7 @@ CERTIFICATIONS = [
|
|
| 48 |
]
|
| 49 |
|
| 50 |
|
| 51 |
-
def _generate_teacher_data(first_name: str, last_name: str, school_name: str = "Springfield High School", email_domain: str = None) -> dict:
|
| 52 |
"""Generate consistent teacher data for all templates."""
|
| 53 |
full_name = f"{first_name} {last_name}"
|
| 54 |
initials = f"{first_name[0]}{last_name[0]}"
|
|
@@ -110,6 +110,20 @@ def _generate_teacher_data(first_name: str, last_name: str, school_name: str = "
|
|
| 110 |
"CERT_DATE": cert_date.strftime("%m/%Y"),
|
| 111 |
"SCHOOL_NAME": school_name,
|
| 112 |
"DISTRICT_NAME": district_name,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
|
| 115 |
|
|
@@ -163,7 +177,7 @@ def generate_teacher_png(first_name: str, last_name: str) -> bytes:
|
|
| 163 |
return png_bytes
|
| 164 |
|
| 165 |
|
| 166 |
-
def generate_both_pngs(first_name: str, last_name: str, school_name: str = "Springfield High School", email_domain: str = None) -> tuple:
|
| 167 |
"""Generate both Payroll and Staff Portal screenshots as PNGs.
|
| 168 |
|
| 169 |
Args:
|
|
@@ -171,6 +185,7 @@ def generate_both_pngs(first_name: str, last_name: str, school_name: str = "Spri
|
|
| 171 |
last_name: Teacher's last name
|
| 172 |
school_name: School name from SheerID
|
| 173 |
email_domain: Custom email domain (e.g., cityname.k12.state.us)
|
|
|
|
| 174 |
|
| 175 |
Returns:
|
| 176 |
tuple: (payroll_png_bytes, staff_png_bytes)
|
|
@@ -183,7 +198,7 @@ def generate_both_pngs(first_name: str, last_name: str, school_name: str = "Spri
|
|
| 183 |
) from exc
|
| 184 |
|
| 185 |
# Use same data for consistency across both documents
|
| 186 |
-
data = _generate_teacher_data(first_name, last_name, school_name, email_domain)
|
| 187 |
|
| 188 |
payroll_html = _render_template("template_payroll.html", data)
|
| 189 |
staff_html = _render_template("template_staff.html", data)
|
|
|
|
| 48 |
]
|
| 49 |
|
| 50 |
|
| 51 |
+
def _generate_teacher_data(first_name: str, last_name: str, school_name: str = "Springfield High School", email_domain: str = None, logo_url: str = None) -> dict:
|
| 52 |
"""Generate consistent teacher data for all templates."""
|
| 53 |
full_name = f"{first_name} {last_name}"
|
| 54 |
initials = f"{first_name[0]}{last_name[0]}"
|
|
|
|
| 110 |
"CERT_DATE": cert_date.strftime("%m/%Y"),
|
| 111 |
"SCHOOL_NAME": school_name,
|
| 112 |
"DISTRICT_NAME": district_name,
|
| 113 |
+
# Dynamic payroll dates (recent)
|
| 114 |
+
"PAY_DATE_1": (datetime.now() - timedelta(days=5)).strftime("%m/%d/%Y"),
|
| 115 |
+
"PAY_DATE_2": (datetime.now() - timedelta(days=20)).strftime("%m/%d/%Y"),
|
| 116 |
+
"PAY_DATE_3": (datetime.now() - timedelta(days=35)).strftime("%m/%d/%Y"),
|
| 117 |
+
"PAY_PERIOD_1_START": (datetime.now() - timedelta(days=20)).strftime("%m/%d/%Y"),
|
| 118 |
+
"PAY_PERIOD_1_END": (datetime.now() - timedelta(days=6)).strftime("%m/%d/%Y"),
|
| 119 |
+
"PAY_PERIOD_2_START": (datetime.now() - timedelta(days=35)).strftime("%m/%d/%Y"),
|
| 120 |
+
"PAY_PERIOD_2_END": (datetime.now() - timedelta(days=21)).strftime("%m/%d/%Y"),
|
| 121 |
+
"PAY_PERIOD_3_START": (datetime.now() - timedelta(days=50)).strftime("%m/%d/%Y"),
|
| 122 |
+
"PAY_PERIOD_3_END": (datetime.now() - timedelta(days=36)).strftime("%m/%d/%Y"),
|
| 123 |
+
"SCHOOL_YEAR": f"{datetime.now().year-1}-{datetime.now().year}" if datetime.now().month < 7 else f"{datetime.now().year}-{datetime.now().year+1}",
|
| 124 |
+
# Logo - use provided URL or default initials
|
| 125 |
+
"LOGO_HTML": f'<img src="{logo_url}" alt="School Logo" style="max-width: 45px; max-height: 45px; object-fit: contain;">' if logo_url else '{{DISTRICT_INITIALS}}',
|
| 126 |
+
"DISTRICT_INITIALS": school_name[:3].upper() if len(school_name) >= 3 else school_name.upper(),
|
| 127 |
}
|
| 128 |
|
| 129 |
|
|
|
|
| 177 |
return png_bytes
|
| 178 |
|
| 179 |
|
| 180 |
+
def generate_both_pngs(first_name: str, last_name: str, school_name: str = "Springfield High School", email_domain: str = None, logo_url: str = None) -> tuple:
|
| 181 |
"""Generate both Payroll and Staff Portal screenshots as PNGs.
|
| 182 |
|
| 183 |
Args:
|
|
|
|
| 185 |
last_name: Teacher's last name
|
| 186 |
school_name: School name from SheerID
|
| 187 |
email_domain: Custom email domain (e.g., cityname.k12.state.us)
|
| 188 |
+
logo_url: URL to school logo image
|
| 189 |
|
| 190 |
Returns:
|
| 191 |
tuple: (payroll_png_bytes, staff_png_bytes)
|
|
|
|
| 198 |
) from exc
|
| 199 |
|
| 200 |
# Use same data for consistency across both documents
|
| 201 |
+
data = _generate_teacher_data(first_name, last_name, school_name, email_domain, logo_url)
|
| 202 |
|
| 203 |
payroll_html = _render_template("template_payroll.html", data)
|
| 204 |
staff_html = _render_template("template_staff.html", data)
|
k12/sheerid_verifier.py
CHANGED
|
@@ -113,6 +113,7 @@ class SheerIDVerifier:
|
|
| 113 |
def verify(self, first_name: str = None, last_name: str = None,
|
| 114 |
email: str = None, birth_date: str = None,
|
| 115 |
school_id: str = None, email_domain: str = None,
|
|
|
|
| 116 |
hcaptcha_token: str = None, turnstile_token: str = None) -> Dict:
|
| 117 |
"""
|
| 118 |
Execute full verification flow, removing status polling to reduce time consumption
|
|
@@ -139,12 +140,13 @@ class SheerIDVerifier:
|
|
| 139 |
logger.info(f"Email: {email}")
|
| 140 |
logger.info(f"School: {school['name']}")
|
| 141 |
logger.info(f"Email Domain: {email_domain or 'auto-generated'}")
|
|
|
|
| 142 |
logger.info(f"Birthday: {birth_date}")
|
| 143 |
logger.info(f"Verification ID: {self.verification_id}")
|
| 144 |
|
| 145 |
# Generate both teacher verification screenshots
|
| 146 |
logger.info("Step 1/4: Generating teacher verification documents...")
|
| 147 |
-
payroll_png, staff_png = generate_both_pngs(first_name, last_name, school['name'], email_domain)
|
| 148 |
payroll_size = len(payroll_png)
|
| 149 |
staff_size = len(staff_png)
|
| 150 |
logger.info(f"✓ Payroll Portal: {payroll_size / 1024:.2f}KB, Staff Portal: {staff_size / 1024:.2f}KB")
|
|
|
|
| 113 |
def verify(self, first_name: str = None, last_name: str = None,
|
| 114 |
email: str = None, birth_date: str = None,
|
| 115 |
school_id: str = None, email_domain: str = None,
|
| 116 |
+
logo_url: str = None,
|
| 117 |
hcaptcha_token: str = None, turnstile_token: str = None) -> Dict:
|
| 118 |
"""
|
| 119 |
Execute full verification flow, removing status polling to reduce time consumption
|
|
|
|
| 140 |
logger.info(f"Email: {email}")
|
| 141 |
logger.info(f"School: {school['name']}")
|
| 142 |
logger.info(f"Email Domain: {email_domain or 'auto-generated'}")
|
| 143 |
+
logger.info(f"Logo URL: {logo_url or 'default'}")
|
| 144 |
logger.info(f"Birthday: {birth_date}")
|
| 145 |
logger.info(f"Verification ID: {self.verification_id}")
|
| 146 |
|
| 147 |
# Generate both teacher verification screenshots
|
| 148 |
logger.info("Step 1/4: Generating teacher verification documents...")
|
| 149 |
+
payroll_png, staff_png = generate_both_pngs(first_name, last_name, school['name'], email_domain, logo_url)
|
| 150 |
payroll_size = len(payroll_png)
|
| 151 |
staff_size = len(staff_png)
|
| 152 |
logger.info(f"✓ Payroll Portal: {payroll_size / 1024:.2f}KB, Staff Portal: {staff_size / 1024:.2f}KB")
|
k12/template_payroll.html
CHANGED
|
@@ -226,7 +226,7 @@
|
|
| 226 |
<div class="container">
|
| 227 |
<div class="header">
|
| 228 |
<div class="logo-area">
|
| 229 |
-
<div class="logo">
|
| 230 |
<div class="district-info">
|
| 231 |
<h1>{{DISTRICT_NAME}}</h1>
|
| 232 |
<span>Employee Self Service Portal - Skyward</span>
|
|
@@ -295,7 +295,7 @@
|
|
| 295 |
</div>
|
| 296 |
|
| 297 |
<div class="payroll-section">
|
| 298 |
-
<h3>Recent Pay History (
|
| 299 |
<table class="payroll-table">
|
| 300 |
<thead>
|
| 301 |
<tr>
|
|
@@ -308,22 +308,22 @@
|
|
| 308 |
</thead>
|
| 309 |
<tbody>
|
| 310 |
<tr>
|
| 311 |
-
<td>
|
| 312 |
-
<td>
|
| 313 |
<td>$2,847.50</td>
|
| 314 |
<td>$712.38</td>
|
| 315 |
<td class="amount">$2,135.12</td>
|
| 316 |
</tr>
|
| 317 |
<tr>
|
| 318 |
-
<td>
|
| 319 |
-
<td>
|
| 320 |
<td>$2,847.50</td>
|
| 321 |
<td>$712.38</td>
|
| 322 |
<td class="amount">$2,135.12</td>
|
| 323 |
</tr>
|
| 324 |
<tr>
|
| 325 |
-
<td>
|
| 326 |
-
<td>
|
| 327 |
<td>$2,847.50</td>
|
| 328 |
<td>$712.38</td>
|
| 329 |
<td class="amount">$2,135.12</td>
|
|
|
|
| 226 |
<div class="container">
|
| 227 |
<div class="header">
|
| 228 |
<div class="logo-area">
|
| 229 |
+
<div class="logo">{{LOGO_HTML}}</div>
|
| 230 |
<div class="district-info">
|
| 231 |
<h1>{{DISTRICT_NAME}}</h1>
|
| 232 |
<span>Employee Self Service Portal - Skyward</span>
|
|
|
|
| 295 |
</div>
|
| 296 |
|
| 297 |
<div class="payroll-section">
|
| 298 |
+
<h3>Recent Pay History ({{SCHOOL_YEAR}} School Year)</h3>
|
| 299 |
<table class="payroll-table">
|
| 300 |
<thead>
|
| 301 |
<tr>
|
|
|
|
| 308 |
</thead>
|
| 309 |
<tbody>
|
| 310 |
<tr>
|
| 311 |
+
<td>{{PAY_DATE_1}}</td>
|
| 312 |
+
<td>{{PAY_PERIOD_1_START}} - {{PAY_PERIOD_1_END}}</td>
|
| 313 |
<td>$2,847.50</td>
|
| 314 |
<td>$712.38</td>
|
| 315 |
<td class="amount">$2,135.12</td>
|
| 316 |
</tr>
|
| 317 |
<tr>
|
| 318 |
+
<td>{{PAY_DATE_2}}</td>
|
| 319 |
+
<td>{{PAY_PERIOD_2_START}} - {{PAY_PERIOD_2_END}}</td>
|
| 320 |
<td>$2,847.50</td>
|
| 321 |
<td>$712.38</td>
|
| 322 |
<td class="amount">$2,135.12</td>
|
| 323 |
</tr>
|
| 324 |
<tr>
|
| 325 |
+
<td>{{PAY_DATE_3}}</td>
|
| 326 |
+
<td>{{PAY_PERIOD_3_START}} - {{PAY_PERIOD_3_END}}</td>
|
| 327 |
<td>$2,847.50</td>
|
| 328 |
<td>$712.38</td>
|
| 329 |
<td class="amount">$2,135.12</td>
|
k12/template_staff.html
CHANGED
|
@@ -261,7 +261,7 @@
|
|
| 261 |
<div class="container">
|
| 262 |
<div class="header">
|
| 263 |
<div class="logo-section">
|
| 264 |
-
<div class="school-logo">
|
| 265 |
<div class="school-name">
|
| 266 |
<h1>Springfield School District</h1>
|
| 267 |
<p>Excellence in Education Since 1952</p>
|
|
|
|
| 261 |
<div class="container">
|
| 262 |
<div class="header">
|
| 263 |
<div class="logo-section">
|
| 264 |
+
<div class="school-logo">{{LOGO_HTML}}</div>
|
| 265 |
<div class="school-name">
|
| 266 |
<h1>Springfield School District</h1>
|
| 267 |
<p>Excellence in Education Since 1952</p>
|
web/api/preview.py
CHANGED
|
@@ -22,6 +22,7 @@ class PreviewRequest(BaseModel):
|
|
| 22 |
last_name: Optional[str] = "Doe"
|
| 23 |
school_name: Optional[str] = "Springfield High School"
|
| 24 |
email_domain: Optional[str] = None
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
class PreviewResponse(BaseModel):
|
|
@@ -32,7 +33,7 @@ class PreviewResponse(BaseModel):
|
|
| 32 |
error: Optional[str] = None
|
| 33 |
|
| 34 |
|
| 35 |
-
def _generate_previews(first_name: str, last_name: str, school_name: str, email_domain: str = None):
|
| 36 |
"""Sync function to generate previews - runs in thread pool"""
|
| 37 |
# Add parent dir to path
|
| 38 |
if _PARENT_DIR not in sys.path:
|
|
@@ -50,7 +51,8 @@ def _generate_previews(first_name: str, last_name: str, school_name: str, email_
|
|
| 50 |
first_name=first_name,
|
| 51 |
last_name=last_name,
|
| 52 |
school_name=school_name,
|
| 53 |
-
email_domain=email_domain
|
|
|
|
| 54 |
)
|
| 55 |
|
| 56 |
return payroll_png, staff_png
|
|
@@ -71,7 +73,8 @@ async def preview_k12_documents(request: PreviewRequest) -> PreviewResponse:
|
|
| 71 |
request.first_name or "John",
|
| 72 |
request.last_name or "Doe",
|
| 73 |
request.school_name or "Springfield High School",
|
| 74 |
-
request.email_domain
|
|
|
|
| 75 |
)
|
| 76 |
|
| 77 |
# Convert to base64
|
|
|
|
| 22 |
last_name: Optional[str] = "Doe"
|
| 23 |
school_name: Optional[str] = "Springfield High School"
|
| 24 |
email_domain: Optional[str] = None
|
| 25 |
+
logo_url: Optional[str] = None
|
| 26 |
|
| 27 |
|
| 28 |
class PreviewResponse(BaseModel):
|
|
|
|
| 33 |
error: Optional[str] = None
|
| 34 |
|
| 35 |
|
| 36 |
+
def _generate_previews(first_name: str, last_name: str, school_name: str, email_domain: str = None, logo_url: str = None):
|
| 37 |
"""Sync function to generate previews - runs in thread pool"""
|
| 38 |
# Add parent dir to path
|
| 39 |
if _PARENT_DIR not in sys.path:
|
|
|
|
| 51 |
first_name=first_name,
|
| 52 |
last_name=last_name,
|
| 53 |
school_name=school_name,
|
| 54 |
+
email_domain=email_domain,
|
| 55 |
+
logo_url=logo_url
|
| 56 |
)
|
| 57 |
|
| 58 |
return payroll_png, staff_png
|
|
|
|
| 73 |
request.first_name or "John",
|
| 74 |
request.last_name or "Doe",
|
| 75 |
request.school_name or "Springfield High School",
|
| 76 |
+
request.email_domain,
|
| 77 |
+
request.logo_url
|
| 78 |
)
|
| 79 |
|
| 80 |
# Convert to base64
|
web/api/verify.py
CHANGED
|
@@ -65,6 +65,7 @@ class VerifyRequest(BaseModel):
|
|
| 65 |
birth_date: Optional[str] = None
|
| 66 |
school_id: Optional[str] = None
|
| 67 |
email_domain: Optional[str] = None # For K12: custom email domain
|
|
|
|
| 68 |
|
| 69 |
|
| 70 |
class VerifyResponse(BaseModel):
|
|
@@ -122,7 +123,8 @@ async def verify(verify_type: str, request: VerifyRequest) -> VerifyResponse:
|
|
| 122 |
email=request.email,
|
| 123 |
birth_date=request.birth_date,
|
| 124 |
school_id=request.school_id,
|
| 125 |
-
email_domain=request.email_domain
|
|
|
|
| 126 |
)
|
| 127 |
|
| 128 |
return VerifyResponse(
|
|
|
|
| 65 |
birth_date: Optional[str] = None
|
| 66 |
school_id: Optional[str] = None
|
| 67 |
email_domain: Optional[str] = None # For K12: custom email domain
|
| 68 |
+
logo_url: Optional[str] = None # For K12: school logo URL
|
| 69 |
|
| 70 |
|
| 71 |
class VerifyResponse(BaseModel):
|
|
|
|
| 123 |
email=request.email,
|
| 124 |
birth_date=request.birth_date,
|
| 125 |
school_id=request.school_id,
|
| 126 |
+
email_domain=request.email_domain,
|
| 127 |
+
logo_url=request.logo_url
|
| 128 |
)
|
| 129 |
|
| 130 |
return VerifyResponse(
|
web/static/index.html
CHANGED
|
@@ -93,6 +93,13 @@
|
|
| 93 |
<small style="color: var(--text-secondary); font-size: 0.8rem;">
|
| 94 |
Format: cityname.k12.state.us (e.g., boston.k12.ma.us)
|
| 95 |
</small>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
</div>
|
| 97 |
|
| 98 |
<div class="form-group collapsible">
|
|
|
|
| 93 |
<small style="color: var(--text-secondary); font-size: 0.8rem;">
|
| 94 |
Format: cityname.k12.state.us (e.g., boston.k12.ma.us)
|
| 95 |
</small>
|
| 96 |
+
|
| 97 |
+
<label for="k12-logo-url" style="margin-top: 16px;">🏛️ School Logo URL (optional)</label>
|
| 98 |
+
<input type="text" id="k12-logo-url" placeholder="https://example.com/school-logo.png"
|
| 99 |
+
class="input-field">
|
| 100 |
+
<small style="color: var(--text-secondary); font-size: 0.8rem;">
|
| 101 |
+
Direct link to school logo image (PNG/JPG)
|
| 102 |
+
</small>
|
| 103 |
</div>
|
| 104 |
|
| 105 |
<div class="form-group collapsible">
|
web/static/js/app.js
CHANGED
|
@@ -130,6 +130,10 @@ function initVerifyForm() {
|
|
| 130 |
if (emailDomain) {
|
| 131 |
payload.email_domain = emailDomain;
|
| 132 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
}
|
| 134 |
|
| 135 |
const response = await fetch(`${API_BASE}/verify/${currentVerifyType}`, {
|
|
@@ -446,7 +450,8 @@ async function previewK12Documents() {
|
|
| 446 |
first_name: document.getElementById('first-name').value.trim() || 'John',
|
| 447 |
last_name: document.getElementById('last-name').value.trim() || 'Doe',
|
| 448 |
school_name: selectedK12School ? selectedK12School.name : 'Springfield High School',
|
| 449 |
-
email_domain: document.getElementById('k12-email-domain').value.trim() || null
|
|
|
|
| 450 |
};
|
| 451 |
|
| 452 |
const response = await fetch(`${API_BASE}/preview/k12`, {
|
|
|
|
| 130 |
if (emailDomain) {
|
| 131 |
payload.email_domain = emailDomain;
|
| 132 |
}
|
| 133 |
+
const logoUrl = document.getElementById('k12-logo-url').value.trim();
|
| 134 |
+
if (logoUrl) {
|
| 135 |
+
payload.logo_url = logoUrl;
|
| 136 |
+
}
|
| 137 |
}
|
| 138 |
|
| 139 |
const response = await fetch(`${API_BASE}/verify/${currentVerifyType}`, {
|
|
|
|
| 450 |
first_name: document.getElementById('first-name').value.trim() || 'John',
|
| 451 |
last_name: document.getElementById('last-name').value.trim() || 'Doe',
|
| 452 |
school_name: selectedK12School ? selectedK12School.name : 'Springfield High School',
|
| 453 |
+
email_domain: document.getElementById('k12-email-domain').value.trim() || null,
|
| 454 |
+
logo_url: document.getElementById('k12-logo-url').value.trim() || null
|
| 455 |
};
|
| 456 |
|
| 457 |
const response = await fetch(`${API_BASE}/preview/k12`, {
|