Files
breakpilot-lehrer/backend-lehrer/llm_gateway/routes/schools_staff.py
Benjamin Admin 34da9f4cda [split-required] Split 700-870 LOC files across all services
backend-lehrer (11 files):
- llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6)
- messenger_api.py (840 → 5), print_generator.py (824 → 5)
- unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4)
- llm_gateway/routes/edu_search_seeds.py (710 → 4)

klausur-service (12 files):
- ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4)
- legal_corpus_api.py (790 → 4), page_crop.py (758 → 3)
- mail/ai_service.py (747 → 4), github_crawler.py (767 → 3)
- trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4)
- dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4)

website (6 pages):
- audit-checklist (867 → 8), content (806 → 6)
- screen-flow (790 → 4), scraper (789 → 5)
- zeugnisse (776 → 5), modules (745 → 4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:01:18 +02:00

234 lines
7.8 KiB
Python

"""
Schools API - Staff Routes.
CRUD and search endpoints for school staff members.
"""
import logging
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from .schools_db import get_db_pool
from .schools_models import (
SchoolStaffBase,
SchoolStaffResponse,
SchoolStaffListResponse,
)
logger = logging.getLogger(__name__)
router = APIRouter(tags=["schools"])
# =============================================================================
# School Staff Endpoints
# =============================================================================
@router.get("/{school_id}/staff", response_model=SchoolStaffListResponse)
async def get_school_staff(school_id: str):
"""Get staff members for a school."""
pool = await get_db_pool()
async with pool.acquire() as conn:
rows = await conn.fetch("""
SELECT
ss.id, ss.school_id, ss.first_name, ss.last_name, ss.full_name,
ss.title, ss.position, ss.position_type, ss.subjects,
ss.email, ss.phone, ss.profile_url, ss.photo_url,
ss.is_active, ss.created_at,
s.name as school_name
FROM school_staff ss
JOIN schools s ON ss.school_id = s.id
WHERE ss.school_id = $1 AND ss.is_active = TRUE
ORDER BY
CASE ss.position_type
WHEN 'principal' THEN 1
WHEN 'vice_principal' THEN 2
WHEN 'secretary' THEN 3
ELSE 4
END,
ss.last_name
""", school_id)
staff = [
SchoolStaffResponse(
id=str(row["id"]),
school_id=str(row["school_id"]),
school_name=row["school_name"],
first_name=row["first_name"],
last_name=row["last_name"],
full_name=row["full_name"],
title=row["title"],
position=row["position"],
position_type=row["position_type"],
subjects=row["subjects"],
email=row["email"],
phone=row["phone"],
profile_url=row["profile_url"],
photo_url=row["photo_url"],
is_active=row["is_active"],
created_at=row["created_at"],
)
for row in rows
]
return SchoolStaffListResponse(
staff=staff,
total=len(staff),
)
@router.post("/{school_id}/staff", response_model=SchoolStaffResponse)
async def create_school_staff(school_id: str, staff: SchoolStaffBase):
"""Add a staff member to a school."""
pool = await get_db_pool()
async with pool.acquire() as conn:
# Verify school exists
school = await conn.fetchrow("SELECT name FROM schools WHERE id = $1", school_id)
if not school:
raise HTTPException(status_code=404, detail="School not found")
# Create full name
full_name = staff.full_name
if not full_name:
parts = []
if staff.title:
parts.append(staff.title)
if staff.first_name:
parts.append(staff.first_name)
parts.append(staff.last_name)
full_name = " ".join(parts)
row = await conn.fetchrow("""
INSERT INTO school_staff (
school_id, first_name, last_name, full_name, title,
position, position_type, subjects, email, phone
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id, created_at
""",
school_id,
staff.first_name,
staff.last_name,
full_name,
staff.title,
staff.position,
staff.position_type,
staff.subjects,
staff.email,
staff.phone,
)
return SchoolStaffResponse(
id=str(row["id"]),
school_id=school_id,
school_name=school["name"],
first_name=staff.first_name,
last_name=staff.last_name,
full_name=full_name,
title=staff.title,
position=staff.position,
position_type=staff.position_type,
subjects=staff.subjects,
email=staff.email,
phone=staff.phone,
is_active=True,
created_at=row["created_at"],
)
# =============================================================================
# Search Endpoints
# =============================================================================
@router.get("/search/staff", response_model=SchoolStaffListResponse)
async def search_school_staff(
q: Optional[str] = Query(None, description="Search query"),
state: Optional[str] = Query(None, description="Filter by state"),
position_type: Optional[str] = Query(None, description="Filter by position type"),
has_email: Optional[bool] = Query(None, description="Only staff with email"),
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=200),
):
"""Search school staff across all schools."""
pool = await get_db_pool()
async with pool.acquire() as conn:
conditions = ["ss.is_active = TRUE", "s.is_active = TRUE"]
params = []
param_idx = 1
if q:
conditions.append(f"""
(LOWER(ss.full_name) LIKE LOWER(${param_idx})
OR LOWER(ss.last_name) LIKE LOWER(${param_idx})
OR LOWER(s.name) LIKE LOWER(${param_idx}))
""")
params.append(f"%{q}%")
param_idx += 1
if state:
conditions.append(f"s.state = ${param_idx}")
params.append(state.upper())
param_idx += 1
if position_type:
conditions.append(f"ss.position_type = ${param_idx}")
params.append(position_type)
param_idx += 1
if has_email is not None and has_email:
conditions.append("ss.email IS NOT NULL")
where_clause = " AND ".join(conditions)
# Count total
total = await conn.fetchval(f"""
SELECT COUNT(*) FROM school_staff ss
JOIN schools s ON ss.school_id = s.id
WHERE {where_clause}
""", *params)
# Fetch staff
offset = (page - 1) * page_size
rows = await conn.fetch(f"""
SELECT
ss.id, ss.school_id, ss.first_name, ss.last_name, ss.full_name,
ss.title, ss.position, ss.position_type, ss.subjects,
ss.email, ss.phone, ss.profile_url, ss.photo_url,
ss.is_active, ss.created_at,
s.name as school_name
FROM school_staff ss
JOIN schools s ON ss.school_id = s.id
WHERE {where_clause}
ORDER BY ss.last_name, ss.first_name
LIMIT ${param_idx} OFFSET ${param_idx + 1}
""", *params, page_size, offset)
staff = [
SchoolStaffResponse(
id=str(row["id"]),
school_id=str(row["school_id"]),
school_name=row["school_name"],
first_name=row["first_name"],
last_name=row["last_name"],
full_name=row["full_name"],
title=row["title"],
position=row["position"],
position_type=row["position_type"],
subjects=row["subjects"],
email=row["email"],
phone=row["phone"],
profile_url=row["profile_url"],
photo_url=row["photo_url"],
is_active=row["is_active"],
created_at=row["created_at"],
)
for row in rows
]
return SchoolStaffListResponse(
staff=staff,
total=total,
)