latest
This commit is contained in:
parent
2a85845835
commit
3758c7572a
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,6 +5,4 @@ __pycache__
|
||||
|
||||
.env
|
||||
|
||||
|
||||
data/init_data/
|
||||
logs/
|
||||
.archive/*
|
||||
212
README_GAIS_IMPORT.md
Normal file
212
README_GAIS_IMPORT.md
Normal file
@ -0,0 +1,212 @@
|
||||
# GAIS Data Import for ClassroomCopilot
|
||||
|
||||
This document describes the GAIS (Get All Information from Schools) data import functionality for ClassroomCopilot, which allows you to import publicly available school databases into the Neo4j database.
|
||||
|
||||
## Overview
|
||||
|
||||
The GAIS data import system is designed to import publicly available educational data from various sources, starting with Edubase All Data. The system follows Neo4j naming conventions and creates a comprehensive graph structure representing schools and their relationships.
|
||||
|
||||
## Neo4j Naming Conventions
|
||||
|
||||
The import system adheres to the following Neo4j naming conventions:
|
||||
|
||||
- **Node Labels**: PascalCase (e.g., `Establishment`, `LocalAuthority`, `EstablishmentType`)
|
||||
- **Relationships**: `HAS_` prefix followed by the target node label (e.g., `HAS_LOCAL_AUTHORITY`, `HAS_ESTABLISHMENT_TYPE`)
|
||||
- **Properties**: camelCase (e.g., `establishmentName`, `schoolCapacity`, `numberOfPupils`)
|
||||
|
||||
## Data Structure
|
||||
|
||||
### Main Nodes
|
||||
|
||||
1. **Establishment** - The primary school/educational institution node
|
||||
- Properties: URN, name, address, capacity, pupil counts, etc.
|
||||
- Unique identifier: `urn` property
|
||||
|
||||
2. **LocalAuthority** - Local authority governing the establishment
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_LOCAL_AUTHORITY`
|
||||
|
||||
3. **EstablishmentType** - Type of educational establishment
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_ESTABLISHMENT_TYPE`
|
||||
|
||||
4. **EstablishmentTypeGroup** - Group classification of establishment types
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_ESTABLISHMENT_TYPE_GROUP`
|
||||
|
||||
5. **PhaseOfEducation** - Educational phase (Primary, Secondary, etc.)
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_PHASE_OF_EDUCATION`
|
||||
|
||||
6. **GenderType** - Gender classification of the establishment
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_GENDER_TYPE`
|
||||
|
||||
7. **ReligiousCharacter** - Religious character of the establishment
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_RELIGIOUS_CHARACTER`
|
||||
|
||||
8. **Diocese** - Religious diocese (if applicable)
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_DIOCESE`
|
||||
|
||||
9. **GovernmentOfficeRegion** - Government office region
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_GOVERNMENT_OFFICE_REGION`
|
||||
|
||||
10. **DistrictAdministrative** - Administrative district
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_DISTRICT_ADMINISTRATIVE`
|
||||
|
||||
11. **MSOA** - Middle Super Output Area
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_MSOA`
|
||||
|
||||
12. **LSOA** - Lower Super Output Area
|
||||
- Properties: code, name
|
||||
- Relationship: `HAS_LSOA`
|
||||
|
||||
13. **Country** - Country of the establishment
|
||||
- Properties: name
|
||||
- Relationship: `HAS_COUNTRY`
|
||||
|
||||
## Usage
|
||||
|
||||
### Command Line
|
||||
|
||||
You can run the GAIS data import using the startup script:
|
||||
|
||||
```bash
|
||||
# Import GAIS data
|
||||
./start.sh gais-data
|
||||
|
||||
# Or directly with Python
|
||||
python main.py --mode gais-data
|
||||
```
|
||||
|
||||
### Programmatic Usage
|
||||
|
||||
```python
|
||||
from run.initialization.gais_data import import_gais_data
|
||||
|
||||
# Import the data
|
||||
result = import_gais_data()
|
||||
|
||||
if result["success"]:
|
||||
print(f"Successfully imported {result['total_rows']} records")
|
||||
print(f"Processing time: {result['processing_time']:.2f} seconds")
|
||||
print(f"Nodes created: {result['nodes_created']}")
|
||||
print(f"Relationships created: {result['relationships_created']}")
|
||||
else:
|
||||
print(f"Import failed: {result['message']}")
|
||||
```
|
||||
|
||||
## Data Sources
|
||||
|
||||
### Edubase All Data
|
||||
|
||||
The primary data source is the Edubase All Data CSV file, which contains information about all educational establishments in England and Wales.
|
||||
|
||||
**File Location**: `run/initialization/import/edubasealldata20250828.csv`
|
||||
|
||||
**Data Volume**: Approximately 51,900 records
|
||||
|
||||
**Key Fields**:
|
||||
- URN (Unique Reference Number)
|
||||
- Establishment details (name, type, status)
|
||||
- Geographic information (address, coordinates, administrative areas)
|
||||
- Educational characteristics (phase, gender, religious character)
|
||||
- Capacity and pupil numbers
|
||||
- Contact information
|
||||
- Inspection details
|
||||
|
||||
## Data Processing
|
||||
|
||||
### Batch Processing
|
||||
|
||||
The import system processes data in batches to optimize performance and memory usage:
|
||||
|
||||
- **Batch Size**: 1,000 records per batch
|
||||
- **Processing**: Nodes are created first, then relationships
|
||||
- **Error Handling**: Individual record failures don't stop the entire import
|
||||
|
||||
### Data Validation
|
||||
|
||||
The system automatically handles:
|
||||
- Empty/blank values (excluded from node properties)
|
||||
- "Not applicable" values (treated as empty)
|
||||
- Date format conversion (DD-MM-YYYY to ISO format)
|
||||
- Numeric value parsing
|
||||
- Duplicate node prevention
|
||||
|
||||
### Relationship Creation
|
||||
|
||||
Relationships are created using a two-pass approach:
|
||||
1. **First Pass**: Create all nodes and build a mapping of keys to node objects
|
||||
2. **Second Pass**: Create relationships between nodes using the mapping
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Memory Usage**: Data is processed in batches to minimize memory footprint
|
||||
- **Database Connections**: Uses connection pooling for efficient database access
|
||||
- **Duplicate Prevention**: Tracks created nodes to avoid duplicates
|
||||
- **Error Resilience**: Continues processing even if individual records fail
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
The GAIS import system is designed to be extensible for additional data sources:
|
||||
|
||||
1. **Governance Data** - School governance and management information
|
||||
2. **Links Data** - Relationships between schools and other entities
|
||||
3. **Groups Data** - Multi-academy trusts and federations
|
||||
4. **Additional Sources** - Other publicly available educational datasets
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **File Not Found**: Ensure the Edubase CSV file is in the correct location
|
||||
2. **Database Connection**: Verify Neo4j is running and accessible
|
||||
3. **Memory Issues**: Reduce batch size if processing large datasets
|
||||
4. **Permission Errors**: Check file permissions for the CSV data file
|
||||
|
||||
### Logging
|
||||
|
||||
The system provides comprehensive logging:
|
||||
- Import progress updates
|
||||
- Error details for failed records
|
||||
- Performance metrics
|
||||
- Node and relationship creation counts
|
||||
|
||||
### Testing
|
||||
|
||||
Use the test script to verify functionality:
|
||||
|
||||
```bash
|
||||
python test_gais_import.py
|
||||
```
|
||||
|
||||
## Data Quality
|
||||
|
||||
The import system maintains data quality by:
|
||||
- Filtering out invalid or empty values
|
||||
- Converting data types appropriately
|
||||
- Maintaining referential integrity
|
||||
- Providing detailed error reporting
|
||||
|
||||
## Schema Compatibility
|
||||
|
||||
The imported data is compatible with the existing ClassroomCopilot schema and can be integrated with:
|
||||
- Calendar structures
|
||||
- User management systems
|
||||
- Educational content management
|
||||
- Analytics and reporting tools
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions related to the GAIS data import:
|
||||
|
||||
1. Check the logs for detailed error information
|
||||
2. Verify data file format and content
|
||||
3. Ensure database connectivity and permissions
|
||||
4. Review the Neo4j schema constraints and indexes
|
||||
66
archive/auto_processing/README.md
Normal file
66
archive/auto_processing/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Auto-Processing Code Archive
|
||||
|
||||
This directory contains the complex auto-processing system that was previously used for automatic document processing after file upload.
|
||||
|
||||
## Archived Components
|
||||
|
||||
### Core Processing Files
|
||||
- `files_with_auto_processing.py` - Original files.py router with automatic processing
|
||||
- `pipeline_controller.py` - Complex multi-phase pipeline orchestration
|
||||
- `task_processors.py` - Document processing task handlers
|
||||
|
||||
### Advanced Queue Management (Created but not deployed)
|
||||
- `memory_aware_queue.py` - Memory-based intelligent queue management
|
||||
- `enhanced_upload_handler.py` - Advanced upload handler with queuing
|
||||
- `enhanced_upload.py` - API endpoints for advanced upload system
|
||||
|
||||
## What This System Did
|
||||
|
||||
### Automatic Processing Pipeline
|
||||
1. **File Upload** → Immediate processing trigger
|
||||
2. **PDF Conversion** (synchronous, blocking)
|
||||
3. **Phase 1**: Structure discovery (Tika, Page Images, Document Analysis, Split Map)
|
||||
4. **Phase 2**: Docling processing (NO_OCR → OCR → VLM pipelines)
|
||||
5. **Complex Dependencies**: Phase coordination, task sequencing
|
||||
6. **Redis Queue Management**: Service limits, rate limits, dependency tracking
|
||||
|
||||
### Features
|
||||
- Multi-phase processing pipelines
|
||||
- Complex task dependency management
|
||||
- Memory-aware queue limits
|
||||
- Multi-user capacity management
|
||||
- Real-time processing status
|
||||
- WebSocket status updates
|
||||
- Service-specific resource limits
|
||||
- Task recovery on restart
|
||||
|
||||
## Why Archived
|
||||
|
||||
The system was overly complex for the current needs:
|
||||
- **Complexity**: Multi-phase pipelines with complex dependencies
|
||||
- **Blocking Operations**: Synchronous PDF conversion causing timeouts
|
||||
- **Resource Management**: Over-engineered for single-user scenarios
|
||||
- **User Experience**: Users had to wait for processing to complete
|
||||
|
||||
## New Simplified Approach
|
||||
|
||||
The new system focuses on:
|
||||
- **Simple Upload**: Just store files and create database records
|
||||
- **No Auto-Processing**: Users manually trigger processing when needed
|
||||
- **Directory Support**: Upload entire folders with manifest tracking
|
||||
- **Immediate Response**: Users get instant confirmation without waiting
|
||||
|
||||
## If You Need to Restore
|
||||
|
||||
To restore the auto-processing functionality:
|
||||
1. Copy `files_with_auto_processing.py` back to `routers/database/files/files.py`
|
||||
2. Ensure `pipeline_controller.py` and `task_processors.py` are in `modules/`
|
||||
3. Update imports and dependencies
|
||||
4. Re-enable background processing in upload handlers
|
||||
|
||||
## Migration Notes
|
||||
|
||||
The database schema and Redis structure remain compatible. The new simplified system can coexist with the archived processing logic if needed.
|
||||
|
||||
Date Archived: $(date)
|
||||
Reason: Simplification for directory upload implementation
|
||||
142
archive/auto_processing/enhanced_upload.py
Normal file
142
archive/auto_processing/enhanced_upload.py
Normal file
@ -0,0 +1,142 @@
|
||||
"""
|
||||
Enhanced Upload Router with Memory-Aware Queuing
|
||||
===============================================
|
||||
|
||||
Provides intelligent upload endpoints with capacity checking,
|
||||
queue management, and real-time status updates.
|
||||
|
||||
Endpoints:
|
||||
- POST /upload/check-capacity - Pre-check if upload is possible
|
||||
- POST /upload/with-queue - Upload with intelligent queuing
|
||||
- GET /upload/status/{file_id} - Get processing status
|
||||
- GET /upload/queue-status - Get overall queue status
|
||||
- WebSocket /ws/upload-status - Real-time status updates
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
from modules.enhanced_upload_handler import get_upload_handler
|
||||
from modules.memory_aware_queue import get_memory_queue
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
# WebSocket connection manager for real-time updates
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: Dict[str, List[WebSocket]] = {}
|
||||
|
||||
async def connect(self, websocket: WebSocket, file_id: str):
|
||||
await websocket.accept()
|
||||
if file_id not in self.active_connections:
|
||||
self.active_connections[file_id] = []
|
||||
self.active_connections[file_id].append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket, file_id: str):
|
||||
if file_id in self.active_connections:
|
||||
self.active_connections[file_id].remove(websocket)
|
||||
if not self.active_connections[file_id]:
|
||||
del self.active_connections[file_id]
|
||||
|
||||
async def broadcast_to_file(self, file_id: str, message: dict):
|
||||
if file_id in self.active_connections:
|
||||
for connection in self.active_connections[file_id].copy():
|
||||
try:
|
||||
await connection.send_json(message)
|
||||
except:
|
||||
self.active_connections[file_id].remove(connection)
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
@router.post("/upload/check-capacity")
|
||||
async def check_upload_capacity(
|
||||
file_size: int = Form(...),
|
||||
mime_type: str = Form(...),
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
Check if user can upload a file of given size and type.
|
||||
|
||||
Returns capacity information and recommendations.
|
||||
"""
|
||||
try:
|
||||
user_id = payload.get('sub') or payload.get('user_id', 'anonymous')
|
||||
|
||||
# Determine environment
|
||||
environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'
|
||||
upload_handler = get_upload_handler(environment)
|
||||
|
||||
eligible, message, details = upload_handler.check_upload_eligibility(
|
||||
user_id, file_size, mime_type
|
||||
)
|
||||
|
||||
response = {
|
||||
'eligible': eligible,
|
||||
'message': message,
|
||||
'details': details,
|
||||
'timestamp': time.time()
|
||||
}
|
||||
|
||||
status_code = 200 if eligible else 429 # Too Many Requests if not eligible
|
||||
|
||||
logger.info(f"📋 Capacity check for user {user_id}: {eligible} - {message}")
|
||||
|
||||
return JSONResponse(content=response, status_code=status_code)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Capacity check error: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/upload/with-queue")
|
||||
async def upload_with_queue(
|
||||
cabinet_id: str = Form(...),
|
||||
path: str = Form(...),
|
||||
scope: str = Form(...),
|
||||
priority: int = Form(1),
|
||||
file: UploadFile = File(...),
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
Upload file with intelligent queuing and capacity management.
|
||||
|
||||
Returns queue information and processing status.
|
||||
"""
|
||||
try:
|
||||
user_id = payload.get('sub') or payload.get('user_id', 'anonymous')
|
||||
|
||||
# Read file content
|
||||
file_bytes = await file.read()
|
||||
file_size = len(file_bytes)
|
||||
mime_type = file.content_type or 'application/octet-stream'
|
||||
filename = file.filename or path
|
||||
|
||||
logger.info(f"📤 Upload request: {filename} ({file_size} bytes) for user {user_id}")
|
||||
|
||||
# Determine environment
|
||||
environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'
|
||||
upload_handler = get_upload_handler(environment)
|
||||
|
||||
# Check if upload queuing is enabled
|
||||
if os.getenv('UPLOAD_QUEUE_ENABLED', 'true').lower() == 'true':
|
||||
# Use new queue-based upload
|
||||
file_id = str(uuid.uuid4())
|
||||
|
||||
result = await upload_handler.handle_upload_with_queue(
|
||||
file_id=file_id,
|
||||
user_id=user_id,
|
||||
filename=filename,
|
||||
file_bytes=file_bytes,
|
||||
mime_type=mime_type,
|
||||
cabinet_id=cabinet_id,
|
||||
priority=priority
|
||||
)\n \n return result\n \n else:\n # Fall back to immediate processing (legacy mode)\n logger.warning(\"Using legacy immediate processing mode\")\n # TODO: Call original upload_file function\n raise HTTPException(status_code=501, detail=\"Legacy mode not implemented in this endpoint\")\n \n except HTTPException:\n raise\n except Exception as e:\n logger.error(f\"Upload error: {e}\")\n raise HTTPException(status_code=500, detail=str(e))\n\n@router.get(\"/upload/status/{file_id}\")\nasync def get_upload_status(\n file_id: str,\n payload: Dict[str, Any] = Depends(auth)\n):\n \"\"\"\n Get current processing status for an uploaded file.\n \"\"\"\n try:\n # Determine environment\n environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'\n upload_handler = get_upload_handler(environment)\n \n status = upload_handler.get_processing_status(file_id)\n \n if status.get('status') == 'not_found':\n raise HTTPException(status_code=404, detail=\"File not found\")\n \n return status\n \n except HTTPException:\n raise\n except Exception as e:\n logger.error(f\"Status check error for {file_id}: {e}\")\n raise HTTPException(status_code=500, detail=str(e))\n\n@router.get(\"/upload/queue-status\")\nasync def get_queue_status(\n payload: Dict[str, Any] = Depends(auth)\n):\n \"\"\"\n Get overall queue status and system capacity information.\n \"\"\"\n try:\n # Determine environment\n environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'\n memory_queue = get_memory_queue(environment)\n \n system_status = memory_queue.get_system_status()\n \n return {\n 'system_status': system_status,\n 'timestamp': time.time(),\n 'environment': environment\n }\n \n except Exception as e:\n logger.error(f\"Queue status error: {e}\")\n raise HTTPException(status_code=500, detail=str(e))\n\n@router.websocket(\"/ws/upload-status/{file_id}\")\nasync def websocket_upload_status(websocket: WebSocket, file_id: str):\n \"\"\"\n WebSocket endpoint for real-time upload status updates.\n \"\"\"\n await manager.connect(websocket, file_id)\n \n try:\n # Send initial status\n environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'\n upload_handler = get_upload_handler(environment)\n initial_status = upload_handler.get_processing_status(file_id)\n \n await websocket.send_json({\n 'type': 'status_update',\n 'data': initial_status\n })\n \n # Keep connection alive and listen for updates\n while True:\n # In a real implementation, you'd have a background task\n # that pushes updates when file status changes\n await asyncio.sleep(5)\n \n # Check for status updates\n current_status = upload_handler.get_processing_status(file_id)\n await websocket.send_json({\n 'type': 'status_update', \n 'data': current_status\n })\n \n except WebSocketDisconnect:\n manager.disconnect(websocket, file_id)\n except Exception as e:\n logger.error(f\"WebSocket error for {file_id}: {e}\")\n manager.disconnect(websocket, file_id)\n\n# Background task to process upload queue\n@router.on_event(\"startup\")\nasync def start_queue_processor():\n \"\"\"Start background queue processor.\"\"\"\n \n if os.getenv('UPLOAD_QUEUE_ENABLED', 'true').lower() != 'true':\n logger.info(\"📋 Upload queue disabled, skipping queue processor\")\n return\n \n import asyncio\n \n environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'\n upload_handler = get_upload_handler(environment)\n \n # Start background processor\n asyncio.create_task(upload_handler.process_queued_files(\"document_processor\"))\n \n logger.info(\"🚀 Upload queue processor started\")\n\nimport time\nimport asyncio"
|
||||
362
archive/auto_processing/enhanced_upload_handler.py
Normal file
362
archive/auto_processing/enhanced_upload_handler.py
Normal file
@ -0,0 +1,362 @@
|
||||
"""
|
||||
Enhanced Upload Handler with Memory-Aware Queuing
|
||||
=================================================
|
||||
|
||||
Replaces the immediate processing model with intelligent queue management.
|
||||
Provides user feedback about capacity, queue position, and processing status.
|
||||
|
||||
Features:
|
||||
- Pre-upload capacity checking
|
||||
- Memory-aware queuing with user quotas
|
||||
- Real-time status updates via WebSocket/SSE
|
||||
- Graceful degradation under load
|
||||
- Fair queuing across multiple users
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import time
|
||||
import logging
|
||||
import asyncio
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from fastapi import HTTPException, BackgroundTasks
|
||||
from dataclasses import asdict
|
||||
|
||||
from .memory_aware_queue import get_memory_queue, QueuedFile
|
||||
from .redis_manager import get_redis_manager
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
from modules.database.tools.storage.storage_admin import StorageAdmin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EnhancedUploadHandler:
|
||||
"""Enhanced upload handler with memory-aware queuing."""
|
||||
|
||||
def __init__(self, environment: str = "dev"):
|
||||
self.memory_queue = get_memory_queue(environment)
|
||||
self.redis_manager = get_redis_manager(environment)
|
||||
self.redis_client = self.redis_manager.client
|
||||
|
||||
# Processing status tracking
|
||||
self.processing_status_key = "file_processing_status"
|
||||
|
||||
def check_upload_eligibility(self, user_id: str, file_size: int,
|
||||
mime_type: str) -> Tuple[bool, str, Dict[str, Any]]:
|
||||
"""
|
||||
Check if user can upload a file right now.
|
||||
|
||||
Returns:
|
||||
(eligible, message, details)
|
||||
"""
|
||||
|
||||
# Check system capacity
|
||||
can_accept, message, queue_info = self.memory_queue.check_upload_capacity(
|
||||
user_id, file_size, mime_type
|
||||
)
|
||||
|
||||
if not can_accept:
|
||||
return False, message, {
|
||||
'reason': 'capacity_exceeded',
|
||||
'queue_info': queue_info,
|
||||
'recommendations': self._get_recommendations(queue_info)
|
||||
}
|
||||
|
||||
return True, message, {
|
||||
'status': 'ready_for_upload',
|
||||
'queue_info': queue_info,
|
||||
'processing_estimate': self._estimate_processing_time(file_size, mime_type)
|
||||
}
|
||||
|
||||
async def handle_upload_with_queue(self, file_id: str, user_id: str,
|
||||
filename: str, file_bytes: bytes,
|
||||
mime_type: str, cabinet_id: str,
|
||||
priority: int = 1) -> Dict[str, Any]:
|
||||
"""
|
||||
Handle file upload with intelligent queuing.
|
||||
|
||||
Steps:
|
||||
1. Store file immediately (cheap operation)
|
||||
2. Add to processing queue
|
||||
3. Return queue status to user
|
||||
4. Process asynchronously when capacity available
|
||||
"""
|
||||
|
||||
# Store file immediately (this is fast)
|
||||
storage = StorageAdmin()
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
# Create database record
|
||||
bucket = f"{cabinet_id}-files" # or your bucket naming convention
|
||||
storage_path = f"{cabinet_id}/{file_id}/{filename}"
|
||||
|
||||
try:
|
||||
# Store file
|
||||
storage.upload_file(bucket, storage_path, file_bytes, mime_type, upsert=True)
|
||||
|
||||
# Create file record
|
||||
insert_res = client.supabase.table('files').insert({
|
||||
'id': file_id,
|
||||
'name': filename,
|
||||
'cabinet_id': cabinet_id,
|
||||
'bucket': bucket,
|
||||
'path': storage_path,
|
||||
'mime_type': mime_type,
|
||||
'uploaded_by': user_id,
|
||||
'size_bytes': len(file_bytes),
|
||||
'source': 'classroomcopilot-web',
|
||||
'status': 'queued_for_processing' # New status
|
||||
}).execute()
|
||||
|
||||
if not insert_res.data:
|
||||
raise HTTPException(status_code=500, detail="Failed to create file record")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to store file {file_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Storage failed: {str(e)}")
|
||||
|
||||
# Add to processing queue
|
||||
try:
|
||||
queue_result = self.memory_queue.enqueue_file(
|
||||
file_id=file_id,
|
||||
user_id=user_id,
|
||||
filename=filename,
|
||||
size_bytes=len(file_bytes),
|
||||
mime_type=mime_type,
|
||||
cabinet_id=cabinet_id,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
# Update file status in database
|
||||
client.supabase.table('files').update({
|
||||
'status': 'queued_for_processing',
|
||||
'extra': {
|
||||
'queue_position': queue_result['queue_position'],
|
||||
'estimated_wait_seconds': queue_result['estimated_wait_seconds'],
|
||||
'memory_estimate_mb': queue_result['memory_estimate_mb']
|
||||
}
|
||||
}).eq('id', file_id).execute()
|
||||
|
||||
logger.info(f"📋 File {file_id} queued at position {queue_result['queue_position']}")
|
||||
|
||||
return {
|
||||
'status': 'upload_successful',
|
||||
'message': 'File uploaded and queued for processing',
|
||||
'file_id': file_id,
|
||||
'queue_info': queue_result,
|
||||
'next_steps': {
|
||||
'poll_status_endpoint': f'/database/files/{file_id}/processing-status',
|
||||
'websocket_updates': f'/ws/file-processing/{file_id}'
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to queue file {file_id}: {e}")
|
||||
# Clean up stored file
|
||||
try:
|
||||
storage.delete_file(bucket, storage_path)
|
||||
client.supabase.table('files').delete().eq('id', file_id).execute()
|
||||
except:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail=f"Queue failed: {str(e)}")
|
||||
|
||||
async def process_queued_files(self, service_name: str = "document_processor"):
|
||||
"""
|
||||
Background service to process queued files.
|
||||
This runs continuously as a background task.
|
||||
"""
|
||||
|
||||
logger.info(f"🚀 Started queue processor for {service_name}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Get next file from queue
|
||||
queued_file = self.memory_queue.dequeue_next_file(service_name)
|
||||
|
||||
if not queued_file:
|
||||
# No files ready for processing
|
||||
await asyncio.sleep(5)
|
||||
continue
|
||||
|
||||
# Update file status
|
||||
await self._update_processing_status(queued_file.file_id, 'processing')
|
||||
|
||||
# Process the file
|
||||
try:
|
||||
await self._process_file(queued_file, service_name)
|
||||
await self._update_processing_status(queued_file.file_id, 'completed')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process file {queued_file.file_id}: {e}")
|
||||
await self._update_processing_status(queued_file.file_id, 'failed', str(e))
|
||||
|
||||
finally:
|
||||
# Always free memory
|
||||
self.memory_queue.complete_processing(
|
||||
service_name,
|
||||
queued_file.file_id,
|
||||
queued_file.memory_estimate_mb
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Queue processor error: {e}")
|
||||
await asyncio.sleep(10) # Back off on errors
|
||||
|
||||
async def _process_file(self, queued_file: QueuedFile, service_name: str):
|
||||
"""Process a single file from the queue."""
|
||||
|
||||
logger.info(f"🔄 Processing file {queued_file.file_id} in {service_name}")
|
||||
|
||||
# Import here to avoid circular imports
|
||||
from modules.pipeline_controller import get_pipeline_controller
|
||||
|
||||
client = SupabaseServiceRoleClient()
|
||||
controller = get_pipeline_controller()
|
||||
|
||||
# Get file record
|
||||
file_result = client.supabase.table('files').select('*').eq('id', queued_file.file_id).single().execute()
|
||||
file_row = file_result.data
|
||||
|
||||
if not file_row:
|
||||
raise Exception(f"File record not found: {queued_file.file_id}")
|
||||
|
||||
# Update status to processing
|
||||
client.supabase.table('files').update({
|
||||
'status': 'processing'
|
||||
}).eq('id', queued_file.file_id).execute()
|
||||
|
||||
# Convert to PDF if needed (this is where the bottleneck was before)
|
||||
processing_path = await self._handle_pdf_conversion(file_row)
|
||||
|
||||
# Enqueue Phase 1 tasks
|
||||
phase1_tasks = controller.enqueue_phase1_tasks(
|
||||
file_id=queued_file.file_id,
|
||||
file_row={**file_row, 'path': processing_path},
|
||||
processing_path=processing_path,
|
||||
processing_mime=file_row['mime_type']
|
||||
)
|
||||
|
||||
# Update database with task IDs
|
||||
client.supabase.table('files').update({
|
||||
'status': 'phase1_processing',
|
||||
'extra': {
|
||||
**file_row.get('extra', {}),
|
||||
'phase1_tasks': phase1_tasks,
|
||||
'processing_started_at': time.time()
|
||||
}
|
||||
}).eq('id', queued_file.file_id).execute()
|
||||
|
||||
logger.info(f"✅ File {queued_file.file_id} processing initiated")
|
||||
|
||||
async def _handle_pdf_conversion(self, file_row: Dict[str, Any]) -> str:
|
||||
"""Handle PDF conversion asynchronously."""
|
||||
|
||||
if file_row['mime_type'] == 'application/pdf':
|
||||
return file_row['path']
|
||||
|
||||
# TODO: Implement async PDF conversion
|
||||
# For now, return original path and handle conversion in pipeline
|
||||
logger.info(f"PDF conversion queued for file {file_row['id']}")
|
||||
return file_row['path']
|
||||
|
||||
async def _update_processing_status(self, file_id: str, status: str, error: str = None):
|
||||
"""Update file processing status."""
|
||||
|
||||
status_data = {
|
||||
'file_id': file_id,
|
||||
'status': status,
|
||||
'timestamp': time.time(),
|
||||
'error': error
|
||||
}
|
||||
|
||||
# Store in Redis for real-time updates
|
||||
status_key = f"{self.processing_status_key}:{file_id}"
|
||||
self.redis_client.setex(status_key, 86400, json.dumps(status_data)) # 24h expiry
|
||||
|
||||
# Update database
|
||||
client = SupabaseServiceRoleClient()
|
||||
client.supabase.table('files').update({
|
||||
'status': status,
|
||||
'error_message': error
|
||||
}).eq('id', file_id).execute()
|
||||
|
||||
logger.info(f"📊 Status update for {file_id}: {status}")
|
||||
|
||||
def get_processing_status(self, file_id: str) -> Dict[str, Any]:
|
||||
"""Get current processing status for a file."""
|
||||
|
||||
status_key = f"{self.processing_status_key}:{file_id}"
|
||||
status_json = self.redis_client.get(status_key)
|
||||
|
||||
if status_json:
|
||||
return json.loads(status_json)
|
||||
|
||||
# Fallback to database
|
||||
client = SupabaseServiceRoleClient()
|
||||
result = client.supabase.table('files').select('status, error_message, extra').eq('id', file_id).single().execute()
|
||||
|
||||
if result.data:
|
||||
return {
|
||||
'file_id': file_id,
|
||||
'status': result.data['status'],
|
||||
'error': result.data.get('error_message'),
|
||||
'extra': result.data.get('extra', {})
|
||||
}
|
||||
|
||||
return {'file_id': file_id, 'status': 'not_found'}
|
||||
|
||||
def _estimate_processing_time(self, file_size: int, mime_type: str) -> Dict[str, Any]:
|
||||
"""Estimate processing time for a file."""
|
||||
|
||||
# Base time estimates (in seconds)
|
||||
base_times = {
|
||||
'application/pdf': 60, # 1 minute per MB
|
||||
'application/msword': 120, # 2 minutes per MB
|
||||
'image/': 30 # 30 seconds per MB
|
||||
}
|
||||
|
||||
# Find matching mime type
|
||||
time_per_mb = 60 # default
|
||||
for mime_prefix, time_val in base_times.items():
|
||||
if mime_type.startswith(mime_prefix):
|
||||
time_per_mb = time_val
|
||||
break
|
||||
|
||||
file_size_mb = file_size / (1024 * 1024)
|
||||
estimated_seconds = int(file_size_mb * time_per_mb)
|
||||
|
||||
return {
|
||||
'estimated_seconds': estimated_seconds,
|
||||
'estimated_minutes': estimated_seconds / 60,
|
||||
'phases': {
|
||||
'pdf_conversion': estimated_seconds * 0.2,
|
||||
'metadata_extraction': estimated_seconds * 0.3,
|
||||
'docling_processing': estimated_seconds * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
def _get_recommendations(self, queue_info: Dict[str, Any]) -> List[str]:
|
||||
"""Get recommendations for user when upload is rejected."""
|
||||
|
||||
recommendations = []
|
||||
|
||||
if queue_info.get('reason') == 'file_too_large':
|
||||
recommendations.append("Try compressing your file or splitting it into smaller parts")
|
||||
|
||||
if queue_info.get('utilization', 0) > 0.9:
|
||||
recommendations.append("System is currently overloaded. Try uploading during off-peak hours")
|
||||
recommendations.append("Consider uploading smaller files first")
|
||||
|
||||
if queue_info.get('user_current', 0) > 0:
|
||||
recommendations.append("Wait for your current uploads to complete before uploading more")
|
||||
|
||||
if not recommendations:
|
||||
recommendations.append("Please try again in a few minutes")
|
||||
|
||||
return recommendations
|
||||
|
||||
# Convenience functions
|
||||
def get_upload_handler(environment: str = "dev") -> EnhancedUploadHandler:
|
||||
"""Get enhanced upload handler instance."""
|
||||
return EnhancedUploadHandler(environment)
|
||||
|
||||
import json
|
||||
997
archive/auto_processing/files_with_auto_processing.py
Normal file
997
archive/auto_processing/files_with_auto_processing.py
Normal file
@ -0,0 +1,997 @@
|
||||
import os
|
||||
import io
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, BackgroundTasks
|
||||
from typing import Any, Dict, Optional
|
||||
import uuid
|
||||
import re
|
||||
import requests
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from modules.auth.supabase_bearer import SupabaseBearer, verify_supabase_jwt_str
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
from modules.database.supabase.utils.storage import StorageAdmin
|
||||
from modules.document_processor import DocumentProcessor
|
||||
from modules.queue_system import (
|
||||
enqueue_tika_task, enqueue_docling_task, enqueue_split_map_task,
|
||||
enqueue_document_analysis_task, enqueue_page_images_task,
|
||||
TaskPriority, get_queue, QueueConnectionError
|
||||
)
|
||||
from fastapi.responses import Response
|
||||
from fastapi import Body
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
doc_processor = DocumentProcessor()
|
||||
|
||||
DEFAULT_BUCKET = os.getenv('DEFAULT_FILES_BUCKET', 'cc.users')
|
||||
|
||||
# Timeout configurations (in seconds)
|
||||
TIKA_TIMEOUT = int(os.getenv('TIKA_TIMEOUT', '300')) # 5 minutes default
|
||||
DOCLING_FRONTMATTER_TIMEOUT = int(os.getenv('DOCLING_FRONTMATTER_TIMEOUT', '1800')) # 30 minutes default
|
||||
DOCLING_NOOCR_TIMEOUT = int(os.getenv('DOCLING_NOOCR_TIMEOUT', '3600')) # 1 hour default
|
||||
|
||||
# (Legacy feature flags removed - using new three-phase system)
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
def _safe_filename(name: str) -> str:
|
||||
base = os.path.basename(name or 'file')
|
||||
return re.sub(r"[^A-Za-z0-9._-]+", "_", base)
|
||||
|
||||
def _choose_bucket(scope: str, user_id: str, school_id: Optional[str]) -> str:
|
||||
scope = (scope or 'teacher').lower()
|
||||
if scope == 'school' and school_id:
|
||||
return f"cc.institutes.{school_id}.private"
|
||||
# teacher / student fall back to users bucket for now
|
||||
return 'cc.users'
|
||||
|
||||
@router.post("/files/upload")
|
||||
async def upload_file(
|
||||
cabinet_id: str = Form(...),
|
||||
path: str = Form(...),
|
||||
scope: str = Form('teacher'),
|
||||
school_id: Optional[str] = Form(default=None),
|
||||
file: UploadFile = File(...),
|
||||
payload: Dict[str, Any] = Depends(auth),
|
||||
background_tasks: BackgroundTasks = None
|
||||
):
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=401, detail="Invalid token payload")
|
||||
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Determine target bucket by scope
|
||||
bucket = _choose_bucket(scope, user_id, school_id)
|
||||
|
||||
# Stage DB row to get file_id
|
||||
staged_path = f"{cabinet_id}/staging/{uuid.uuid4()}"
|
||||
name = _safe_filename(path or file.filename)
|
||||
file_bytes = await file.read()
|
||||
insert_res = client.supabase.table('files').insert({
|
||||
'cabinet_id': cabinet_id,
|
||||
'name': name,
|
||||
'path': staged_path,
|
||||
'bucket': bucket,
|
||||
'mime_type': file.content_type,
|
||||
'uploaded_by': user_id,
|
||||
'size_bytes': len(file_bytes),
|
||||
'source': 'classroomcopilot-web'
|
||||
}).execute()
|
||||
if not insert_res.data:
|
||||
raise HTTPException(status_code=500, detail="Failed to create file record")
|
||||
file_row = insert_res.data[0]
|
||||
file_id = file_row['id']
|
||||
|
||||
# Final storage path: bucket/cabinet_id/file_id/file
|
||||
final_storage_path = f"{cabinet_id}/{file_id}/{name}"
|
||||
try:
|
||||
storage.upload_file(bucket, final_storage_path, file_bytes, file.content_type or 'application/octet-stream', upsert=True)
|
||||
except Exception as e:
|
||||
# cleanup staged row
|
||||
client.supabase.table('files').delete().eq('id', file_id).execute()
|
||||
raise HTTPException(status_code=500, detail=f"Storage upload failed: {str(e)}")
|
||||
|
||||
# Update DB path to final
|
||||
update_res = client.supabase.table('files').update({
|
||||
'path': final_storage_path
|
||||
}).eq('id', file_id).execute()
|
||||
# Kick off initial artefacts generation in background (Tika + Docling frontmatter + no-OCR)
|
||||
try:
|
||||
if background_tasks is not None:
|
||||
logger.info(f"Scheduling initial artefacts generation for file_id={file_id}")
|
||||
background_tasks.add_task(generate_initial_artefacts, file_id, payload)
|
||||
else:
|
||||
logger.info(f"Running initial artefacts generation synchronously for file_id={file_id}")
|
||||
generate_initial_artefacts(file_id, payload)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to schedule initial artefacts for file_id={file_id}: {e}")
|
||||
|
||||
return update_res.data
|
||||
|
||||
@router.get("/files")
|
||||
def list_files(cabinet_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('files').select('*').eq('cabinet_id', cabinet_id).execute()
|
||||
return res.data
|
||||
|
||||
@router.post("/files/{file_id}/move")
|
||||
def move_file(file_id: str, body: Dict[str, Any], payload: Dict[str, Any] = Depends(auth)):
|
||||
client = SupabaseServiceRoleClient()
|
||||
updates = {}
|
||||
if 'cabinet_id' in body:
|
||||
updates['cabinet_id'] = body['cabinet_id']
|
||||
if 'path' in body:
|
||||
updates['path'] = body['path']
|
||||
if not updates:
|
||||
raise HTTPException(status_code=400, detail="No changes provided")
|
||||
res = client.supabase.table('files').update(updates).eq('id', file_id).execute()
|
||||
return res.data
|
||||
|
||||
@router.delete("/files/{file_id}")
|
||||
def delete_file(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('files').delete().eq('id', file_id).execute()
|
||||
return res.data
|
||||
|
||||
@router.get("/files/{file_id}/artefacts")
|
||||
def list_artefacts(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('document_artefacts').select('*').eq('file_id', file_id).order('created_at', desc=True).execute()
|
||||
return res.data
|
||||
|
||||
@router.get("/files/{file_id}/viewer-artefacts")
|
||||
def list_viewer_artefacts(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""
|
||||
Get artefacts organized for UI viewer display, including frontmatter JSON,
|
||||
processing bundles, and analysis data with proper display metadata.
|
||||
"""
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
# Get all artefacts for the file
|
||||
res = client.supabase.table('document_artefacts').select('*').eq('file_id', file_id).order('created_at', desc=True).execute()
|
||||
all_artefacts = res.data or []
|
||||
|
||||
# Organize artefacts by category for UI display
|
||||
viewer_artefacts = {
|
||||
'document_analysis': [],
|
||||
'processing_bundles': [],
|
||||
'raw_data': []
|
||||
}
|
||||
|
||||
for artefact in all_artefacts:
|
||||
artefact_type = artefact.get('type', '')
|
||||
extra = artefact.get('extra', {})
|
||||
|
||||
# Enhanced artefact info for UI display
|
||||
artefact_info = {
|
||||
'id': artefact['id'],
|
||||
'type': artefact_type,
|
||||
'display_name': extra.get('display_name'),
|
||||
'bundle_label': extra.get('bundle_label'),
|
||||
'section_title': extra.get('section_title'),
|
||||
'page_range': extra.get('page_range'),
|
||||
'page_count': extra.get('page_count'),
|
||||
'pipeline': extra.get('pipeline'),
|
||||
'processing_mode': extra.get('processing_mode'),
|
||||
'ui_order': extra.get('ui_order', 999),
|
||||
'description': extra.get('description'),
|
||||
'viewer_type': extra.get('viewer_type', 'json'),
|
||||
'created_at': artefact['created_at'],
|
||||
'status': artefact.get('status', 'unknown')
|
||||
}
|
||||
|
||||
# Categorize artefacts for UI organization
|
||||
if artefact_type == 'docling_frontmatter_json':
|
||||
artefact_info.update({
|
||||
'display_name': artefact_info['display_name'] or 'Document Frontmatter',
|
||||
'bundle_label': artefact_info['bundle_label'] or 'Frontmatter Analysis',
|
||||
'description': artefact_info['description'] or 'OCR analysis of document structure and metadata',
|
||||
'ui_order': 1,
|
||||
'viewer_type': 'json'
|
||||
})
|
||||
viewer_artefacts['document_analysis'].append(artefact_info)
|
||||
|
||||
elif artefact_type == 'split_map_json':
|
||||
artefact_info.update({
|
||||
'display_name': 'Document Structure Map',
|
||||
'bundle_label': 'Split Map',
|
||||
'description': 'Document section boundaries and organization structure',
|
||||
'ui_order': 2,
|
||||
'viewer_type': 'json'
|
||||
})
|
||||
viewer_artefacts['document_analysis'].append(artefact_info)
|
||||
|
||||
elif artefact_type == 'tika_json':
|
||||
artefact_info.update({
|
||||
'display_name': 'Document Metadata',
|
||||
'bundle_label': 'Tika Analysis',
|
||||
'description': 'Raw document metadata and properties extracted by Apache Tika',
|
||||
'ui_order': 3,
|
||||
'viewer_type': 'json'
|
||||
})
|
||||
viewer_artefacts['raw_data'].append(artefact_info)
|
||||
|
||||
elif artefact_type in ['canonical_docling_json', 'docling_bundle_split', 'docling_bundle', 'docling_standard', 'docling_bundle_split_pages']:
|
||||
# Processing bundles (OCR, No-OCR, VLM) - use original_pipeline for proper differentiation
|
||||
pipeline_name = extra.get('original_pipeline', extra.get('pipeline', 'Unknown'))
|
||||
bundle_label = artefact_info['bundle_label'] or f"{pipeline_name.upper().replace('_', '-')} Bundle"
|
||||
display_name = artefact_info['display_name'] or f"{pipeline_name.upper().replace('_', '-')} Processing Result"
|
||||
|
||||
# Special handling for master manifests
|
||||
if artefact_type == 'docling_bundle_split_pages':
|
||||
display_name = f"{pipeline_name.upper().replace('_', '-')} Document Pages"
|
||||
bundle_label = f"{pipeline_name.upper().replace('_', '-')} Pages Bundle"
|
||||
artefact_info.update({
|
||||
'viewer_type': 'bundle_collection',
|
||||
'is_master_manifest': True,
|
||||
'ui_order': 10 # Show master manifests before individual pages
|
||||
})
|
||||
elif artefact_type == 'docling_standard':
|
||||
# Individual page bundles - lower UI priority
|
||||
artefact_info.update({
|
||||
'viewer_type': 'page_bundle',
|
||||
'is_individual_page': True,
|
||||
'ui_order': extra.get('split_order', 999) + 100 # Show after master manifests
|
||||
})
|
||||
|
||||
artefact_info.update({
|
||||
'display_name': display_name,
|
||||
'bundle_label': bundle_label,
|
||||
'description': f"Docling processing result using {pipeline_name.replace('_', '-')} pipeline",
|
||||
'pipeline_type': pipeline_name # Add explicit pipeline type for UI
|
||||
})
|
||||
viewer_artefacts['processing_bundles'].append(artefact_info)
|
||||
|
||||
elif artefact_type.startswith('docling_') and artefact_type.endswith('_json'):
|
||||
# Other docling JSON results
|
||||
pipeline_name = artefact_type.replace('docling_', '').replace('_json', '').upper()
|
||||
artefact_info.update({
|
||||
'display_name': f"{pipeline_name} Analysis",
|
||||
'bundle_label': f"{pipeline_name} Result",
|
||||
'description': f"Docling {pipeline_name.lower()} processing result",
|
||||
'viewer_type': 'json'
|
||||
})
|
||||
viewer_artefacts['processing_bundles'].append(artefact_info)
|
||||
|
||||
elif artefact_type == 'page_images':
|
||||
artefact_info.update({
|
||||
'display_name': 'Page Images',
|
||||
'bundle_label': 'Visual Pages',
|
||||
'description': 'Generated page images for document visualization',
|
||||
'viewer_type': 'images'
|
||||
})
|
||||
viewer_artefacts['raw_data'].append(artefact_info)
|
||||
|
||||
# Sort each category by ui_order
|
||||
for category in viewer_artefacts.values():
|
||||
category.sort(key=lambda x: (x['ui_order'], x['created_at']))
|
||||
|
||||
return {
|
||||
'file_id': file_id,
|
||||
'categories': viewer_artefacts,
|
||||
'total_artefacts': len(all_artefacts)
|
||||
}
|
||||
|
||||
@router.post("/files/{file_id}/artefacts/initial")
|
||||
def generate_initial_artefacts(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""
|
||||
Generate initial artefacts using the new three-phase pipeline architecture.
|
||||
|
||||
Phase 1: Document Structure Discovery & Analysis
|
||||
- Tika metadata extraction
|
||||
- Page images generation
|
||||
- Document structure analysis (LLM-enhanced)
|
||||
- Split map generation
|
||||
|
||||
Phase 2: Triggered automatically after Phase 1 completion
|
||||
"""
|
||||
logger.info(f"Three-phase pipeline: Starting Phase 1 for file_id={file_id}")
|
||||
|
||||
from modules.pipeline_controller import get_pipeline_controller
|
||||
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
controller = get_pipeline_controller()
|
||||
|
||||
# Load file row
|
||||
fr = client.supabase.table('files').select('*').eq('id', file_id).single().execute()
|
||||
file_row = fr.data
|
||||
if not file_row:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
bucket = file_row['bucket']
|
||||
storage_path = file_row['path']
|
||||
cabinet_id = file_row['cabinet_id']
|
||||
mime = file_row.get('mime_type') or 'application/octet-stream'
|
||||
filename = file_row.get('name', 'file')
|
||||
|
||||
# Step 1: Convert to PDF if not already a PDF (synchronous for now)
|
||||
processing_path = storage_path
|
||||
processing_mime = mime
|
||||
|
||||
if mime != 'application/pdf':
|
||||
logger.info(f"Converting non-PDF file to PDF: file_id={file_id} mime={mime}")
|
||||
try:
|
||||
file_bytes = storage.download_file(bucket, storage_path)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Save original file to temp location
|
||||
temp_input = Path(temp_dir) / filename
|
||||
with open(temp_input, 'wb') as f:
|
||||
f.write(file_bytes)
|
||||
|
||||
# Convert to PDF
|
||||
pdf_bytes = doc_processor.convert_to_pdf(temp_input)
|
||||
|
||||
# Store PDF as artefact
|
||||
pdf_artefact_id = str(uuid.uuid4())
|
||||
pdf_rel_path = f"{cabinet_id}/{file_id}/{pdf_artefact_id}/document.pdf"
|
||||
storage.upload_file(bucket, pdf_rel_path, pdf_bytes, 'application/pdf', upsert=True)
|
||||
|
||||
pdf_ar = client.supabase.table('document_artefacts').insert({
|
||||
'file_id': file_id,
|
||||
'type': 'document_pdf',
|
||||
'rel_path': pdf_rel_path,
|
||||
'extra': {'converted_from': mime, 'original_filename': filename},
|
||||
'status': 'completed'
|
||||
}).execute()
|
||||
|
||||
# Use converted PDF for subsequent processing
|
||||
processing_path = pdf_rel_path
|
||||
processing_mime = 'application/pdf'
|
||||
logger.info(f"PDF conversion: completed file_id={file_id} rel_path={pdf_rel_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"PDF conversion: error processing file_id={file_id}: {e}")
|
||||
# Continue with original file if conversion fails
|
||||
else:
|
||||
logger.info(f"File is already PDF, skipping conversion: file_id={file_id}")
|
||||
|
||||
# Step 2: Enqueue Phase 1 tasks using the new pipeline controller
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
priority = TaskPriority.HIGH if user_id else TaskPriority.NORMAL
|
||||
|
||||
try:
|
||||
# Update file row with processing path
|
||||
updated_file_row = {**file_row, 'path': processing_path, 'mime_type': processing_mime}
|
||||
|
||||
# Enqueue Phase 1 tasks
|
||||
phase1_tasks = controller.enqueue_phase1_tasks(
|
||||
file_id=file_id,
|
||||
file_row=updated_file_row,
|
||||
processing_path=processing_path,
|
||||
processing_mime=processing_mime,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
total_tasks = sum(len(task_list) for task_list in phase1_tasks.values())
|
||||
|
||||
logger.info(f"Three-phase pipeline: Enqueued {total_tasks} Phase 1 tasks for file_id={file_id}")
|
||||
|
||||
|
||||
return {
|
||||
'message': f'Three-phase pipeline: Enqueued {total_tasks} Phase 1 tasks. Phase 2 will trigger automatically after completion.',
|
||||
'phase1_tasks': {k: v for k, v in phase1_tasks.items()},
|
||||
'file_id': file_id,
|
||||
'pipeline_mode': 'three_phase',
|
||||
'bundle_architecture_enabled': True
|
||||
}
|
||||
|
||||
except QueueConnectionError as e:
|
||||
logger.error(f"Queue system unavailable for file_id={file_id}: {e}")
|
||||
logger.error("Redis is not running. Please start the API server with './start.sh dev' to auto-start Redis.")
|
||||
return {
|
||||
'message': 'File uploaded successfully, but processing tasks could not be queued (Redis unavailable)',
|
||||
'file_id': file_id,
|
||||
'queue_status': 'unavailable',
|
||||
'error': 'Queue system unavailable. Please restart the API server with Redis enabled.'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error enqueueing Phase 1 tasks for file_id={file_id}: {e}")
|
||||
return {
|
||||
'message': 'File uploaded successfully, but processing tasks failed to queue',
|
||||
'file_id': file_id,
|
||||
'queue_status': 'failed',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
@router.get("/files/{file_id}/page-images/manifest")
|
||||
def get_page_images_manifest(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Return the page_images manifest JSON for a file via service-role access."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Find file row to get bucket
|
||||
fr = client.supabase.table('files').select('id,bucket,cabinet_id').eq('id', file_id).single().execute()
|
||||
file_row = fr.data or {}
|
||||
if not file_row:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
bucket = file_row['bucket']
|
||||
cabinet_id = file_row['cabinet_id']
|
||||
|
||||
# Find page_images artefact
|
||||
arts = client.supabase.table('document_artefacts') \
|
||||
.select('id,type,rel_path,extra') \
|
||||
.eq('file_id', file_id).eq('type', 'page_images') \
|
||||
.order('created_at', desc=True).limit(1).execute().data or []
|
||||
if not arts:
|
||||
raise HTTPException(status_code=404, detail="page_images artefact not found")
|
||||
art = arts[0]
|
||||
|
||||
# Manifest path
|
||||
manifest_rel_path = (art.get('extra') or {}).get('manifest') or f"{art['rel_path'].rstrip('/')}/page_images.json"
|
||||
|
||||
try:
|
||||
raw = storage.download_file(bucket, manifest_rel_path)
|
||||
import json as _json
|
||||
manifest = _json.loads(raw.decode('utf-8'))
|
||||
# Ensure bucket and base prefix are present for the UI
|
||||
manifest.setdefault('bucket', bucket)
|
||||
manifest.setdefault('base_dir', art['rel_path'])
|
||||
return manifest
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load manifest: {e}")
|
||||
|
||||
def json_dumps(obj: Any) -> str:
|
||||
try:
|
||||
import json
|
||||
return json.dumps(obj, ensure_ascii=False)
|
||||
except Exception:
|
||||
return "{}"
|
||||
|
||||
|
||||
@router.get("/files/{file_id}/artefacts/{artefact_id}/json")
|
||||
def get_artefact_json(file_id: str, artefact_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Return the JSON content of a document artefact using service-role storage access."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
# Look up artefact to get rel_path and validate it belongs to file
|
||||
ar = client.supabase.table('document_artefacts').select('id,file_id,rel_path').eq('id', artefact_id).single().execute()
|
||||
artefact = ar.data
|
||||
if not artefact:
|
||||
raise HTTPException(status_code=404, detail="Artefact not found")
|
||||
if artefact.get('file_id') != file_id:
|
||||
raise HTTPException(status_code=400, detail="Artefact does not belong to file")
|
||||
|
||||
# Look up file to get bucket
|
||||
fr = client.supabase.table('files').select('bucket').eq('id', file_id).single().execute()
|
||||
file_row = fr.data
|
||||
if not file_row:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
bucket = file_row['bucket']
|
||||
rel_path = artefact['rel_path']
|
||||
try:
|
||||
raw = storage.download_file(bucket, rel_path)
|
||||
import json as _json
|
||||
data = _json.loads(raw.decode('utf-8'))
|
||||
return data
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load artefact JSON: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/files/{file_id}/artefacts/{artefact_id}/vlm-section-manifest")
|
||||
def get_vlm_section_manifest(file_id: str, artefact_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Return the VLM section page bundle manifest JSON for a VLM section bundle artefact."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
ar = client.supabase.table('document_artefacts').select('id,file_id,rel_path,type,extra').eq('id', artefact_id).single().execute().data
|
||||
if not ar:
|
||||
raise HTTPException(status_code=404, detail="Artefact not found")
|
||||
if ar.get('file_id') != file_id:
|
||||
raise HTTPException(status_code=400, detail="Artefact does not belong to file")
|
||||
if ar.get('type') != 'vlm_section_page_bundle':
|
||||
raise HTTPException(status_code=400, detail="Artefact is not a VLM section page bundle")
|
||||
|
||||
fr = client.supabase.table('files').select('bucket').eq('id', file_id).single().execute().data
|
||||
if not fr:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
bucket = fr['bucket']
|
||||
|
||||
# The rel_path directly points to the manifest JSON file
|
||||
manifest_rel_path = ar['rel_path']
|
||||
|
||||
try:
|
||||
raw = storage.download_file(bucket, manifest_rel_path)
|
||||
import json as _json
|
||||
data = _json.loads(raw.decode('utf-8'))
|
||||
# ensure bucket present for client use
|
||||
data.setdefault('bucket', bucket)
|
||||
return data
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load VLM section manifest: {e}")
|
||||
|
||||
|
||||
@router.post("/files/{file_id}/artefacts/outline")
|
||||
def enqueue_outline_structure(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""
|
||||
Manually enqueue the fast document outline (headings-only) analysis for an existing file.
|
||||
Returns the queued task id.
|
||||
"""
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
fr = client.supabase.table('files').select('id,bucket,cabinet_id,path,mime_type').eq('id', file_id).single().execute()
|
||||
file_row = fr.data
|
||||
if not file_row:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
bucket = file_row['bucket']
|
||||
storage_path = file_row['path']
|
||||
cabinet_id = file_row['cabinet_id']
|
||||
mime = file_row.get('mime_type') or 'application/pdf'
|
||||
|
||||
# Prefer converted PDF artefact if available
|
||||
arts = client.supabase.table('document_artefacts').select('type,rel_path').eq('file_id', file_id).order('created_at', desc=True).execute().data or []
|
||||
pdf_art = next((a for a in arts if a.get('type') == 'document_pdf'), None)
|
||||
processing_path = pdf_art['rel_path'] if pdf_art else storage_path
|
||||
|
||||
try:
|
||||
task_id = enqueue_docling_task(
|
||||
file_id=file_id,
|
||||
task_type='document_structure_analysis',
|
||||
payload={
|
||||
'bucket': bucket,
|
||||
'file_path': processing_path,
|
||||
'cabinet_id': cabinet_id,
|
||||
'mime_type': mime,
|
||||
'config': {
|
||||
'target_type': 'inbody',
|
||||
'to_formats': 'json',
|
||||
'do_ocr': False,
|
||||
'force_ocr': False
|
||||
}
|
||||
},
|
||||
priority=TaskPriority.NORMAL,
|
||||
timeout=300
|
||||
)
|
||||
return { 'message': 'outline task enqueued', 'task_id': task_id, 'file_id': file_id }
|
||||
except QueueConnectionError as e:
|
||||
raise HTTPException(status_code=503, detail=f"Queue unavailable: {e}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to enqueue outline task: {e}")
|
||||
|
||||
@router.get("/files/proxy")
|
||||
def proxy_storage_file(bucket: str, path: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Proxy a storage file (service-role), useful for private image access in the UI."""
|
||||
storage = StorageAdmin()
|
||||
try:
|
||||
data = storage.download_file(bucket, path)
|
||||
media = 'application/octet-stream'
|
||||
lp = path.lower()
|
||||
if lp.endswith('.png'):
|
||||
media = 'image/png'
|
||||
elif lp.endswith('.webp'):
|
||||
media = 'image/webp'
|
||||
elif lp.endswith('.jpg') or lp.endswith('.jpeg'):
|
||||
media = 'image/jpeg'
|
||||
elif lp.endswith('.json'):
|
||||
media = 'application/json'
|
||||
return Response(content=data, media_type=media)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404, detail=f"File not found or inaccessible: {e}")
|
||||
|
||||
|
||||
# Signed proxy for iframe/img tags without Authorization header
|
||||
@router.get("/files/proxy_signed")
|
||||
def proxy_storage_file_signed(bucket: str, path: str, token: str):
|
||||
"""Proxy using a signed bearer token passed as query param 'token'."""
|
||||
try:
|
||||
payload = verify_supabase_jwt_str(token)
|
||||
if not payload:
|
||||
raise HTTPException(status_code=403, detail="Invalid token")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=403, detail=f"Invalid token: {e}")
|
||||
|
||||
storage = StorageAdmin()
|
||||
try:
|
||||
data = storage.download_file(bucket, path)
|
||||
media = 'application/octet-stream'
|
||||
lp = path.lower()
|
||||
if lp.endswith('.png'):
|
||||
media = 'image/png'
|
||||
elif lp.endswith('.webp'):
|
||||
media = 'image/webp'
|
||||
elif lp.endswith('.jpg') or lp.endswith('.jpeg'):
|
||||
media = 'image/jpeg'
|
||||
elif lp.endswith('.json'):
|
||||
media = 'application/json'
|
||||
return Response(content=data, media_type=media)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404, detail=f"File not found or inaccessible: {e}")
|
||||
|
||||
# -------- Canonical bundle manifest ---------
|
||||
|
||||
@router.get("/files/{file_id}/artefacts/{artefact_id}/manifest")
|
||||
def get_canonical_manifest(file_id: str, artefact_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Return the manifest.json for a canonical_docling_bundle artefact."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
ar = client.supabase.table('document_artefacts').select('id,file_id,rel_path,extra').eq('id', artefact_id).single().execute().data
|
||||
if not ar:
|
||||
raise HTTPException(status_code=404, detail="Artefact not found")
|
||||
if ar.get('file_id') != file_id:
|
||||
raise HTTPException(status_code=400, detail="Artefact does not belong to file")
|
||||
extra = ar.get('extra') or {}
|
||||
manifest_rel_path = extra.get('manifest')
|
||||
if not manifest_rel_path:
|
||||
raise HTTPException(status_code=404, detail="Manifest path not recorded on artefact")
|
||||
|
||||
fr = client.supabase.table('files').select('bucket').eq('id', file_id).single().execute().data
|
||||
if not fr:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
bucket = fr['bucket']
|
||||
|
||||
try:
|
||||
raw = storage.download_file(bucket, manifest_rel_path)
|
||||
import json as _json
|
||||
data = _json.loads(raw.decode('utf-8'))
|
||||
# ensure bucket present for client use
|
||||
data.setdefault('bucket', bucket)
|
||||
return data
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load manifest: {e}")
|
||||
|
||||
# -------- Canonical Docling generation ---------
|
||||
|
||||
def _load_split_map(client: SupabaseServiceRoleClient, storage: StorageAdmin, bucket: str, file_id: str) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
arts = client.supabase.table('document_artefacts') \
|
||||
.select('id,type,rel_path') \
|
||||
.eq('file_id', file_id).eq('type', 'split_map_json') \
|
||||
.order('created_at', desc=True).limit(1).execute().data or []
|
||||
if not arts:
|
||||
return None
|
||||
art = arts[0]
|
||||
raw = storage.download_file(bucket, art['rel_path'])
|
||||
import json as _json
|
||||
return _json.loads(raw.decode('utf-8'))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@router.post("/files/{file_id}/artefacts/canonical-docling")
|
||||
def enqueue_canonical_docling(
|
||||
file_id: str,
|
||||
body: Dict[str, Any] = Body(default={}),
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""Enqueue generation of canonical Docling JSON(s) for a file.
|
||||
|
||||
If a split_map is available and the document is large, this will enqueue
|
||||
multiple Docling jobs using page ranges per section. Otherwise a single
|
||||
job is created for the whole document.
|
||||
"""
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
fr = client.supabase.table('files').select('*').eq('id', file_id).single().execute()
|
||||
file_row = fr.data
|
||||
if not file_row:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
bucket = file_row['bucket']
|
||||
cabinet_id = file_row['cabinet_id']
|
||||
mime = file_row.get('mime_type') or 'application/pdf'
|
||||
storage_path = file_row['path']
|
||||
|
||||
# Prefer converted PDF if available
|
||||
try:
|
||||
arts = client.supabase.table('document_artefacts').select('type,rel_path').eq('file_id', file_id).order('created_at', desc=True).execute().data or []
|
||||
a_pdf = next((a for a in arts if a.get('type') == 'document_pdf'), None)
|
||||
processing_path = a_pdf['rel_path'] if a_pdf else storage_path
|
||||
processing_mime = 'application/pdf' if a_pdf else mime
|
||||
except Exception:
|
||||
processing_path = storage_path
|
||||
processing_mime = mime
|
||||
|
||||
# Determine page_count (prefer Tika; fallback to PDF parser if needed)
|
||||
page_count = None
|
||||
try:
|
||||
arts_pc = client.supabase.table('document_artefacts').select('type,rel_path').eq('file_id', file_id).execute().data or []
|
||||
a_tika_pc = next((a for a in arts_pc if a.get('type') == 'tika_json'), None)
|
||||
if a_tika_pc:
|
||||
raw = storage.download_file(bucket, a_tika_pc['rel_path'])
|
||||
import json as _json
|
||||
tj = _json.loads(raw.decode('utf-8'))
|
||||
for k in ("xmpTPg:NPages", "Page-Count", "pdf:PageCount"):
|
||||
v = tj.get(k) or tj.get(k.lower())
|
||||
if v is not None:
|
||||
page_count = int(v)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"[canonical-docling] Tika page_count read failed: {e}")
|
||||
pass
|
||||
|
||||
# Fallback: compute page_count from PDF if Tika did not provide it
|
||||
if page_count is None:
|
||||
try:
|
||||
pdf_bytes = storage.download_file(bucket, processing_path)
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
doc = fitz.open(stream=pdf_bytes, filetype='pdf')
|
||||
page_count = int(doc.page_count)
|
||||
doc.close()
|
||||
logger.info(f"[canonical-docling] page_count via PyMuPDF: {page_count}")
|
||||
except Exception:
|
||||
try:
|
||||
from PyPDF2 import PdfReader
|
||||
reader = PdfReader(io.BytesIO(pdf_bytes))
|
||||
page_count = int(len(reader.pages))
|
||||
logger.info(f"[canonical-docling] page_count via PyPDF2: {page_count}")
|
||||
except Exception:
|
||||
page_count = None
|
||||
except Exception:
|
||||
page_count = None
|
||||
else:
|
||||
logger.info(f"[canonical-docling] page_count via Tika: {page_count}")
|
||||
|
||||
# Optional custom range from caller
|
||||
custom_range = body.get('custom_range')
|
||||
custom_label = body.get('custom_label') or ''
|
||||
selected_section_id = body.get('selected_section_id')
|
||||
selected_section_title = body.get('selected_section_title')
|
||||
|
||||
# Load split map if requested and document is large enough
|
||||
use_split_requested = bool(body.get('use_split_map', True))
|
||||
split_threshold = int(body.get('threshold') or os.getenv('DOCLING_SPLIT_THRESHOLD', '50'))
|
||||
ranges = [] # list of (start,end)
|
||||
split_map = None
|
||||
sections = [] # list of dicts: {start,end,title}
|
||||
logger.info(f"[canonical-docling] use_split_map={use_split_requested} threshold={split_threshold} page_count={page_count}")
|
||||
# If custom range provided, honor it and bypass split map
|
||||
if isinstance(custom_range, list) and len(custom_range) >= 2:
|
||||
try:
|
||||
cs = int(custom_range[0]); ce = int(custom_range[1])
|
||||
if page_count is not None:
|
||||
cs = max(1, min(cs, page_count))
|
||||
ce = max(cs, min(ce, page_count))
|
||||
ranges = [(cs, ce)]
|
||||
sections = [{'start': cs, 'end': ce, 'title': custom_label or 'Custom range'}]
|
||||
use_split_requested = False
|
||||
logger.info(f"[canonical-docling] using custom_range start={cs} end={ce} label='{custom_label}'")
|
||||
except Exception as _e:
|
||||
logger.warning(f"[canonical-docling] invalid custom_range; falling back. err={_e}")
|
||||
|
||||
if not ranges and use_split_requested and (page_count is None or page_count >= split_threshold):
|
||||
split_map = _load_split_map(client, storage, bucket, file_id)
|
||||
entries = (split_map or {}).get('entries') if split_map else []
|
||||
logger.info(f"[canonical-docling] split_map loaded entries={len(entries) if isinstance(entries, list) else 0}")
|
||||
if split_map and isinstance(entries, list) and len(entries) > 0:
|
||||
# Normalize and sort entries by start_page to enforce correct order
|
||||
norm: list[dict] = []
|
||||
for e in entries:
|
||||
try:
|
||||
s = int(e.get('start_page', 1))
|
||||
t = int(e.get('end_page', s))
|
||||
if t < s:
|
||||
t = s
|
||||
title = e.get('title') or e.get('label') or ''
|
||||
norm.append({'start': s, 'end': t, 'title': title})
|
||||
except Exception:
|
||||
continue
|
||||
norm.sort(key=lambda x: x['start'])
|
||||
# Deduplicate identical or overlapping starts by keeping the earliest occurrence
|
||||
ordered: list[dict] = []
|
||||
last_end = 0
|
||||
for e in norm:
|
||||
s, t = int(e['start']), int(e['end'])
|
||||
if ordered and s <= last_end:
|
||||
# Clamp to prevent inversion and maintain order
|
||||
s = last_end + 1
|
||||
if s > (page_count or s):
|
||||
continue
|
||||
if t < s:
|
||||
t = s
|
||||
last_end = max(last_end, t)
|
||||
ordered.append({'start': s, 'end': t, 'title': e['title']})
|
||||
for e in ordered:
|
||||
ranges.append((e['start'], e['end']))
|
||||
sections.append(e)
|
||||
|
||||
# Fallback: if no split_map ranges... we shouldn't be here
|
||||
if not ranges:
|
||||
# If document is large, split into fixed windows to protect Docling server
|
||||
if page_count is not None and page_count >= split_threshold:
|
||||
chunk = int(os.getenv('DOCLING_FALLBACK_CHUNK_PAGES', '25'))
|
||||
chunk = max(5, min(100, chunk))
|
||||
for i in range(1, (page_count or 1) + 1, chunk):
|
||||
end = min(i + chunk - 1, page_count or i)
|
||||
ranges.append((i, end))
|
||||
sections.append({'start': i, 'end': end, 'title': f"Pages {i}-{end}"})
|
||||
logger.warning(f"[canonical-docling] using fallback chunking ranges={len(ranges)} chunk={chunk}")
|
||||
else:
|
||||
ranges = [(1, page_count or 9223372036854775807)]
|
||||
logger.warning(f"[canonical-docling] using single-range fallback (small doc)")
|
||||
|
||||
# Build config
|
||||
cfg = body.get('config', {})
|
||||
pipeline = cfg.get('pipeline', 'standard')
|
||||
config: Dict[str, Any] = {
|
||||
# target_type is computed in processor based on to_formats unless explicitly provided by user
|
||||
'to_formats': cfg.get('to_formats', 'json'),
|
||||
'do_ocr': bool(cfg.get('do_ocr', True)),
|
||||
'force_ocr': bool(cfg.get('force_ocr', False)),
|
||||
'image_export_mode': cfg.get('image_export_mode', 'embedded'),
|
||||
'ocr_engine': cfg.get('ocr_engine', 'easyocr'),
|
||||
'ocr_lang': cfg.get('ocr_lang', 'en'),
|
||||
'pdf_backend': cfg.get('pdf_backend', 'dlparse_v4'),
|
||||
'table_mode': cfg.get('table_mode', 'fast'),
|
||||
'pipeline': pipeline,
|
||||
'do_picture_classification': bool(cfg.get('do_picture_classification', False)),
|
||||
'do_picture_description': bool(cfg.get('do_picture_description', False)),
|
||||
}
|
||||
# If user explicitly set target_type, pass it through
|
||||
if 'target_type' in cfg:
|
||||
config['target_type'] = cfg['target_type']
|
||||
# Optional VLM settings (only include API fields if provided as JSON by caller)
|
||||
if config['do_picture_description']:
|
||||
pd_api = cfg.get('picture_description_api')
|
||||
if isinstance(pd_api, (dict, list)):
|
||||
config['picture_description_api'] = pd_api
|
||||
elif isinstance(pd_api, str) and pd_api.strip().startswith(('{', '[')):
|
||||
config['picture_description_api'] = pd_api
|
||||
if cfg.get('picture_description_prompt'):
|
||||
config['picture_description_prompt'] = cfg['picture_description_prompt']
|
||||
if pipeline == 'vlm':
|
||||
# Provider presets mapping
|
||||
provider = (cfg.get('vlm_provider') or '').strip().lower()
|
||||
provider_model = (cfg.get('vlm_provider_model') or '').strip()
|
||||
provider_base = (cfg.get('vlm_provider_base_url') or '').strip()
|
||||
if provider in ('ollama', 'openai') and provider_model:
|
||||
if provider == 'ollama':
|
||||
base_url = provider_base or os.getenv('OLLAMA_BASE_URL') or os.getenv('VLM_OLLAMA_BASE_URL')
|
||||
if base_url:
|
||||
endpoint = f"{base_url.rstrip('/')}/v1/chat/completions"
|
||||
# Use OpenAI provider schema against Ollama's OpenAI-compatible endpoint
|
||||
cfg_api = {
|
||||
'provider': 'openai',
|
||||
'url': endpoint,
|
||||
'model': provider_model,
|
||||
'response_format': 'markdown',
|
||||
'request_params': {'model': provider_model}
|
||||
}
|
||||
logger.info(f"[canonical-docling] VLM provider=ollama mapped to openai-compatible url={endpoint} model={provider_model}")
|
||||
config['vlm_pipeline_model_api'] = cfg_api
|
||||
# Also wire picture_description_api if picture description is enabled
|
||||
if config.get('do_picture_description'):
|
||||
config['picture_description_api'] = {
|
||||
'url': endpoint,
|
||||
'headers': {},
|
||||
'params': {'model': provider_model}
|
||||
}
|
||||
elif provider == 'openai':
|
||||
base_url = provider_base or os.getenv('OPENAI_BASE_URL') or 'https://api.openai.com/v1'
|
||||
api_key = os.getenv('OPENAI_API_KEY') or os.getenv('OPENAI_API_KEY_READONLY')
|
||||
# Do not inline key if not present; server may have default
|
||||
model_cfg: Dict[str, Any] = {
|
||||
'provider': 'openai',
|
||||
'url': f"{base_url.rstrip('/')}/chat/completions",
|
||||
'model': provider_model,
|
||||
'response_format': 'markdown',
|
||||
'request_params': {'model': provider_model}
|
||||
}
|
||||
if api_key:
|
||||
model_cfg['api_key'] = api_key
|
||||
# Also pass explicit Authorization header for servers that expect it
|
||||
model_cfg['headers'] = {
|
||||
'Authorization': f"Bearer {api_key}"
|
||||
}
|
||||
logger.info(f"[canonical-docling] VLM provider=openai url={model_cfg['url']} model={provider_model} api_key={'yes' if api_key else 'no'}")
|
||||
config['vlm_pipeline_model_api'] = model_cfg
|
||||
# Also wire picture_description_api if picture description is enabled
|
||||
if config.get('do_picture_description'):
|
||||
headers = {'Authorization': f"Bearer {api_key}"} if api_key else {}
|
||||
config['picture_description_api'] = {
|
||||
'url': f"{base_url.rstrip('/')}/chat/completions",
|
||||
'headers': headers,
|
||||
'params': {'model': provider_model}
|
||||
}
|
||||
else:
|
||||
# Pass through explicit API/local JSON if provided by caller
|
||||
vpa = cfg.get('vlm_pipeline_model_api')
|
||||
if isinstance(vpa, (dict, list)):
|
||||
config['vlm_pipeline_model_api'] = vpa
|
||||
elif isinstance(vpa, str) and vpa.strip().startswith(('{', '[')):
|
||||
config['vlm_pipeline_model_api'] = vpa
|
||||
|
||||
# Enqueue tasks for each range
|
||||
priority = TaskPriority.HIGH
|
||||
task_ids = []
|
||||
multi = len(ranges) > 1
|
||||
logger.info(f"[canonical-docling] final ranges={len(ranges)} multi={multi} pipeline={pipeline} producer={body.get('producer', 'manual')}")
|
||||
|
||||
# Create a group id for split bundles (used for UI grouping)
|
||||
# Use provided group_id if present (for two-pass auto system), otherwise generate new
|
||||
group_id = body.get('group_id') or (str(uuid.uuid4()) if multi else None)
|
||||
if multi and not sections:
|
||||
# Build sections from ranges if titles were not captured
|
||||
for (start, end) in ranges:
|
||||
sections.append({'start': int(start), 'end': int(end), 'title': ''})
|
||||
|
||||
idx = 0
|
||||
for (start, end) in ranges:
|
||||
# Locate title for this range if available
|
||||
title = ''
|
||||
if multi and sections and idx < len(sections):
|
||||
title = sections[idx].get('title') or ''
|
||||
idx += 1
|
||||
|
||||
cfg_range = dict(config)
|
||||
# Ensure 1-based inclusive range is passed through
|
||||
cfg_range['page_range'] = [max(1, int(start)), max(int(start), int(end))]
|
||||
extra = {
|
||||
'is_subdoc': multi,
|
||||
'page_range': [int(start), int(end)],
|
||||
'label': (title or f"subdoc p{int(start)}-{int(end)}") if multi else 'canonical'
|
||||
}
|
||||
# Attach selected section metadata if provided by caller
|
||||
if selected_section_id:
|
||||
extra['selected_section_id'] = selected_section_id
|
||||
if selected_section_title or custom_label:
|
||||
extra['selected_section_title'] = selected_section_title or custom_label
|
||||
# For split processing, force split bundle artefact type and add grouping/order metadata
|
||||
if multi:
|
||||
extra.update({
|
||||
# UI grouping metadata
|
||||
'split_order': idx,
|
||||
'split_heading': title,
|
||||
'split_total': len(ranges)
|
||||
})
|
||||
if group_id:
|
||||
extra['group_id'] = group_id
|
||||
extra['group_pack_type'] = 'docling_standard_auto_split'
|
||||
else:
|
||||
# Single-bundle case: allow caller to override type (defaults to canonical bundle)
|
||||
if 'artefact_type_override' in body and body.get('artefact_type_override'):
|
||||
extra['artefact_type_override'] = body.get('artefact_type_override')
|
||||
|
||||
# Mark producer and selection metadata
|
||||
extra['producer'] = body.get('producer') or ('auto_split' if (multi and body.get('use_split_map')) else 'manual')
|
||||
if selected_section_id:
|
||||
extra['selected_section_id'] = selected_section_id
|
||||
if selected_section_title or custom_label:
|
||||
extra['selected_section_title'] = selected_section_title or custom_label
|
||||
|
||||
# Enhanced logging for canonical operations
|
||||
if multi:
|
||||
logger.info(f"[canonical-docling] enqueue range idx={idx}/{len(ranges)} start={start} end={end} title='{title}' group_id={group_id} producer={extra.get('producer')} pipeline={pipeline}")
|
||||
else:
|
||||
logger.info(f"[canonical-docling] enqueue single range start={start} end={end} producer={extra.get('producer')} pipeline={pipeline}")
|
||||
tid = enqueue_docling_task(
|
||||
file_id=file_id,
|
||||
task_type='canonical_docling_subdoc_json' if multi else 'canonical_docling_json',
|
||||
payload={
|
||||
'bucket': bucket,
|
||||
'file_path': processing_path,
|
||||
'cabinet_id': cabinet_id,
|
||||
'mime_type': processing_mime,
|
||||
'config': cfg_range,
|
||||
'artefact_extra': extra,
|
||||
# Ensure canonical tasks respect upstream dependencies (e.g., Frontmatter)
|
||||
'depends_on': body.get('depends_on', []),
|
||||
# Pass through grouping info if provided by caller (kept for backward-compat)
|
||||
'group_pack_type': body.get('group_pack_type')
|
||||
},
|
||||
priority=priority,
|
||||
timeout=int(body.get('timeout', DOCLING_NOOCR_TIMEOUT))
|
||||
)
|
||||
task_ids.append(tid)
|
||||
|
||||
logger.info(f"[canonical-docling] completed enqueue file_id={file_id} tasks={len(task_ids)} ranges={len(ranges)} pipeline={pipeline} producer={body.get('producer','manual')} group_id={group_id if multi else 'single'}")
|
||||
|
||||
return {
|
||||
'message': f'enqueued {len(task_ids)} canonical docling job(s)',
|
||||
'task_ids': task_ids,
|
||||
'ranges': ranges,
|
||||
'used_split_map': bool(split_map),
|
||||
'group_id': group_id,
|
||||
'pipeline': pipeline,
|
||||
'producer': body.get('producer', 'manual')
|
||||
}
|
||||
|
||||
411
archive/auto_processing/memory_aware_queue.py
Normal file
411
archive/auto_processing/memory_aware_queue.py
Normal file
@ -0,0 +1,411 @@
|
||||
"""
|
||||
Memory-Aware Queue Management System
|
||||
====================================
|
||||
|
||||
Provides intelligent queue management based on memory usage and file sizes
|
||||
rather than simple task count limits. Supports multiple users with fair
|
||||
queuing and capacity management.
|
||||
|
||||
Features:
|
||||
- Memory-based queue limits (not just task count)
|
||||
- Fair queuing across multiple users
|
||||
- Upload capacity checking with user feedback
|
||||
- Graceful degradation under load
|
||||
- Service-specific memory tracking
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
import redis
|
||||
from .redis_manager import get_redis_manager
|
||||
import psutil
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class QueueStatus(Enum):
|
||||
ACCEPTING = "accepting" # Normal operation
|
||||
BUSY = "busy" # High load, warn users
|
||||
OVERLOADED = "overloaded" # Reject new uploads
|
||||
MAINTENANCE = "maintenance" # Manual override
|
||||
|
||||
@dataclass
|
||||
class MemoryConfig:
|
||||
"""Memory configuration for queue management."""
|
||||
max_total_memory_mb: int = 2048 # 2GB total queue memory
|
||||
max_user_memory_mb: int = 512 # 512MB per user
|
||||
max_file_size_mb: int = 100 # 100MB max file size
|
||||
memory_warning_threshold: float = 0.8 # Warn at 80%
|
||||
memory_reject_threshold: float = 0.95 # Reject at 95%
|
||||
|
||||
@dataclass
|
||||
class QueuedFile:
|
||||
"""Represents a file waiting in the queue."""
|
||||
file_id: str
|
||||
user_id: str
|
||||
filename: str
|
||||
size_bytes: int
|
||||
mime_type: str
|
||||
cabinet_id: str
|
||||
priority: int = 1
|
||||
queued_at: float = 0
|
||||
estimated_processing_time: int = 300 # seconds
|
||||
memory_estimate_mb: float = 0
|
||||
|
||||
def __post_init__(self):
|
||||
if self.queued_at == 0:
|
||||
self.queued_at = time.time()
|
||||
|
||||
# Estimate memory usage (rough heuristic)
|
||||
self.memory_estimate_mb = self._estimate_memory_usage()
|
||||
|
||||
def _estimate_memory_usage(self) -> float:
|
||||
"""Estimate memory usage for this file during processing."""
|
||||
base_mb = self.size_bytes / (1024 * 1024)
|
||||
|
||||
# Processing multipliers based on operations
|
||||
if self.mime_type == 'application/pdf':
|
||||
# PDF: original + extracted text + images + thumbnails
|
||||
return base_mb * 3.5
|
||||
elif self.mime_type.startswith('image/'):
|
||||
# Images: original + resized variants + OCR text
|
||||
return base_mb * 2.5
|
||||
else:
|
||||
# Other docs: original + PDF conversion + processing
|
||||
return base_mb * 4.0
|
||||
|
||||
class MemoryAwareQueue:
|
||||
"""Memory-aware queue management system."""
|
||||
|
||||
def __init__(self, environment: str = "dev"):
|
||||
self.redis_manager = get_redis_manager(environment)
|
||||
self.redis_client = self.redis_manager.client
|
||||
self.config = self._load_config()
|
||||
|
||||
# Redis keys
|
||||
self.upload_queue_key = "upload_queue"
|
||||
self.processing_memory_key = "processing_memory"
|
||||
self.user_quota_key = "user_quotas"
|
||||
self.system_status_key = "system_status"
|
||||
|
||||
logger.info(f"🧠 Memory-aware queue initialized (max: {self.config.max_total_memory_mb}MB)")
|
||||
|
||||
def _load_config(self) -> MemoryConfig:
|
||||
"""Load memory configuration from environment."""
|
||||
return MemoryConfig(
|
||||
max_total_memory_mb=int(os.getenv('QUEUE_MAX_MEMORY_MB', '2048')),
|
||||
max_user_memory_mb=int(os.getenv('QUEUE_MAX_USER_MEMORY_MB', '512')),
|
||||
max_file_size_mb=int(os.getenv('MAX_FILE_SIZE_MB', '100')),
|
||||
memory_warning_threshold=float(os.getenv('MEMORY_WARNING_THRESHOLD', '0.8')),
|
||||
memory_reject_threshold=float(os.getenv('MEMORY_REJECT_THRESHOLD', '0.95'))
|
||||
)
|
||||
|
||||
def check_upload_capacity(self, user_id: str, file_size_bytes: int,
|
||||
mime_type: str) -> Tuple[bool, str, Dict[str, Any]]:
|
||||
"""
|
||||
Check if system can accept a new upload.
|
||||
|
||||
Returns:
|
||||
(can_accept, message, queue_info)
|
||||
"""
|
||||
|
||||
# Create temporary QueuedFile to estimate memory
|
||||
temp_file = QueuedFile(
|
||||
file_id="temp",
|
||||
user_id=user_id,
|
||||
filename="temp",
|
||||
size_bytes=file_size_bytes,
|
||||
mime_type=mime_type,
|
||||
cabinet_id="temp"
|
||||
)
|
||||
|
||||
file_memory_mb = temp_file.memory_estimate_mb
|
||||
|
||||
# Check file size limit
|
||||
if file_size_bytes > (self.config.max_file_size_mb * 1024 * 1024):
|
||||
return False, f"File too large (max: {self.config.max_file_size_mb}MB)", {}
|
||||
|
||||
# Get current memory usage
|
||||
current_memory = self._get_current_memory_usage()
|
||||
user_memory = self._get_user_memory_usage(user_id)
|
||||
|
||||
# Check user quota
|
||||
if user_memory + file_memory_mb > self.config.max_user_memory_mb:
|
||||
return False, f"User quota exceeded (limit: {self.config.max_user_memory_mb}MB)", {
|
||||
'user_current': user_memory,
|
||||
'user_limit': self.config.max_user_memory_mb
|
||||
}
|
||||
|
||||
# Check system capacity
|
||||
total_after = current_memory + file_memory_mb
|
||||
max_memory = self.config.max_total_memory_mb
|
||||
|
||||
if total_after > (max_memory * self.config.memory_reject_threshold):
|
||||
queue_info = self._get_queue_info()
|
||||
return False, "System overloaded. Please try again later.", {
|
||||
'current_memory': current_memory,
|
||||
'max_memory': max_memory,
|
||||
'utilization': current_memory / max_memory,
|
||||
'queue_position': queue_info['total_queued'] + 1
|
||||
}
|
||||
|
||||
# Calculate wait time estimate
|
||||
wait_estimate = self._estimate_wait_time(user_id)
|
||||
|
||||
status = "ready"
|
||||
message = "Upload accepted"
|
||||
|
||||
if total_after > (max_memory * self.config.memory_warning_threshold):
|
||||
status = "busy"
|
||||
message = f"System busy. Estimated wait: {wait_estimate // 60}m {wait_estimate % 60}s"
|
||||
|
||||
return True, message, {
|
||||
'status': status,
|
||||
'estimated_wait_seconds': wait_estimate,
|
||||
'memory_usage': {
|
||||
'current': current_memory,
|
||||
'after_upload': total_after,
|
||||
'limit': max_memory,
|
||||
'utilization': total_after / max_memory
|
||||
},
|
||||
'user_quota': {
|
||||
'used': user_memory,
|
||||
'after_upload': user_memory + file_memory_mb,
|
||||
'limit': self.config.max_user_memory_mb
|
||||
}
|
||||
}
|
||||
|
||||
def enqueue_file(self, file_id: str, user_id: str, filename: str,
|
||||
size_bytes: int, mime_type: str, cabinet_id: str,
|
||||
priority: int = 1) -> Dict[str, Any]:
|
||||
"""
|
||||
Add file to upload queue.
|
||||
|
||||
Returns:
|
||||
Queue information including position and estimated wait time
|
||||
"""
|
||||
|
||||
queued_file = QueuedFile(
|
||||
file_id=file_id,
|
||||
user_id=user_id,
|
||||
filename=filename,
|
||||
size_bytes=size_bytes,
|
||||
mime_type=mime_type,
|
||||
cabinet_id=cabinet_id,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
# Serialize and add to Redis queue (priority queue: higher priority = lower score)
|
||||
score = time.time() - (priority * 1000000) # Priority affects score significantly
|
||||
|
||||
self.redis_client.zadd(
|
||||
self.upload_queue_key,
|
||||
{json.dumps(asdict(queued_file)): score}
|
||||
)
|
||||
|
||||
# Update user quota tracking
|
||||
self._update_user_quota(user_id, queued_file.memory_estimate_mb, increment=True)
|
||||
|
||||
# Get queue position and wait estimate
|
||||
position = self._get_queue_position(file_id)
|
||||
wait_estimate = self._estimate_wait_time(user_id)
|
||||
|
||||
logger.info(f"📋 Queued file {file_id} for user {user_id} (pos: {position}, wait: {wait_estimate}s)")
|
||||
|
||||
return {
|
||||
'queued': True,
|
||||
'file_id': file_id,
|
||||
'queue_position': position,
|
||||
'estimated_wait_seconds': wait_estimate,
|
||||
'memory_estimate_mb': queued_file.memory_estimate_mb
|
||||
}
|
||||
|
||||
def dequeue_next_file(self, service_name: str) -> Optional[QueuedFile]:
|
||||
"""
|
||||
Get next file from queue for processing.
|
||||
|
||||
Args:
|
||||
service_name: The service requesting work (for capacity management)
|
||||
"""
|
||||
|
||||
# Check if service has capacity
|
||||
service_memory = self._get_service_memory_usage(service_name)
|
||||
service_limit = self._get_service_memory_limit(service_name)
|
||||
|
||||
if service_memory >= service_limit:
|
||||
logger.debug(f"Service {service_name} at capacity ({service_memory}/{service_limit}MB)")
|
||||
return None
|
||||
|
||||
# Get next item from priority queue (lowest score first)
|
||||
items = self.redis_client.zrange(self.upload_queue_key, 0, 0, withscores=True)
|
||||
|
||||
if not items:
|
||||
return None
|
||||
|
||||
file_data_json, score = items[0]
|
||||
file_data = json.loads(file_data_json)
|
||||
queued_file = QueuedFile(**file_data)
|
||||
|
||||
# Check if this file would exceed service memory limit
|
||||
if service_memory + queued_file.memory_estimate_mb > service_limit:
|
||||
# Skip this file for now, try smaller ones later
|
||||
logger.debug(f"File {queued_file.file_id} too large for {service_name} capacity")
|
||||
return None
|
||||
|
||||
# Remove from queue
|
||||
self.redis_client.zrem(self.upload_queue_key, file_data_json)
|
||||
|
||||
# Update tracking
|
||||
self._update_user_quota(queued_file.user_id, queued_file.memory_estimate_mb, increment=False)
|
||||
self._update_service_memory(service_name, queued_file.memory_estimate_mb, increment=True)
|
||||
|
||||
logger.info(f"🎯 Dequeued file {queued_file.file_id} for {service_name} processing")
|
||||
|
||||
return queued_file
|
||||
|
||||
def complete_processing(self, service_name: str, file_id: str, memory_used_mb: float):
|
||||
"""Mark file processing as complete and free memory."""
|
||||
self._update_service_memory(service_name, memory_used_mb, increment=False)
|
||||
logger.info(f"✅ Completed processing {file_id} in {service_name} (freed {memory_used_mb}MB)")
|
||||
|
||||
def _get_current_memory_usage(self) -> float:
|
||||
"""Get current total memory usage across all services."""
|
||||
services = ['docling', 'tika', 'llm', 'document_analysis']
|
||||
total = 0
|
||||
|
||||
for service in services:
|
||||
service_key = f"{self.processing_memory_key}:{service}"
|
||||
memory = float(self.redis_client.get(service_key) or 0)
|
||||
total += memory
|
||||
|
||||
return total
|
||||
|
||||
def _get_user_memory_usage(self, user_id: str) -> float:
|
||||
"""Get current memory usage for a specific user."""
|
||||
user_key = f"{self.user_quota_key}:{user_id}"
|
||||
return float(self.redis_client.get(user_key) or 0)
|
||||
|
||||
def _get_service_memory_usage(self, service_name: str) -> float:
|
||||
"""Get current memory usage for a service."""
|
||||
service_key = f"{self.processing_memory_key}:{service_name}"
|
||||
return float(self.redis_client.get(service_key) or 0)
|
||||
|
||||
def _get_service_memory_limit(self, service_name: str) -> float:
|
||||
"""Get memory limit for a service."""
|
||||
# Service-specific memory limits as percentage of total
|
||||
limits = {
|
||||
'docling': 0.4, # 40% for Docling (memory-intensive)
|
||||
'tika': 0.2, # 20% for Tika
|
||||
'llm': 0.3, # 30% for LLM processing
|
||||
'document_analysis': 0.1 # 10% for document analysis
|
||||
}
|
||||
|
||||
percentage = limits.get(service_name, 0.1)
|
||||
return self.config.max_total_memory_mb * percentage
|
||||
|
||||
def _update_user_quota(self, user_id: str, memory_mb: float, increment: bool):
|
||||
"""Update user memory quota tracking."""
|
||||
user_key = f"{self.user_quota_key}:{user_id}"
|
||||
|
||||
if increment:
|
||||
self.redis_client.incrbyfloat(user_key, memory_mb)
|
||||
else:
|
||||
current = float(self.redis_client.get(user_key) or 0)
|
||||
new_value = max(0, current - memory_mb)
|
||||
self.redis_client.set(user_key, new_value)
|
||||
|
||||
# Set expiration for cleanup
|
||||
self.redis_client.expire(user_key, 86400) # 24 hours
|
||||
|
||||
def _update_service_memory(self, service_name: str, memory_mb: float, increment: bool):
|
||||
"""Update service memory usage tracking."""
|
||||
service_key = f"{self.processing_memory_key}:{service_name}"
|
||||
|
||||
if increment:
|
||||
self.redis_client.incrbyfloat(service_key, memory_mb)
|
||||
else:
|
||||
current = float(self.redis_client.get(service_key) or 0)
|
||||
new_value = max(0, current - memory_mb)
|
||||
self.redis_client.set(service_key, new_value)
|
||||
|
||||
# Set expiration for cleanup
|
||||
self.redis_client.expire(service_key, 3600) # 1 hour
|
||||
|
||||
def _get_queue_position(self, file_id: str) -> int:
|
||||
"""Get position of file in queue."""
|
||||
items = self.redis_client.zrange(self.upload_queue_key, 0, -1)
|
||||
for i, item in enumerate(items):
|
||||
file_data = json.loads(item)
|
||||
if file_data['file_id'] == file_id:
|
||||
return i + 1
|
||||
return 0
|
||||
|
||||
def _estimate_wait_time(self, user_id: str) -> int:
|
||||
"""Estimate wait time for user's next file."""
|
||||
# Simple estimation based on queue position and average processing time
|
||||
queue_size = self.redis_client.zcard(self.upload_queue_key)
|
||||
avg_processing_time = 300 # 5 minutes average
|
||||
|
||||
return int(queue_size * avg_processing_time * 0.5) # Assume parallel processing
|
||||
|
||||
def _get_queue_info(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive queue information."""
|
||||
total_queued = self.redis_client.zcard(self.upload_queue_key)
|
||||
current_memory = self._get_current_memory_usage()
|
||||
max_memory = self.config.max_total_memory_mb
|
||||
|
||||
return {
|
||||
'total_queued': total_queued,
|
||||
'memory_usage': {
|
||||
'current_mb': current_memory,
|
||||
'max_mb': max_memory,
|
||||
'utilization': current_memory / max_memory if max_memory > 0 else 0
|
||||
},
|
||||
'status': self._determine_system_status(current_memory, max_memory)
|
||||
}
|
||||
|
||||
def _determine_system_status(self, current_memory: float, max_memory: float) -> str:
|
||||
"""Determine current system status based on memory usage."""
|
||||
utilization = current_memory / max_memory if max_memory > 0 else 0
|
||||
|
||||
if utilization >= self.config.memory_reject_threshold:
|
||||
return "overloaded"
|
||||
elif utilization >= self.config.memory_warning_threshold:
|
||||
return "busy"
|
||||
else:
|
||||
return "ready"
|
||||
|
||||
def get_system_status(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive system status for monitoring."""
|
||||
queue_info = self._get_queue_info()
|
||||
|
||||
# Service-specific info
|
||||
services = {}
|
||||
for service_name in ['docling', 'tika', 'llm', 'document_analysis']:
|
||||
services[service_name] = {
|
||||
'memory_used_mb': self._get_service_memory_usage(service_name),
|
||||
'memory_limit_mb': self._get_service_memory_limit(service_name),
|
||||
'utilization': self._get_service_memory_usage(service_name) / self._get_service_memory_limit(service_name)
|
||||
}
|
||||
|
||||
return {
|
||||
'status': queue_info['status'],
|
||||
'queue': queue_info,
|
||||
'services': services,
|
||||
'config': asdict(self.config)
|
||||
}
|
||||
|
||||
# Convenience functions
|
||||
def get_memory_queue(environment: str = "dev") -> MemoryAwareQueue:
|
||||
"""Get memory-aware queue instance."""
|
||||
return MemoryAwareQueue(environment)
|
||||
|
||||
def check_upload_capacity(user_id: str, file_size: int, mime_type: str, environment: str = "dev") -> Tuple[bool, str, Dict]:
|
||||
"""Quick capacity check for upload."""
|
||||
queue = get_memory_queue(environment)
|
||||
return queue.check_upload_capacity(user_id, file_size, mime_type)
|
||||
1316
archive/auto_processing/pipeline_controller.py
Normal file
1316
archive/auto_processing/pipeline_controller.py
Normal file
File diff suppressed because it is too large
Load Diff
2531
archive/auto_processing/task_processors.py
Normal file
2531
archive/auto_processing/task_processors.py
Normal file
File diff suppressed because it is too large
Load Diff
111
clear_stuck_tasks.py
Normal file
111
clear_stuck_tasks.py
Normal file
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Clear stuck tasks from Redis queue after worker restart.
|
||||
|
||||
This script identifies tasks that are marked as "active" but have no
|
||||
corresponding worker processing them, and returns them to the queue.
|
||||
"""
|
||||
|
||||
import redis
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Dict, Any
|
||||
|
||||
def get_redis_client():
|
||||
"""Get Redis client using the same config as the main app."""
|
||||
try:
|
||||
from modules.redis_config import get_redis_url
|
||||
return redis.from_url(get_redis_url())
|
||||
except:
|
||||
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379')
|
||||
return redis.from_url(redis_url)
|
||||
|
||||
def check_stuck_tasks():
|
||||
"""Check for stuck tasks in Redis."""
|
||||
client = get_redis_client()
|
||||
|
||||
print("=== REDIS QUEUE STATUS ===")
|
||||
processing_count = client.llen("document_processing")
|
||||
failed_count = client.llen("document_processing:failed")
|
||||
active_count = client.scard("active_tasks")
|
||||
|
||||
print(f"Processing queue: {processing_count} tasks")
|
||||
print(f"Failed queue: {failed_count} tasks")
|
||||
print(f"Active tasks: {active_count} tasks")
|
||||
|
||||
if active_count > 0:
|
||||
print(f"\n⚠️ WARNING: {active_count} tasks marked as 'active' but workers may have been restarted!")
|
||||
return True
|
||||
elif processing_count > 0:
|
||||
print(f"\n✅ {processing_count} tasks waiting in queue (normal)")
|
||||
return False
|
||||
else:
|
||||
print("\n✅ No stuck tasks found")
|
||||
return False
|
||||
|
||||
def clear_stuck_tasks():
|
||||
"""Move stuck 'active' tasks back to processing queue."""
|
||||
client = get_redis_client()
|
||||
|
||||
# Get all active task IDs
|
||||
active_tasks = client.smembers("active_tasks")
|
||||
if not active_tasks:
|
||||
print("No active tasks to clear")
|
||||
return
|
||||
|
||||
cleared = 0
|
||||
for task_id in active_tasks:
|
||||
task_id_str = task_id.decode('utf-8')
|
||||
try:
|
||||
# Try to get the task data
|
||||
task_data = client.get(f"task:{task_id_str}")
|
||||
if task_data:
|
||||
# Parse task to determine priority queue
|
||||
task_obj = json.loads(task_data.decode('utf-8'))
|
||||
priority = task_obj.get('priority', 'normal')
|
||||
|
||||
queue_key = {
|
||||
'high': 'document_processing:high',
|
||||
'normal': 'document_processing',
|
||||
'low': 'document_processing:low'
|
||||
}.get(priority, 'document_processing')
|
||||
|
||||
# Move task back to processing queue
|
||||
client.lpush(queue_key, task_id_str)
|
||||
client.srem("active_tasks", task_id_str)
|
||||
cleared += 1
|
||||
print(f"✅ Cleared stuck task: {task_id_str} ({task_obj.get('service', 'unknown')}/{task_obj.get('task_type', 'unknown')})")
|
||||
else:
|
||||
# Task data not found, just remove from active set
|
||||
client.srem("active_tasks", task_id_str)
|
||||
cleared += 1
|
||||
print(f"🗑️ Removed orphaned active task: {task_id_str}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to clear task {task_id_str}: {e}")
|
||||
|
||||
print(f"\n✅ Cleared {cleared} stuck tasks")
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
print("🔍 Checking for stuck tasks in Redis...")
|
||||
|
||||
try:
|
||||
has_stuck = check_stuck_tasks()
|
||||
|
||||
if has_stuck:
|
||||
response = input("\n❓ Clear stuck tasks? (y/N): ")
|
||||
if response.lower() in ['y', 'yes']:
|
||||
print("\n🧹 Clearing stuck tasks...")
|
||||
clear_stuck_tasks()
|
||||
print("\n✅ Done! Tasks should now be processed by workers.")
|
||||
else:
|
||||
print("Skipped clearing tasks.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,624 +0,0 @@
|
||||
URN,LA (code),LA (name),EstablishmentNumber,EstablishmentName,TypeOfEstablishment (name),EstablishmentTypeGroup (name),EstablishmentStatus (name),ReasonEstablishmentOpened (name),OpenDate,ReasonEstablishmentClosed (name),CloseDate,PhaseOfEducation (name),StatutoryLowAge,StatutoryHighAge,Boarders (name),NurseryProvision (name),OfficialSixthForm (name),Gender (name),ReligiousCharacter (name),ReligiousEthos (name),Diocese (name),AdmissionsPolicy (name),SchoolCapacity,SpecialClasses (name),CensusDate,NumberOfPupils,NumberOfBoys,NumberOfGirls,PercentageFSM,TrustSchoolFlag (name),Trusts (name),SchoolSponsorFlag (name),SchoolSponsors (name),FederationFlag (name),Federations (name),UKPRN,FEHEIdentifier,FurtherEducationType (name),OfstedLastInsp,LastChangedDate,Street,Locality,Address3,Town,County (name),Postcode,SchoolWebsite,TelephoneNum,HeadTitle (name),HeadFirstName,HeadLastName,HeadPreferredJobTitle,BSOInspectorateName (name),InspectorateReport,DateOfLastInspectionVisit,NextInspectionVisit,TeenMoth (name),TeenMothPlaces,CCF (name),SENPRU (name),EBD (name),PlacesPRU,FTProv (name),EdByOther (name),Section41Approved (name),SEN1 (name),SEN2 (name),SEN3 (name),SEN4 (name),SEN5 (name),SEN6 (name),SEN7 (name),SEN8 (name),SEN9 (name),SEN10 (name),SEN11 (name),SEN12 (name),SEN13 (name),TypeOfResourcedProvision (name),ResourcedProvisionOnRoll,ResourcedProvisionCapacity,SenUnitOnRoll,SenUnitCapacity,GOR (name),DistrictAdministrative (name),AdministrativeWard (name),ParliamentaryConstituency (name),UrbanRural (name),GSSLACode (name),Easting,Northing,MSOA (name),LSOA (name),InspectorateName (name),SENStat,SENNoStat,PropsName,OfstedRating (name),RSCRegion (name),Country (name),UPRN,SiteName,QABName (name),EstablishmentAccredited (name),QABReport,CHNumber,MSOA (code),LSOA (code),FSM,AccreditationExpiryDate
|
||||
118229,886,Kent,1001,Northfleet Nursery School,Local authority nursery school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Nursery,2.0,5,No boarders,Has Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,,Not applicable,19-01-2023,91.0,57.0,34.0,0.0,Not applicable,,Not applicable,,Not under a federation,,,,Not applicable,19-07-2022,16-05-2024,140 London Road,Northfleet,,Gravesend,Kent,DA11 9JS,www.northfleetnursery.co.uk,1474533950.0,Mrs,Neerasha,Singh,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Gravesham,Rosherville,Gravesham,(England/Wales) Urban major conurbation,E10000016,563365.0,174145.0,Gravesham 001,Gravesham 001D,,,,,Outstanding,South-East England and South London,,10012024908.0,,Not applicable,Not applicable,,,E02005055,E01024279,0.0,
|
||||
118254,886,Kent,2088,Crockenhill Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,187.0,83.0,104.0,29.4,Not applicable,,Not applicable,,Not under a federation,,10072997.0,,Not applicable,27-03-2019,04-12-2023,The Green,Crockenhill,,,Kent,BR8 8JG,http://www.crockenhill.kent.sch.uk,3000658300.0,Mrs,Karen,Dodd,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Sevenoaks,Crockenhill and Well Hill,Sevenoaks,(England/Wales) Urban city and town,E10000016,550590.0,167367.0,Sevenoaks 003,Sevenoaks 003A,,,,,Good,South-East England and South London,,,,Not applicable,Not applicable,,,E02005089,E01024421,55.0,
|
||||
118255,886,Kent,2089,The Anthony Roper Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,315.0,No Special Classes,19-01-2023,307.0,151.0,156.0,9.8,Not supported by a trust,,Not applicable,,Not under a federation,,10069650.0,,Not applicable,27-06-2019,16-04-2024,High Street,Eynsford,,Dartford,Kent,DA4 0AA,www.anthony-roper.kent.sch.uk/,1322863680.0,Mr,Adam,Nicholls,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Eynsford,Sevenoaks,(England/Wales) Rural town and fringe,E10000016,554368.0,165949.0,Sevenoaks 005,Sevenoaks 005A,,,,,Good,South-East England and South London,,100060999084.0,,Not applicable,Not applicable,,,E02005091,E01024431,30.0,
|
||||
118257,886,Kent,2094,Cobham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,219.0,No Special Classes,19-01-2023,218.0,109.0,109.0,7.3,Not applicable,,Not applicable,,Not under a federation,,10073366.0,,Not applicable,15-11-2012,07-05-2024,The Street,Cobham,,Gravesend,Kent,DA12 3BN,www.cobham.kent.sch.uk/,1474814373.0,Mrs,Jacqui,Saunders,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,"Istead Rise, Cobham & Luddesdown",Gravesham,(England/Wales) Rural village,E10000016,567152.0,168444.0,Gravesham 013,Gravesham 013D,,,,,Outstanding,South-East England and South London,,100062312570.0,,Not applicable,Not applicable,,,E02005067,E01024302,16.0,
|
||||
118258,886,Kent,2095,Cecil Road Primary and Nursery School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,378.0,No Special Classes,19-01-2023,444.0,228.0,216.0,16.2,Supported by a trust,Northfleet Schools Co-Operative Trust,Not applicable,,Not under a federation,,10069334.0,,Not applicable,05-12-2019,23-04-2024,Cecil Road,Northfleet,,Gravesend,Kent,DA11 7BT,www.cecilroad.co.uk,1474534544.0,Mrs,C,Old,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Gravesham,Coldharbour & Perry Street,Gravesham,(England/Wales) Urban major conurbation,E10000016,563896.0,173124.0,Gravesham 004,Gravesham 004E,,,,,Good,South-East England and South London,,100062310705.0,,Not applicable,Not applicable,,,E02005058,E01024284,64.0,
|
||||
118262,886,Kent,2109,Higham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,208.0,101.0,107.0,11.5,Not applicable,,Not applicable,,Not under a federation,,10073365.0,,Not applicable,25-01-2024,20-05-2024,School Lane,Higham,,Rochester,Kent,ME3 7JL,www.higham.kent.sch.uk,1474822535.0,Mrs,Catherine,Grattan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Higham & Shorne,Gravesham,(England/Wales) Rural village,E10000016,571349.0,172256.0,Gravesham 010,Gravesham 010C,,,,,Good,South-East England and South London,,100062390148.0,,Not applicable,Not applicable,,,E02005064,E01024267,24.0,
|
||||
118264,886,Kent,2116,Lawn Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,262.0,No Special Classes,19-01-2023,230.0,117.0,113.0,52.7,Supported by a trust,Northfleet Schools Co-Operative Trust,Not applicable,,Not under a federation,,10077449.0,,Not applicable,11-01-2023,08-01-2024,High Street,Northfleet,,Gravesend,Kent,DA11 9HB,http://www.lawnprimary.co.uk,1474365303.0,Mrs,A,Wilson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Northfleet & Springhead,Gravesham,(England/Wales) Urban major conurbation,E10000016,562102.0,174368.0,Gravesham 001,Gravesham 001C,,,,,Requires improvement,South-East England and South London,,100062311102.0,,Not applicable,Not applicable,,,E02005055,E01024278,108.0,
|
||||
118266,886,Kent,2120,Bean Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,190.0,88.0,102.0,22.1,Not applicable,,Not applicable,,Not under a federation,,10073364.0,,Not applicable,06-11-2019,21-05-2024,School Lane,Bean,,Dartford,Kent,DA2 8AW,https://www.bean.kent.sch.uk/,1474833225.0,Mr,Graham,Reilly,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dartford,Bean & Village Park,Dartford,(England/Wales) Rural town and fringe,E10000016,558989.0,172058.0,Dartford 012,Dartford 012B,,,,,Good,South-East England and South London,,100060855655.0,,Not applicable,Not applicable,,,E02005039,E01024134,42.0,
|
||||
118271,886,Kent,2128,Capel Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,212.0,No Special Classes,19-01-2023,207.0,113.0,94.0,15.9,Not applicable,,Not applicable,,Not under a federation,,10073362.0,,Not applicable,15-01-2019,24-04-2024,Five Oak Green Road,Five Oak Green,,Tonbridge,Kent,TN12 6RP,www.capelschool.com/,1892833919.0,Mrs,Suzanne,Farr,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Capel,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,563919.0,145118.0,Tunbridge Wells 001,Tunbridge Wells 001A,,,,,Good,South-East England and South London,,10008670848.0,,Not applicable,Not applicable,,,E02005162,E01024798,33.0,
|
||||
118272,886,Kent,2130,Dunton Green Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,187.0,90.0,97.0,31.6,Not applicable,,Not applicable,,Not under a federation,,10069525.0,,Not applicable,18-07-2018,08-04-2024,London Road,Dunton Green,,Sevenoaks,Kent,TN13 2UP,www.dunton-green.kent.sch.uk/,1732462221.0,Mr,Ben,Hulme,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Dunton Green and Riverhead,Sevenoaks,(England/Wales) Urban city and town,E10000016,551173.0,157352.0,Sevenoaks 008,Sevenoaks 008F,,,,,Good,South-East England and South London,United Kingdom,100062547929.0,,Not applicable,Not applicable,,,E02005094,E01035000,59.0,
|
||||
118273,886,Kent,2132,Hadlow Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,187.0,107.0,80.0,38.5,Not applicable,,Not applicable,,Supported by a federation,The Bourne Partnership,10073361.0,,Not applicable,02-10-2019,13-04-2024,Hadlow,,,Tonbridge,Kent,TN11 0EH,www.hadlow.kent.sch.uk,1732850349.0,Miss,Nicole,Chapman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Bourne,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,563334.0,149843.0,Tonbridge and Malling 008,Tonbridge and Malling 008D,,,,,Good,South-East England and South London,,200000967219.0,,Not applicable,Not applicable,,,E02005156,E01024747,72.0,
|
||||
118277,886,Kent,2136,Kemsing Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,184.0,99.0,85.0,14.7,Not applicable,,Not applicable,,Not under a federation,,10069522.0,,Not applicable,20-07-2022,19-01-2024,West End,Kemsing,,Sevenoaks,Kent,TN15 6PU,http://www.kemsing.kent.sch.uk,1732761236.0,Mr,Tom,Hardwick,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Kemsing,Sevenoaks,(England/Wales) Rural town and fringe,E10000016,555461.0,158873.0,Sevenoaks 009,Sevenoaks 009C,,,,,Good,South-East England and South London,,50002002944.0,,Not applicable,Not applicable,,,E02005095,E01024450,27.0,
|
||||
118278,886,Kent,2137,Leigh Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,171.0,No Special Classes,19-01-2023,161.0,77.0,84.0,18.0,Not applicable,,Not applicable,,Not under a federation,,10073359.0,,Not applicable,08-02-2024,20-05-2024,The Green,Leigh,,Tonbridge,Kent,TN11 8QP,www.leighprimaryschool.com,1732832660.0,Mrs,Jenna,Halfhide,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Leigh and Chiddingstone Causeway,Tonbridge and Malling,(England/Wales) Rural village,E10000016,554871.0,146478.0,Sevenoaks 015,Sevenoaks 015B,,,,,Good,South-East England and South London,,50002001763.0,,Not applicable,Not applicable,,,E02005101,E01024451,29.0,
|
||||
118279,886,Kent,2138,Otford Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,332.0,172.0,160.0,13.3,Not applicable,,Not applicable,,Not under a federation,,10069521.0,,Not applicable,18-10-2023,23-04-2024,High Street,Otford,,Sevenoaks,Kent,TN14 5PG,www.otford.kent.sch.uk,1959523145.0,Mrs,Helen,Roberts,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Otford and Shoreham,Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,552632.0,159289.0,Sevenoaks 008,Sevenoaks 008E,,,,,Good,South-East England and South London,,100062548574.0,,Not applicable,Not applicable,,,E02005094,E01024452,44.0,
|
||||
118280,886,Kent,2139,Pembury School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,510.0,No Special Classes,19-01-2023,404.0,199.0,205.0,11.6,Not applicable,,Not applicable,,Not under a federation,,10073358.0,,Not applicable,26-02-2019,04-04-2024,Lower Green Road,Pembury,,Tunbridge Wells,Kent,TN2 4EB,http://www.pembury.kent.sch.uk,1892822259.0,Mrs,Hannah,Walters,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Pembury,Tunbridge Wells,(England/Wales) Rural town and fringe,E10000016,562858.0,141702.0,Tunbridge Wells 004,Tunbridge Wells 004D,,,,,Good,South-East England and South London,,100062554668.0,,Not applicable,Not applicable,,,E02005165,E01024827,47.0,
|
||||
118282,886,Kent,2142,Sandhurst Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,191.0,No Special Classes,19-01-2023,154.0,74.0,80.0,25.3,Not applicable,,Not applicable,,Not under a federation,,10069520.0,,Not applicable,06-02-2019,07-05-2024,Rye Road,Sandhurst,Sandhurst Primary School,Cranbrook,Kent,TN18 5JE,www.sandhurst.kent.sch.uk,1580850288.0,Mrs,Amanda,Norman,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Hawkhurst and Sandhurst,Tunbridge Wells,(England/Wales) Rural village,E10000016,580082.0,128340.0,Tunbridge Wells 014,Tunbridge Wells 014D,,,,,Good,South-East England and South London,,10000069892.0,,Not applicable,Not applicable,,,E02005175,E01024809,39.0,
|
||||
118283,886,Kent,2147,Weald Community Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,175.0,No Special Classes,19-01-2023,126.0,55.0,71.0,11.9,Not applicable,,Not applicable,,Not under a federation,,10069519.0,,Not applicable,04-03-2020,27-03-2024,Long Barn Road,Weald,,Sevenoaks,Kent,TN14 6PY,www.weald.kent.sch.uk,1732463307.0,Mr,David,Pyle,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Seal and Weald,Sevenoaks,(England/Wales) Rural village,E10000016,552697.0,150900.0,Sevenoaks 012,Sevenoaks 012B,,,,,Good,South-East England and South London,,100062548945.0,,Not applicable,Not applicable,,,E02005098,E01024459,15.0,
|
||||
118284,886,Kent,2148,Shoreham Village School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,84.0,40.0,44.0,21.4,Not applicable,,Not applicable,,Not under a federation,,10073357.0,,Not applicable,26-03-2019,25-04-2024,Church Street,Shoreham,,Sevenoaks,Kent,TN14 7SN,www.shorehamvillageschool.net,1959522228.0,Mrs,Gillian,Lovatt-Young,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Otford and Shoreham,Sevenoaks,(England/Wales) Rural village,E10000016,551878.0,161599.0,Sevenoaks 008,Sevenoaks 008E,,,,,Good,South-East England and South London,,50002000337.0,,Not applicable,Not applicable,,,E02005094,E01024452,18.0,
|
||||
118285,886,Kent,2155,Slade Primary School and Attached Unit for Children with Hearing Impairment,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,418.0,223.0,195.0,13.2,Not applicable,,Not applicable,,Not under a federation,,10072996.0,,Not applicable,06-03-2024,22-05-2024,The Slade,,,Tonbridge,Kent,TN9 1HR,www.slade.kent.sch.uk,1732350354.0,Mrs,Karen,Slade,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,,,,,,,,,,,,,Resourced provision,10.0,10.0,,,South East,Tonbridge and Malling,Judd,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558843.0,146765.0,Tonbridge and Malling 012,Tonbridge and Malling 012A,,,,,Good,South-East England and South London,,200000962234.0,,Not applicable,Not applicable,,,E02005160,E01024732,55.0,
|
||||
118286,886,Kent,2156,Sussex Road Community Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,418.0,209.0,209.0,13.9,Not applicable,,Not applicable,,Not under a federation,,10072995.0,,Not applicable,24-11-2021,08-05-2024,Sussex Road,,,Tonbridge,Kent,TN9 2TP,http://www.sussex-road.kent.sch.uk/,1732352367.0,Mrs,Sarah,Miles,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tonbridge and Malling,Judd,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558245.0,145866.0,Tonbridge and Malling 013,Tonbridge and Malling 013A,,,,,Good,South-East England and South London,,200000961981.0,,Not applicable,Not applicable,,,E02005161,E01024757,58.0,
|
||||
118288,886,Kent,2161,Boughton Monchelsea Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,206.0,102.0,104.0,19.4,Not applicable,,Not applicable,,Not under a federation,,10073356.0,,Not applicable,29-09-2023,22-04-2024,Church Hill,Boughton Monchelsea,,Maidstone,Kent,ME17 4HP,http://www.boughton-monchelsea.kent.sch.uk,1622743596.0,Mrs,Mandy,Gibbs,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,Boughton Monchelsea and Chart Sutton,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,576889.0,150785.0,Maidstone 012,Maidstone 012A,,,,,Good,South-East England and South London,,200003674284.0,,Not applicable,Not applicable,,,E02005079,E01024331,40.0,
|
||||
118289,886,Kent,2163,East Farleigh Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,208.0,104.0,104.0,13.0,Not applicable,,Not applicable,,Not under a federation,,10073355.0,,Not applicable,22-06-2022,23-04-2024,Vicarage Lane,East Farleigh,,Maidstone,Kent,ME15 0LY,www.east-farleigh.kent.sch.uk,1622726364.0,Mr,Peter,Goodman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Coxheath and Hunton,Maidstone and The Weald,(England/Wales) Rural village,E10000016,573555.0,152821.0,Maidstone 016,Maidstone 016B,,,,,Good,South-East England and South London,,200003673168.0,,Not applicable,Not applicable,,,E02005083,E01024343,27.0,
|
||||
118290,886,Kent,2164,East Peckham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,175.0,99.0,76.0,14.3,Not applicable,,Not applicable,,Not under a federation,,10073354.0,,Not applicable,04-07-2023,14-05-2024,130 Pound Road,East Peckham,,Tonbridge,Kent,TN12 5LH,http://www.east-peckham.kent.sch.uk/,1622871268.0,Mrs,Kate,Elliott,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"East and West Peckham, Mereworth & Wateringbury",Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,566468.0,149067.0,Tonbridge and Malling 008,Tonbridge and Malling 008B,,,,,Requires improvement,South-East England and South London,,100062628528.0,,Not applicable,Not applicable,,,E02005156,E01024744,25.0,
|
||||
118291,886,Kent,2165,Headcorn Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,392.0,210.0,182.0,20.2,Not applicable,,Not applicable,,Not under a federation,,10073353.0,,Not applicable,05-05-2022,02-05-2024,Kings Road,,,Headcorn,Kent,TN27 9QT,www.headcorn.kent.sch.uk,1622891289.0,Miss,Sarah,Symonds,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Headcorn,Faversham and Mid Kent,(England/Wales) Rural town and fringe,E10000016,583351.0,144445.0,Maidstone 017,Maidstone 017B,,,,,Requires improvement,South-East England and South London,,200003698713.0,,Not applicable,Not applicable,,,E02005084,E01024365,79.0,
|
||||
118292,886,Kent,2166,Hollingbourne Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,96.0,48.0,48.0,24.0,Not applicable,,Not applicable,,Not under a federation,,10073352.0,,Not applicable,01-03-2022,12-03-2024,Eyhorne Street,Hollingbourne,,Maidstone,Kent,ME17 1UA,www.hollingbourne.kent.sch.uk/,1622880270.0,Mrs,Helen,Bradley-Wyatt,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,North Downs,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,584016.0,154881.0,Maidstone 011,Maidstone 011D,,,,,Good,South-East England and South London,,200003719238.0,,Not applicable,Not applicable,,,E02005078,E01024387,23.0,
|
||||
118293,886,Kent,2167,Ightham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,209.0,105.0,104.0,3.3,Not applicable,,Not applicable,,Not under a federation,,10073351.0,,Not applicable,04-03-2020,07-05-2024,Oldbury Lane,Ightham,,Sevenoaks,Kent,TN15 9DD,www.ightham.kent.sch.uk,1732882405.0,Mr,David,Sherhod,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Pilgrims with Ightham,Tonbridge and Malling,(England/Wales) Rural village,E10000016,558908.0,156539.0,Tonbridge and Malling 006,Tonbridge and Malling 006E,,,,,Outstanding,South-East England and South London,,100062551218.0,,Not applicable,Not applicable,,,E02005154,E01024755,7.0,
|
||||
118294,886,Kent,2168,Lenham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,210.0,No Special Classes,19-01-2023,213.0,104.0,109.0,21.1,Not applicable,,Not applicable,,Not under a federation,,10075877.0,,Not applicable,06-12-2023,13-05-2024,Ham Lane,Lenham,,Maidstone,Kent,ME17 2LL,http://www.lenham.kent.sch.uk,1622858260.0,Mrs,Andrea,McCluskey,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Harrietsham and Lenham,Faversham and Mid Kent,(England/Wales) Rural town and fringe,E10000016,589472.0,152164.0,Maidstone 011,Maidstone 011B,,,,,Good,South-East England and South London,,200003719333.0,,Not applicable,Not applicable,,,E02005078,E01024362,45.0,
|
||||
118295,886,Kent,2169,Platts Heath Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,91.0,No Special Classes,19-01-2023,62.0,32.0,30.0,29.0,Not applicable,,Not applicable,,Not under a federation,,10073350.0,,Not applicable,21-04-2022,21-05-2024,Headcorn Road,Platts Heath,,Maidstone,Kent,ME17 2NH,https://plattsheathkentsch.co.uk/,1622850316.0,Mr,Darren,Waters,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Harrietsham and Lenham,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,587729.0,150533.0,Maidstone 011,Maidstone 011B,,,,,Good,South-East England and South London,,200003703748.0,,Not applicable,Not applicable,,,E02005078,E01024362,18.0,
|
||||
118297,886,Kent,2171,Brunswick House Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,421.0,No Special Classes,19-01-2023,421.0,207.0,214.0,25.2,Not applicable,,Not applicable,,Not under a federation,,10069518.0,,Not applicable,19-07-2023,21-05-2024,Leafy Lane,,,Maidstone,Kent,ME16 0QQ,http://www.brunswick-house.kent.sch.uk,1622752102.0,Mrs,Wendy,Skinner,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bridge,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575123.0,156195.0,Maidstone 006,Maidstone 006B,,,,,Good,South-East England and South London,,200003659753.0,,Not applicable,Not applicable,,,E02005073,E01024340,106.0,
|
||||
118301,886,Kent,2175,North Borough Junior School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,353.0,201.0,152.0,28.6,Not applicable,,Not applicable,,Supported by a federation,St Paul's and North Borough Schools Federation,10079035.0,,Not applicable,19-10-2023,25-04-2024,"North Borough Junior School, Peel Street",,,Maidstone,Kent,ME14 2BP,www.north-borough.kent.sch.uk/,1622754708.0,Mrs,Dawn,Wakefield,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,North,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576137.0,156985.0,Maidstone 004,Maidstone 004C,,,,,Good,South-East England and South London,,200003704716.0,,Not applicable,Not applicable,,,E02005071,E01024382,101.0,
|
||||
118302,886,Kent,2176,Park Way Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,315.0,No Special Classes,19-01-2023,316.0,153.0,163.0,26.3,Not applicable,,Not applicable,,Not under a federation,,10073349.0,,Not applicable,13-11-2018,12-09-2023,Park Way,,,Maidstone,Kent,ME15 7AH,http://www.park-way.kent.sch.uk,1622753651.0,Mrs,Karen,Dhanecha,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Shepway North,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,576965.0,154343.0,Maidstone 010,Maidstone 010B,,,,,Good,South-East England and South London,,200003686775.0,,Not applicable,Not applicable,,,E02005077,E01024392,83.0,
|
||||
118307,886,Kent,2185,Mereworth Community Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,218.0,No Special Classes,19-01-2023,203.0,117.0,86.0,16.7,Not applicable,,Not applicable,,Not under a federation,,10069517.0,,Not applicable,07-07-2022,21-05-2024,39 the Street,Mereworth,,Maidstone,Kent,ME18 5ND,www.mereworth.kent.sch.uk,1622812569.0,Miss,Amanda,Lavelle,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"East and West Peckham, Mereworth & Wateringbury",Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,565535.0,153533.0,Tonbridge and Malling 007,Tonbridge and Malling 007A,,,,,Good,South-East England and South London,,100062386957.0,,Not applicable,Not applicable,,,E02005155,E01024746,34.0,
|
||||
118308,886,Kent,2187,Offham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,205.0,100.0,105.0,5.9,Not applicable,,Not applicable,,Not under a federation,,10073347.0,,Not applicable,20-05-2015,23-01-2024,Church Road,Offham,,West Malling,Kent,ME19 5NX,http://www.offham.kent.sch.uk,1732842355.0,Mrs,Emily,John,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"East Malling, West Malling & Offham",Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,565935.0,157744.0,Tonbridge and Malling 014,Tonbridge and Malling 014E,,,,,Outstanding,South-East England and South London,,10002907519.0,,Not applicable,Not applicable,,,E02006833,E01032620,12.0,
|
||||
118309,886,Kent,2188,Plaxtol Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,109.0,No Special Classes,19-01-2023,98.0,49.0,49.0,10.2,Not applicable,,Not applicable,,Not under a federation,,10073346.0,,Not applicable,21-03-2023,21-05-2024,School Lane,Plaxtol,,Sevenoaks,Kent,TN15 0QD,http://www.plaxtol.kent.sch.uk/,1732810200.0,Mrs,Claire,Rowley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Bourne,Tonbridge and Malling,(England/Wales) Rural village,E10000016,560235.0,153454.0,Tonbridge and Malling 006,Tonbridge and Malling 006A,,,,,Good,South-East England and South London,,200000959103.0,,Not applicable,Not applicable,,,E02005154,E01024723,10.0,
|
||||
118310,886,Kent,2189,Ryarsh Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,210.0,107.0,103.0,8.1,Not applicable,,Not applicable,,Not under a federation,,10073345.0,,Not applicable,26-04-2012,03-06-2024,Birling Road,Ryarsh,,West Malling,Kent,ME19 5LS,www.ryarsh.kent.sch.uk/,1732870600.0,Mr,Daniel,Childs,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"Birling, Leybourne & Ryarsh",Tonbridge and Malling,(England/Wales) Rural village,E10000016,567182.0,159943.0,Tonbridge and Malling 014,Tonbridge and Malling 014F,,,,,Outstanding,South-East England and South London,,200000959182.0,,Not applicable,Not applicable,,,E02006833,E01032829,17.0,
|
||||
118311,886,Kent,2190,Shipbourne School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,59.0,No Special Classes,19-01-2023,60.0,28.0,32.0,18.3,Not applicable,,Not applicable,,Supported by a federation,The Bourne Partnership,10073344.0,,Not applicable,28-03-2019,25-04-2024,Stumble Hill,Shipbourne,,Tonbridge,Kent,TN11 9PB,http://www.shipbourne.kent.sch.uk,1732810344.0,Mrs,Terri,Daters,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Bourne,Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,559185.0,151948.0,Tonbridge and Malling 006,Tonbridge and Malling 006A,,,,,Good,South-East England and South London,,200000966854.0,,Not applicable,Not applicable,,,E02005154,E01024723,11.0,
|
||||
118313,886,Kent,2192,Staplehurst School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,525.0,No Special Classes,19-01-2023,406.0,194.0,212.0,19.5,Not applicable,,Not applicable,,Not under a federation,,10073343.0,,Not applicable,26-01-2022,17-04-2024,Gybbon Rise,Staplehurst,,Tonbridge,Kent,TN12 0LZ,www.staplehurstschool.co.uk/,1580891765.0,Miss,Lucy,Davenport,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Staplehurst,Maidstone and The Weald,(England/Wales) Rural town and fringe,E10000016,578439.0,143382.0,Maidstone 019,Maidstone 019D,,,,,Good,South-East England and South London,,200003679143.0,,Not applicable,Not applicable,,,E02005086,E01024409,79.0,
|
||||
118314,886,Kent,2193,Sutton Valence Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,205.0,100.0,105.0,20.5,Not applicable,,Not applicable,,Not under a federation,,10073342.0,,Not applicable,29-03-2023,09-05-2024,North Street,Sutton Valence,,Maidstone,Kent,ME17 3HT,http://www.sutton-valence.kent.sch.uk,1622842188.0,Mrs,Rebecca,Latham-Parsons,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Sutton Valence and Langley,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,581028.0,149379.0,Maidstone 017,Maidstone 017D,,,,,Good,South-East England and South London,,200003720509.0,,Not applicable,Not applicable,,,E02005084,E01024411,42.0,
|
||||
118336,886,Kent,2226,Eastling Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,101.0,39.0,62.0,20.8,Not applicable,,Not applicable,,Not under a federation,,10073341.0,,Not applicable,20-10-2021,04-06-2024,Kettle Hill Road,Eastling,,Faversham,Kent,ME13 0BA,http://www.eastling.kent.sch.uk,1795890252.0,Mrs,Melanie,Dale,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,East Downs,Faversham and Mid Kent,(England/Wales) Rural hamlet and isolated dwellings,E10000016,596524.0,156325.0,Swale 016,Swale 016B,,,,,Good,South-East England and South London,,200002532821.0,,Not applicable,Not applicable,,,E02005130,E01024566,21.0,
|
||||
118337,886,Kent,2227,Ethelbert Road Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,209.0,108.0,101.0,5.7,Not applicable,,Not applicable,,Not under a federation,,10073340.0,,Not applicable,01-10-2014,29-05-2024,Ethelbert Road,,,Faversham,Kent,ME13 8SQ,www.ethelbert-road.kent.sch.uk,1795533124.0,Mrs,Michele,Kirkbride,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Watling,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,601072.0,160785.0,Swale 014,Swale 014E,,,,,Outstanding,South-East England and South London,,10035063205.0,,Not applicable,Not applicable,,,E02005128,E01024626,12.0,
|
||||
118338,886,Kent,2228,Davington Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,417.0,208.0,209.0,21.8,Not applicable,,Not applicable,,Not under a federation,,10069515.0,,Not applicable,22-06-2023,18-03-2024,Priory Row,Davington,,Faversham,Kent,ME13 7EQ,www.davington.kent.sch.uk/,1795532401.0,Mr,Chilton,Saint,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Priory,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,601089.0,161986.0,Swale 015,Swale 015D,,,,,Good,South-East England and South London,,100062379818.0,,Not applicable,Not applicable,,,E02005129,E01024563,91.0,
|
||||
118341,886,Kent,2231,Lower Halstow Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,188.0,94.0,94.0,13.3,Not applicable,,Not applicable,,Supported by a federation,The Federation of Lower Halstow Primary and Newington CofE Primary School,10073339.0,,Not applicable,14-03-2019,04-06-2024,School Lane,Lower Halstow,,Sittingbourne,Kent,ME9 7ES,www.lower-halstow.kent.sch.uk,1795842344.0,Mrs,Tara,Deevoy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,"Bobbing, Iwade and Lower Halstow",Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,585951.0,166826.0,Swale 008,Swale 008E,,,,,Good,South-East England and South London,,200002532824.0,,Not applicable,Not applicable,,,E02005122,E01024575,25.0,
|
||||
118346,886,Kent,2239,Rodmersham School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,70.0,No Special Classes,19-01-2023,117.0,59.0,58.0,3.4,Not applicable,,Not applicable,,Not under a federation,,10073338.0,,Not applicable,22-09-2011,24-04-2024,Rodmersham Green,,,Sittingbourne,Kent,ME9 0PS,www.rodmersham.kent.sch.uk/,1795423776.0,Mrs,Nicola,McMullon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,West Downs,Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,591601.0,161273.0,Swale 013,Swale 013C,,,,,Outstanding,South-East England and South London,,100062397234.0,,Not applicable,Not applicable,,,E02005127,E01024628,4.0,
|
||||
118348,886,Kent,2245,Rose Street Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,438.0,225.0,213.0,59.6,Not applicable,,Not applicable,,Supported by a federation,Sheerness West Federation,10076228.0,,Not applicable,30-11-2022,14-05-2024,Rose Street,,,Sheerness,Kent,ME12 1AW,www.swfed.co.uk,1795663012.0,Mrs,Samantha,Mackay,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheerness,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591895.0,174664.0,Swale 002,Swale 002A,,,,,Requires improvement,South-East England and South London,,100062377402.0,,Not applicable,Not applicable,,,E02005116,E01024613,249.0,
|
||||
118354,886,Kent,2254,Canterbury Road Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,208.0,103.0,105.0,32.7,Not applicable,,Not applicable,,Not under a federation,,10073337.0,,Not applicable,06-03-2024,22-05-2024,School Road,,,Sittingbourne,Kent,ME10 4SE,http://www.canterbury-road.kent.sch.uk,1795423818.0,Mr,Timothy,Pye,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Roman,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591591.0,163352.0,Swale 010,Swale 010D,,,,,Good,South-East England and South London,,200002531732.0,,Not applicable,Not applicable,,,E02005124,E01024599,68.0,
|
||||
118356,886,Kent,2258,Blean Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,469.0,Not applicable,19-01-2023,431.0,215.0,216.0,6.7,Not applicable,,Not applicable,,Not under a federation,,10073336.0,,Not applicable,09-03-2022,07-05-2024,Whitstable Road,Blean,,Canterbury,Kent,CT2 9ED,www.bleanprimary.org.uk/,1227471254.0,Mr,Ian,Rowden,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Blean Forest,Canterbury,(England/Wales) Urban city and town,E10000016,612837.0,159932.0,Canterbury 012,Canterbury 012H,,,,,Outstanding,South-East England and South London,,200000683406.0,,Not applicable,Not applicable,,,E02005021,E01035309,29.0,
|
||||
118359,886,Kent,2263,Herne Bay Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,2.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,304.0,154.0,150.0,35.7,Not applicable,,Not applicable,,Not under a federation,,10074072.0,,Not applicable,04-12-2019,17-01-2024,Stanley Road,,,Herne Bay,Kent,CT6 5SH,www.herne-bay.kent.sch.uk/,1227372245.0,Ms,Nicky,Brown,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Heron,North Thanet,(England/Wales) Urban city and town,E10000016,617893.0,167868.0,Canterbury 003,Canterbury 003D,,,,,Good,South-East England and South London,,200000682558.0,,Not applicable,Not applicable,,,E02005012,E01024083,99.0,
|
||||
118361,886,Kent,2265,Hoath Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,103.0,45.0,58.0,16.5,Not applicable,,Not applicable,,Supported by a federation,The Federation of Chislet CE and Hoath Primary Schools,10073335.0,,Not applicable,18-01-2022,12-01-2024,School Lane,Hoath,,Canterbury,Kent,CT3 4LA,www.chislethoathfederation.co.uk,1227860249.0,Mr,Tim,Whitehouse,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Reculver,North Thanet,(England/Wales) Rural hamlet and isolated dwellings,E10000016,620491.0,164365.0,Canterbury 010,Canterbury 010C,,,,,Good,South-East England and South London,,10033152360.0,,Not applicable,Not applicable,,,E02005019,E01024086,17.0,
|
||||
118363,886,Kent,2268,Westmeads Community Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,156.0,84.0,72.0,23.1,Not applicable,,Not applicable,,Not under a federation,,10074071.0,,Not applicable,18-05-2022,03-06-2024,Cromwell Road,,,Whitstable,Kent,CT5 1NA,www.westmeads.kent.sch.uk/,1227272995.0,Ms,Kirsty,White,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Gorrell,Canterbury,(England/Wales) Urban city and town,E10000016,611013.0,166711.0,Canterbury 008,Canterbury 008D,,,,,Requires improvement,South-East England and South London,,100062300538.0,,Not applicable,Not applicable,,,E02005017,E01024072,36.0,
|
||||
118364,886,Kent,2269,Whitstable Junior School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,255.0,No Special Classes,19-01-2023,232.0,126.0,106.0,28.9,Supported by a trust,The Coastal Alliance Co-operative Trust,Not applicable,,Not under a federation,,10077448.0,,Not applicable,18-06-2019,02-04-2024,Oxford Street,,,Whitstable,Kent,CT5 1DB,www.whitstable-junior.kent.sch.uk/,1227272385.0,Ms,Sarah,Kent,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Gorrell,Canterbury,(England/Wales) Urban city and town,E10000016,610711.0,166216.0,Canterbury 008,Canterbury 008C,,,,,Good,South-East England and South London,,100062300650.0,,Not applicable,Not applicable,,,E02005017,E01024070,67.0,
|
||||
118365,886,Kent,2270,Aldington Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,191.0,112.0,79.0,11.0,Supported by a trust,CARE Foundation Trust,Not applicable,,Not under a federation,,10073112.0,,Not applicable,07-02-2024,22-05-2024,Roman Road,Aldington,,Ashford,Kent,TN25 7EE,http://www.aldington.kent.sch.uk/,1233720247.0,Mr,Ben,Dawson.,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Saxon Shore,Folkestone and Hythe,(England/Wales) Rural village,E10000016,606435.0,136475.0,Ashford 010,Ashford 010A,,,,,Good,South-East England and South London,,100062562493.0,,Not applicable,Not applicable,,,E02005005,E01024013,21.0,
|
||||
118369,886,Kent,2275,Victoria Road Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,206.0,No Special Classes,19-01-2023,211.0,113.0,98.0,25.6,Not applicable,,Not applicable,,Not under a federation,,10069513.0,,Not applicable,15-01-2019,25-04-2024,Victoria Road,,,Ashford,Kent,TN23 7HQ,http://www.victoriaroad.co.uk/,1233620044.0,Mrs,Kelly,Collens,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Victoria,Ashford,(England/Wales) Urban city and town,E10000016,600804.0,142343.0,Ashford 005,Ashford 005F,,,,,Good,South-East England and South London,,100062048541.0,,Not applicable,Not applicable,,,E02005000,E01034985,54.0,
|
||||
118370,886,Kent,2276,Willesborough Infant School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,390.0,No Special Classes,19-01-2023,357.0,177.0,180.0,15.7,Not supported by a trust,,Not applicable,,Supported by a federation,The Willesborough Schools,10080431.0,,Not applicable,15-09-2022,08-05-2024,Church Road,Willesborough,,Ashford,Kent,TN24 0JZ,www.willesborough-infant.kent.sch.uk/,1233624165.0,Mr,Tom,Head,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,,,,,South East,Ashford,Highfield,Ashford,(England/Wales) Urban city and town,E10000016,603174.0,141786.0,Ashford 006,Ashford 006B,,,,,Good,South-East England and South London,,100062560396.0,,Not applicable,Not applicable,,,E02005001,E01023995,56.0,
|
||||
118371,886,Kent,5226,Willesborough Junior School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,510.0,No Special Classes,19-01-2023,511.0,249.0,262.0,18.8,Not supported by a trust,,Not applicable,,Supported by a federation,The Willesborough Schools,10069649.0,,Not applicable,22-03-2023,12-09-2023,Highfield Road,Willesborough,,Ashford,Kent,TN24 0JU,www.willesborough-js.kent.sch.uk/,1233620405.0,Mr,Tom,Head,Interim Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Highfield,Ashford,(England/Wales) Urban city and town,E10000016,603107.0,141715.0,Ashford 006,Ashford 006B,,,,,Good,South-East England and South London,,200002904249.0,,Not applicable,Not applicable,,,E02005001,E01023995,96.0,
|
||||
118372,886,Kent,2278,Bethersden Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,140.0,No Special Classes,19-01-2023,131.0,70.0,61.0,25.4,Not applicable,,Not applicable,,Not under a federation,,10073334.0,,Not applicable,08-06-2023,21-05-2024,School Road,Bethersden,,Ashford,Kent,TN26 3AH,www.bethersden.kent.sch.uk/,1233820479.0,Ms,Rebecca,Heaton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Weald Central,Ashford,(England/Wales) Rural village,E10000016,592556.0,140108.0,Ashford 012,Ashford 012B,,,,,Good,South-East England and South London,,100062563194.0,,Not applicable,Not applicable,,,E02005007,E01024031,33.0,
|
||||
118373,886,Kent,2279,Brook Community Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,71.0,40.0,31.0,12.7,Supported by a trust,CARE Foundation Trust,Not applicable,,Not under a federation,,10073111.0,,Not applicable,11-05-2023,22-04-2024,Spelders Hill,Brook,,Ashford,Kent,TN25 5PB,www.brook-ashford.kent.sch.uk,1233812614.0,Mrs,Ellen,Ranson-McCabe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Bircholt,Folkestone and Hythe,(England/Wales) Rural village,E10000016,606030.0,143546.0,Ashford 010,Ashford 010C,,,,,Good,South-East England and South London,,100062561999.0,,Not applicable,Not applicable,,,E02005005,E01024015,9.0,
|
||||
118374,886,Kent,2280,Challock Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,210.0,100.0,110.0,7.6,Supported by a trust,CARE Foundation Trust,Not applicable,,Not under a federation,,10073110.0,,Not applicable,12-07-2023,13-05-2024,Church Lane,Challock,,Ashford,Kent,TN25 4BU,www.challockprimaryschool.co.uk,1233740286.0,Mrs,Susan,Sweet,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Downs West,Ashford,(England/Wales) Rural village,E10000016,600941.0,150288.0,Ashford 002,Ashford 002C,,,,,Outstanding,South-East England and South London,,100062561620.0,,Not applicable,Not applicable,,,E02004997,E01023989,16.0,
|
||||
118375,886,Kent,2282,Great Chart Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,420.0,219.0,201.0,17.6,Not applicable,,Not applicable,,Not under a federation,,10073333.0,,Not applicable,07-06-2023,03-06-2024,Hoxton Close,Singleton,,Ashford,Kent,TN23 5LB,www.great-chart.kent.sch.uk/,1233620040.0,Mrs,Wendy,Pang,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Singleton East,Ashford,(England/Wales) Urban city and town,E10000016,598848.0,141528.0,Ashford 007,Ashford 007E,,,,,Outstanding,South-East England and South London,,100062617920.0,,Not applicable,Not applicable,,,E02005002,E01024017,74.0,
|
||||
118377,886,Kent,2285,Mersham Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,181.0,93.0,88.0,14.4,Supported by a trust,CARE Foundation Trust,Not applicable,,Not under a federation,,10073109.0,,Not applicable,23-02-2022,12-04-2024,Church Road,Mersham,,Ashford,Kent,TN25 6NU,www.mersham.kent.sch.uk/,1233720449.0,Mrs,Cheryl,Chalkley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,"Mersham, Sevington South with Finberry",Ashford,(England/Wales) Rural village,E10000016,605060.0,139253.0,Ashford 010,Ashford 010F,,,,,Good,South-East England and South London,,100062562340.0,,Not applicable,Not applicable,,,E02005005,E01034988,26.0,
|
||||
118381,886,Kent,2289,Smeeth Community Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,140.0,No Special Classes,19-01-2023,122.0,66.0,56.0,13.1,Supported by a trust,CARE Foundation Trust,Not applicable,,Not under a federation,,10073108.0,,Not applicable,04-07-2023,03-11-2023,Caroland Close,Smeeth,,Ashford,Kent,TN25 6RX,www.smeeth.kent.sch.uk,1303813128.0,Ms,Jennifer,Payne,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Bircholt,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,607714.0,140096.0,Ashford 010,Ashford 010A,,,,,Good,South-East England and South London,,100062562341.0,,Not applicable,Not applicable,,,E02005005,E01024013,16.0,
|
||||
118385,886,Kent,2298,Hawkinge Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,396.0,189.0,207.0,17.9,Not supported by a trust,,Not applicable,,Not under a federation,,10073107.0,,Not applicable,12-06-2019,03-05-2024,Canterbury Road,Hawkinge,,Folkestone,Kent,CT18 7BN,www.hawkingeprimaryschool.co.uk/,1303892224.0,Miss,Aly,Ward,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs East,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,621648.0,139892.0,Folkestone and Hythe 002,Folkestone and Hythe 002B,,,,,Outstanding,South-East England and South London,,50034845.0,,Not applicable,Not applicable,,,E02005103,E01024541,71.0,
|
||||
118387,886,Kent,2300,Sellindge Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,142.0,61.0,81.0,13.4,Not applicable,,Not applicable,,Not under a federation,,10073331.0,,Not applicable,03-02-2023,22-03-2024,Main Road,Sellindge,,Ashford,Kent,TN25 6JY,www.sellindge-ashford.co.uk/,1303812073.0,Miss,Joanne,Wren,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs West,Folkestone and Hythe,(England/Wales) Rural village,E10000016,610305.0,138188.0,Folkestone and Hythe 009,Folkestone and Hythe 009D,,,,,Good,South-East England and South London,,50010667.0,,Not applicable,Not applicable,,,E02005110,E01024546,19.0,
|
||||
118393,886,Kent,2312,River Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,427.0,Not applicable,19-01-2023,413.0,210.0,203.0,18.4,Not applicable,,Not applicable,,Supported by a federation,Lydden and River Primary Schools Federation,10072994.0,,Not applicable,29-11-2013,24-01-2024,Lewisham Road,River,,Dover,Kent,CT17 0PP,www.river.kent.sch.uk/,1304822516.0,Mr,Neil,Brinicombe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,12.0,12.0,,,South East,Dover,Dover Downs & River,Dover,(England/Wales) Urban city and town,E10000016,629160.0,143403.0,Dover 010,Dover 010C,,,,,Outstanding,South-East England and South London,,100062290036.0,,Not applicable,Not applicable,,,E02005050,E01024233,76.0,
|
||||
118398,886,Kent,2318,Langdon Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,86.0,51.0,35.0,22.1,Not applicable,,Not applicable,,Not under a federation,,10073330.0,,Not applicable,28-01-2020,07-05-2024,East Langdon,,The Street,Dover,Kent,CT15 5JQ,www.langdonprimaryschool.co.uk,1304852600.0,Mrs,Lynn,Paylor Sutton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Guston, Kingsdown & St Margaret's-at-Cliffe",Dover,(England/Wales) Rural village,E10000016,633397.0,146333.0,Dover 012,Dover 012B,,,,,Good,South-East England and South London,,100062287609.0,,Not applicable,Not applicable,,,E02005052,E01024238,19.0,
|
||||
118399,886,Kent,2320,Eythorne Elvington Community Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,140.0,No Special Classes,19-01-2023,107.0,47.0,60.0,52.3,Not applicable,,Not applicable,,Supported by a federation,The Federation of Shepherdswell Church of England and Eythorne Elvington,10069511.0,,Not applicable,14-12-2022,17-05-2024,Adelaide Road,Eythorne,,Dover,Kent,CT15 4AN,www.eythorne-elvington.kent.sch.uk,1304830376.0,Mr,N,Garvey,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Aylesham, Eythorne & Shepherdswell",Dover,(England/Wales) Rural town and fringe,E10000016,628011.0,149789.0,Dover 006,Dover 006D,,,,,Outstanding,South-East England and South London,,10034882456.0,,Not applicable,Not applicable,,,E02005046,E01024204,56.0,
|
||||
118400,886,Kent,2321,Lydden Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,84.0,No Special Classes,19-01-2023,78.0,37.0,41.0,11.7,Not applicable,,Not applicable,,Supported by a federation,Lydden and River Primary Schools Federation,10073329.0,,Not applicable,05-02-2019,26-03-2024,Stonehall,Lydden,,Dover,Kent,CT15 7LA,http://www.lydden.kent.sch.uk,1304822887.0,Mr,Neil,Brinicombe,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Dover Downs & River,Dover,(England/Wales) Rural village,E10000016,626743.0,145568.0,Dover 010,Dover 010G,,,,,Good,South-East England and South London,,100062288383.0,,Not applicable,Not applicable,,,E02005050,E01033210,9.0,
|
||||
118401,886,Kent,2322,Preston Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,133.0,No Special Classes,19-01-2023,117.0,58.0,59.0,7.7,Not applicable,,Not applicable,,Supported by a federation,The Federation of Preston and Wingham,10073328.0,,Not applicable,01-02-2024,20-05-2024,Mill Lane,Preston,,Canterbury,Kent,CT3 1HB,www.prestonprimary.org.uk,1227722235.0,Mrs,Helen,Clements,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Little Stour & Ashstone,South Thanet,(England/Wales) Rural village,E10000016,625140.0,160901.0,Dover 001,Dover 001A,,,,,Good,South-East England and South London,,100062298104.0,,Not applicable,Not applicable,,,E02005041,E01024206,9.0,
|
||||
118403,886,Kent,2326,Wingham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,191.0,97.0,94.0,15.7,Not applicable,,Not applicable,,Supported by a federation,The Federation of Preston and Wingham,10073327.0,,Not applicable,17-11-2021,18-04-2024,School Lane,Wingham,,Canterbury,Kent,CT3 1BD,www.winghamprimary.org.uk/,1227720277.0,Mrs,Helen,Clements,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Little Stour & Ashstone,South Thanet,(England/Wales) Rural town and fringe,E10000016,624160.0,157221.0,Dover 001,Dover 001D,,,,,Good,South-East England and South London,,100062298256.0,,Not applicable,Not applicable,,,E02005041,E01024209,30.0,
|
||||
118405,886,Kent,2328,St Mildred's Primary Infant School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,269.0,135.0,134.0,21.9,Supported by a trust,Thanet Endeavour Learning Trust,Not applicable,,Supported by a federation,The Federation of Bromstone Primary School and St.Mildred's Primary Infant School,10074070.0,,Not applicable,24-11-2021,30-04-2024,St Mildred's Avenue,,,Broadstairs,Kent,CT10 2BX,http://www.st-mildreds.kent.sch.uk,1843862035.0,Headteacher,James,Williams,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Viking,South Thanet,(England/Wales) Urban city and town,E10000016,639125.0,167790.0,Thanet 010,Thanet 010C,,,,,Outstanding,South-East England and South London,,200002882176.0,,Not applicable,Not applicable,,,E02005141,E01024706,59.0,
|
||||
118406,886,Kent,2329,Callis Grange Nursery and Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,296.0,No Special Classes,19-01-2023,288.0,159.0,129.0,21.2,Not applicable,,Not applicable,,Not under a federation,,10074069.0,,Not applicable,21-04-2022,15-05-2024,Beacon Road,,,Broadstairs,Kent,CT10 3DG,http://www.callis-grange.kent.sch.uk,1843862531.0,Mrs,Vikki,Bowman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Beacon Road,South Thanet,(England/Wales) Urban city and town,E10000016,638675.0,169146.0,Thanet 006,Thanet 006A,,,,,Good,South-East England and South London,,100062281748.0,,Not applicable,Not applicable,,,E02005137,E01024633,61.0,
|
||||
118411,886,Kent,2337,St Crispin's Community Primary Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,254.0,128.0,126.0,28.3,Not applicable,,Not applicable,,Not under a federation,,10074068.0,,Not applicable,11-09-2019,22-04-2024,St Crispin's Road,,,Westgate-on-Sea,Kent,CT8 8EB,http://www.st-crispinsinfants.org.uk,1843832040.0,Mrs,Louise,Davidson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Westgate-on-Sea,North Thanet,(England/Wales) Urban city and town,E10000016,632291.0,169367.0,Thanet 007,Thanet 007E,,,,,Good,South-East England and South London,,200001851326.0,,Not applicable,Not applicable,,,E02005138,E01024716,72.0,
|
||||
118414,886,Kent,2340,Ellington Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,134.0,71.0,63.0,47.8,Not applicable,,Not applicable,,Not under a federation,,10074067.0,,Not applicable,19-07-2022,23-04-2024,High Street,St Lawrence,,Ramsgate,Kent,CT11 0QH,www.ellington-infant.kent.sch.uk,1843591638.0,Mr,Adnan,Ahmet,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Central Harbour,South Thanet,(England/Wales) Urban city and town,E10000016,637175.0,165319.0,Thanet 015,Thanet 015A,,,,,Good,South-East England and South London,,200003079680.0,,Not applicable,Not applicable,,,E02005146,E01024645,64.0,
|
||||
118416,886,Kent,2345,Priory Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,155.0,76.0,79.0,38.1,Not applicable,,Not applicable,,Not under a federation,,10074066.0,,Not applicable,21-06-2023,05-06-2024,Cannon Road,,,Ramsgate,Kent,CT11 9XT,www.priory.kent.sch.uk,1843593105.0,Ms,Tracey,Sandy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Central Harbour,South Thanet,(England/Wales) Urban city and town,E10000016,637770.0,165232.0,Thanet 016,Thanet 016A,,,,,Good,South-East England and South London,,100062283791.0,,Not applicable,Not applicable,,,E02005147,E01024646,59.0,
|
||||
118436,886,Kent,2431,Shears Green Junior School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,480.0,No Special Classes,19-01-2023,483.0,232.0,251.0,28.0,Supported by a trust,Northfleet Schools Co-Operative Trust,Not applicable,,Not under a federation,,10073106.0,,Not applicable,19-01-2023,03-05-2024,White Avenue,Northfleet,,Gravesend,Kent,DA11 7JB,www.shearsgreenjuniorschool.co.uk,1474567359.0,Mr,Matthew,Paterson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Coldharbour & Perry Street,Gravesham,(England/Wales) Urban major conurbation,E10000016,563816.0,172261.0,Gravesham 009,Gravesham 009A,,,,,Good,South-East England and South London,,100062310707.0,,Not applicable,Not applicable,,,E02005063,E01024264,135.0,
|
||||
118438,886,Kent,2434,West Minster Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,570.0,Has Special Classes,19-01-2023,508.0,253.0,255.0,56.6,Not applicable,,Not applicable,,Supported by a federation,Sheerness West Federation,10076225.0,,Not applicable,01-12-2021,14-05-2024,St George's Avenue,,,Sheerness,Kent,ME12 1ET,www.swfed.co.uk,1795662178.0,Ms,Hazel,Brewer,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheerness,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591524.0,173868.0,Swale 002,Swale 002C,,,,,Good,South-East England and South London,,100062626557.0,,Not applicable,Not applicable,,,E02005116,E01024615,269.0,
|
||||
118449,886,Kent,2454,Aycliffe Community Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,135.0,No Special Classes,19-01-2023,93.0,54.0,39.0,61.3,Not applicable,,Not applicable,,Not under a federation,,10073325.0,,Not applicable,01-12-2022,19-04-2024,St David's Avenue,,,Dover,Kent,CT17 9HJ,www.aycliffecpschool.co.uk,1304202651.0,Mrs,Jacky,Cox,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Town & Castle,Dover,(England/Wales) Urban city and town,E10000016,630152.0,139911.0,Dover 013,Dover 013E,,,,,Good,South-East England and South London,,200001851339.0,,Not applicable,Not applicable,,,E02005053,E01024249,57.0,
|
||||
118453,886,Kent,2459,Riverhead Infants' School,Community school,Local authority maintained schools,Open,Not applicable,26-02-1997,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,268.0,140.0,128.0,4.5,Not applicable,,Not applicable,,Not under a federation,,10080749.0,,Not applicable,22-03-2023,24-01-2024,Worships Hill,,,Riverhead,Kent,TN13 2AS,http://www.riverhead.kent.sch.uk,1732452475.0,Mr,Andrew,King,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Dunton Green and Riverhead,Sevenoaks,(England/Wales) Urban city and town,E10000016,551308.0,156016.0,Sevenoaks 011,Sevenoaks 011C,,,,,Good,South-East England and South London,,50002018912.0,,Not applicable,Not applicable,,,E02005097,E01024424,12.0,
|
||||
118456,886,Kent,2465,Claremont Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,436.0,No Special Classes,19-01-2023,438.0,239.0,199.0,2.7,Not applicable,,Not applicable,,Not under a federation,,10069510.0,,Not applicable,12-01-2023,24-05-2024,Banner Farm Road,,,Tunbridge Wells,Kent,TN2 5EB,www.claremont.kent.sch.uk,1892531395.0,Mrs,Candi,Roberts,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Pantiles and St Mark's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558955.0,138862.0,Tunbridge Wells 012,Tunbridge Wells 012E,,,,,Good,South-East England and South London,,100062554871.0,,Not applicable,Not applicable,,,E02005173,E01024820,12.0,
|
||||
118459,886,Kent,2471,Whitfield Aspen School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,591.0,Has Special Classes,19-01-2023,585.0,329.0,256.0,36.1,Not applicable,,Not applicable,,Not under a federation,,10072993.0,,Not applicable,12-09-2019,02-05-2024,Mayfield Road,Whitfield,,Dover,Kent,CT16 3LJ,www.whitfieldaspenschool.co.uk/,1304821526.0,Mr,Jason,Cook,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,PMLD - Profound and Multiple Learning Difficulty,,,,,,,,,,,,,Resourced provision,168.0,168.0,,,South East,Dover,Whitfield,Dover,(England/Wales) Urban city and town,E10000016,630324.0,145151.0,Dover 010,Dover 010F,,,,,Good,South-East England and South London,,100062289581.0,,Not applicable,Not applicable,,,E02005050,E01024256,211.0,
|
||||
118461,886,Kent,2474,St Paul's Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,261.0,120.0,141.0,30.3,Not applicable,,Not applicable,,Supported by a federation,St Paul's and North Borough Schools Federation,10074064.0,,Not applicable,15-01-2020,24-04-2024,Hillary Road,,,Maidstone,Kent,ME14 2BS,www.stpaulsmaidstone.co.uk,1622753322.0,Mrs,Sarah,Aldridge,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,North,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576222.0,157046.0,Maidstone 004,Maidstone 004D,,,,,Good,South-East England and South London,,200003707812.0,,Not applicable,Not applicable,,,E02005071,E01024383,79.0,
|
||||
118465,886,Kent,2482,Langton Green Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,414.0,209.0,205.0,5.1,Not applicable,,Not applicable,,Not under a federation,,10073324.0,,Not applicable,20-06-2012,21-05-2024,Lampington Row,Langton Green,,Tunbridge Wells,Kent,TN3 0JG,www.langton-green-school.org,1892862648.0,Mr,Alex,Cornelius,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Speldhurst and Bidborough,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,554343.0,139529.0,Tunbridge Wells 006,Tunbridge Wells 006D,,,,,Outstanding,South-East England and South London,,100062566427.0,,Not applicable,Not applicable,,,E02005167,E01024853,21.0,
|
||||
118468,886,Kent,2490,Bishops Down Primary and Nursery School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,233.0,121.0,112.0,11.7,Not applicable,,Not applicable,,Not under a federation,,10077232.0,,Not applicable,02-11-2023,16-04-2024,Rydal Drive,,,Tunbridge Wells,Kent,TN4 9SU,www.bishopsdownprimary.org/,1892520114.0,Mrs,Laura,Johnson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,7.0,8.0,,,South East,Tunbridge Wells,Culverden,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,556995.0,140187.0,Tunbridge Wells 007,Tunbridge Wells 007B,,,,,Requires improvement,South-East England and South London,,100062586163.0,,Not applicable,Not applicable,,,E02005168,E01024800,25.0,
|
||||
118479,886,Kent,2509,Singlewell Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,390.0,No Special Classes,19-01-2023,392.0,178.0,214.0,18.4,Not applicable,,Not applicable,,Not under a federation,,10069509.0,,Not applicable,25-01-2023,03-05-2024,Mackenzie Way,,,Gravesend,Kent,DA12 5TY,www.singlewell.kent.sch.uk,1474569859.0,Mrs,Michelle,Brown,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Singlewell,Gravesham,(England/Wales) Urban major conurbation,E10000016,565793.0,170786.0,Gravesham 011,Gravesham 011B,,,,,Good,South-East England and South London,,100062312820.0,,Not applicable,Not applicable,,,E02005065,E01024304,72.0,
|
||||
118480,886,Kent,2510,Cheriton Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,389.0,203.0,186.0,24.9,Not supported by a trust,,Not applicable,,Not under a federation,,10073105.0,,Not applicable,30-10-2019,24-04-2024,Church Road,,,Folkestone,Kent,CT20 3EP,http://www.cheritonprimary.org.uk/,1303276112.0,Ms,Sophia,Dover,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,619394.0,136651.0,Folkestone and Hythe 005,Folkestone and Hythe 005C,,,,,Good,South-East England and South London,,50025272.0,,Not applicable,Not applicable,,,E02005106,E01024494,97.0,
|
||||
118484,886,Kent,2514,Brookfield Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,180.0,85.0,95.0,18.9,Not applicable,,Not applicable,,Supported by a federation,Flourish,10074062.0,,Not applicable,20-04-2023,23-04-2024,Swallow Road,Larkfield,,Aylesford,Kent,ME20 6PY,https://flourish-federation.secure-primarysite.net/,1732840955.0,Miss,C,Smith,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Larkfield,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,570115.0,158700.0,Tonbridge and Malling 003,Tonbridge and Malling 003F,,,,,Good,South-East England and South London,,10002912773.0,,Not applicable,Not applicable,,,E02005151,E01024765,34.0,
|
||||
118487,886,Kent,2519,Vigo Village School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,142.0,78.0,64.0,15.5,Not applicable,,Not applicable,,Not under a federation,,10073323.0,,Not applicable,06-11-2019,16-05-2024,Erskine Road,Vigo Village,Meopham,Gravesend,Kent,DA13 0RL,http://www.vigo.kent.sch.uk,1732823144.0,Mr,Roger,Barber,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Meopham South & Vigo,Gravesham,(England/Wales) Rural town and fringe,E10000016,564387.0,161691.0,Gravesham 013,Gravesham 013C,,,,,Good,South-East England and South London,,100062312944.0,,Not applicable,Not applicable,,,E02005067,E01024275,22.0,
|
||||
118488,886,Kent,2520,Madginford Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,654.0,No Special Classes,19-01-2023,642.0,353.0,289.0,10.1,Not applicable,,Not applicable,,Not under a federation,,10073322.0,,Not applicable,20-04-2023,12-09-2023,Egremont Road,Bearsted,,Maidstone,Kent,ME15 8LH,www.madginfordprimaryschool.co.uk,1622734539.0,Mrs,Amanda,Woolcombe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bearsted,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,579076.0,154708.0,Maidstone 007,Maidstone 007B,,,,,Good,South-East England and South London,,200003691655.0,,Not applicable,Not applicable,,,E02005074,E01024327,65.0,
|
||||
118490,886,Kent,2524,Palmarsh Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,140.0,No Special Classes,19-01-2023,191.0,82.0,109.0,30.9,Not applicable,,Not applicable,,Not under a federation,,10073321.0,,Not applicable,02-10-2019,04-06-2024,St George's Place,,,Hythe,Kent,CT21 6NE,http://www.palmarsh.kent.sch.uk,1303260212.0,Mr,Jamie,Leach,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Hythe Rural,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,613796.0,133711.0,Folkestone and Hythe 010,Folkestone and Hythe 010D,,,,,Good,South-East England and South London,,50013151.0,,Not applicable,Not applicable,,,E02005111,E01024529,59.0,
|
||||
118491,886,Kent,2525,Painters Ash Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,406.0,203.0,203.0,22.4,Supported by a trust,Northfleet Schools Co-Operative Trust,Not applicable,,Not under a federation,,10073104.0,,Not applicable,01-03-2023,23-05-2024,Masefield Road,Northfleet,,Gravesend,Kent,DA11 8EL,www.paintersashprimary.co.uk,1474568991.0,,Georgina,Salter,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Painters Ash,Gravesham,(England/Wales) Urban major conurbation,E10000016,562691.0,172101.0,Gravesham 006,Gravesham 006E,,,,,Good,South-East England and South London,,100062310909.0,,Not applicable,Not applicable,,,E02005060,E01024288,91.0,
|
||||
118493,886,Kent,2530,Tunbury Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,587.0,292.0,295.0,7.8,Not applicable,,Not applicable,,Not under a federation,,10073320.0,,Not applicable,08-06-2023,14-05-2024,Tunbury Avenue,Walderslade,,Chatham,Kent,ME5 9HY,www.tunbury.kent.sch.uk,1634863085.0,Mrs,Ruth,Austin,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Walderslade,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,575737.0,162426.0,Tonbridge and Malling 001,Tonbridge and Malling 001D,,,,,Good,South-East England and South London,,100062393390.0,,Not applicable,Not applicable,,,E02005149,E01024722,46.0,
|
||||
118495,886,Kent,2532,St Margaret's-at-Cliffe Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,189.0,96.0,93.0,9.0,Not applicable,,Not applicable,,Not under a federation,,10069508.0,,Not applicable,03-07-2015,03-06-2024,Sea Street,St Margaret's-At-Cliffe,,Dover,Kent,CT15 6SS,http://www.stmargaretsprimary.co.uk,1304852639.0,Ms,Helen,Comfort,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Guston, Kingsdown & St Margaret's-at-Cliffe",Dover,(England/Wales) Rural town and fringe,E10000016,636125.0,144710.0,Dover 009,Dover 009B,,,,,Outstanding,South-East England and South London,,100062288026.0,,Not applicable,Not applicable,,,E02005049,E01024236,17.0,
|
||||
118501,886,Kent,2539,Stocks Green Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,218.0,No Special Classes,19-01-2023,216.0,115.0,101.0,5.1,Not applicable,,Not applicable,,Not under a federation,,10069507.0,,Not applicable,07-03-2024,22-05-2024,Leigh Road,Hildenborough,,Tonbridge,Kent,TN11 9AE,www.stocksgreenprimary.co.uk,1732832758.0,Mr,P,Hipkiss,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Hildenborough,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,557065.0,148064.0,Tonbridge and Malling 010,Tonbridge and Malling 010B,,,,,Good,South-East England and South London,,100062628569.0,,Not applicable,Not applicable,,,E02005158,E01024752,11.0,
|
||||
118505,886,Kent,2545,Sandgate Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,418.0,206.0,212.0,9.8,Not applicable,,Not applicable,,Not under a federation,,10073318.0,,Not applicable,16-09-2021,12-09-2023,Coolinge Lane,,,Folkestone,Kent,CT20 3QU,www.sandgateprimaryschool.co.uk/,1303257280.0,Mr,Matthew,Green,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Sandgate & West Folkestone,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,620849.0,135894.0,Folkestone and Hythe 006,Folkestone and Hythe 006H,,,,,Good,South-East England and South London,,50027156.0,,Not applicable,Not applicable,,,E02005107,E01024521,41.0,
|
||||
118511,886,Kent,2552,Sandling Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,421.0,226.0,195.0,4.8,Not applicable,,Not applicable,,Not under a federation,,10073317.0,,Not applicable,05-02-2020,07-05-2024,Ashburnham Road,Penenden Heath,,Maidstone,Kent,ME14 2JG,http://www.sandling.kent.sch.uk/,1622763297.0,Miss,Claire,Coombes,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,North,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576624.0,157776.0,Maidstone 002,Maidstone 002D,,,,,Good,South-East England and South London,,10014308132.0,,Not applicable,Not applicable,,,E02005069,E01024385,20.0,
|
||||
118515,886,Kent,2559,Capel-le-Ferne Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,177.0,99.0,78.0,22.6,Not applicable,,Not applicable,,Not under a federation,,10071996.0,,Not applicable,30-03-2022,21-05-2024,Capel Street,Capel-le-Ferne,,Folkestone,Kent,CT18 7HB,www.capelleferneprimary.co.uk,1303251353.0,Mr,Anthony,Richards,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dover,Alkham & Capel-le-Ferne,Dover,(England/Wales) Rural hamlet and isolated dwellings,E10000016,625026.0,139092.0,Dover 014,Dover 014A,,,,,Good,South-East England and South London,,100062290850.0,,Not applicable,Not applicable,,,E02005054,E01024198,40.0,
|
||||
118516,886,Kent,2562,Lunsford Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,208.0,104.0,104.0,20.7,Not applicable,,Not applicable,,Not under a federation,,10073316.0,,Not applicable,14-06-2023,01-04-2024,Swallow Road,Larkfield,,Aylesford,Kent,ME20 6PY,http://www.lunsford.kent.sch.uk,1732843352.0,Mr,Gary,Anscombe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Larkfield,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,570068.0,158786.0,Tonbridge and Malling 003,Tonbridge and Malling 003F,,,,,Good,South-East England and South London,,200000966558.0,,Not applicable,Not applicable,,,E02005151,E01024765,43.0,
|
||||
118523,886,Kent,2574,Downs View Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,260.0,138.0,122.0,16.2,Not applicable,,Not applicable,,Not under a federation,,10080430.0,,Not applicable,18-10-2023,21-05-2024,Ball Lane,Kennington,,Ashford,Kent,TN25 4PJ,www.downs-view.kent.sch.uk/,1233632339.0,Mrs,Tracy Kent,Mrs Sarah Collins,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Kennington,Ashford,(England/Wales) Urban city and town,E10000016,602119.0,145227.0,Ashford 003,Ashford 003B,,,,,Good,South-East England and South London,,100062561615.0,,Not applicable,Not applicable,,,E02004998,E01023999,42.0,
|
||||
118524,886,Kent,2578,Kingswood Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,140.0,No Special Classes,19-01-2023,129.0,70.0,59.0,10.1,Not applicable,,Not applicable,,Not under a federation,,10073315.0,,Not applicable,19-07-2022,20-11-2023,Cayser Drive,Kingswood,,Maidstone,Kent,ME17 3QF,www.kingswoodkentsch.co.uk,1622842674.0,Mrs,Lynsey,Sanchez Daviu,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Leeds,Faversham and Mid Kent,(England/Wales) Rural town and fringe,E10000016,584054.0,150831.0,Maidstone 015,Maidstone 015C,,,,,Good,South-East England and South London,,10022897422.0,,Not applicable,Not applicable,,,E02005082,E01024375,13.0,
|
||||
118526,886,Kent,2586,Senacre Wood Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,212.0,109.0,103.0,32.1,Not applicable,,Not applicable,,Not under a federation,,10073314.0,,Not applicable,04-12-2019,27-02-2024,Graveney Road,Senacre,,Maidstone,Kent,ME15 8QQ,www.senacre-wood.kent.sch.uk/,3000658430.0,Mrs,Emily,Sweeney,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Shepway South,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,578633.0,153076.0,Maidstone 013,Maidstone 013F,,,,,Good,South-East England and South London,,200003717450.0,,Not applicable,Not applicable,,,E02005080,E01024399,68.0,
|
||||
118534,886,Kent,2603,"Bromstone Primary School, Broadstairs",Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Has Special Classes,19-01-2023,406.0,221.0,185.0,38.9,Supported by a trust,Thanet Endeavour Learning Trust,Not applicable,,Supported by a federation,The Federation of Bromstone Primary School and St.Mildred's Primary Infant School,10073313.0,,Not applicable,27-03-2019,21-05-2024,Rumfields Road,,,Broadstairs,Kent,CT10 2PW,www.bromstoneschool.com/,1843867010.0,Mr,James,Williams,Executive Head,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,VI - Visual Impairment,HI - Hearing Impairment,"SLCN - Speech, language and Communication",,,,,,,,,,,Resourced provision,35.0,35.0,,,South East,Thanet,St Peters,South Thanet,(England/Wales) Urban city and town,E10000016,637928.0,167475.0,Thanet 011,Thanet 011D,,,,,Good,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005142,E01024690,158.0,
|
||||
118536,886,Kent,2607,Parkside Community Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,168.0,84.0,84.0,75.6,Supported by a trust,Thanet Endeavour Learning Trust,Not applicable,,Not under a federation,,10073312.0,,Not applicable,26-04-2023,30-04-2024,Tennyson Avenue,,,Canterbury,Kent,CT1 1EP,www.parksidecommunityprimaryschool.co.uk,1227464956.0,Mr,James,Williams,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Northgate,Canterbury,(England/Wales) Urban city and town,E10000016,616671.0,158965.0,Canterbury 014,Canterbury 014C,,,,,Good,South-East England and South London,,100062279371.0,,Not applicable,Not applicable,,,E02005023,E01024090,127.0,
|
||||
118541,886,Kent,2615,High Firs Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,206.0,98.0,108.0,13.6,Not applicable,,Not applicable,,Not under a federation,,10073311.0,,Not applicable,12-10-2023,23-04-2024,Court Crescent,,,Swanley,Kent,BR8 8NR,www.high-firs.kent.sch.uk/,1322669721.0,Mr,Andrew,Kilbride,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Swanley Christchurch and Swanley Village,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,551435.0,168115.0,Sevenoaks 003,Sevenoaks 003C,,,,,Good,South-East England and South London,,50002001954.0,,Not applicable,Not applicable,,,E02005089,E01024474,28.0,
|
||||
118548,886,Kent,2627,Sandwich Junior School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,240.0,No Special Classes,19-01-2023,210.0,104.0,106.0,22.4,Not applicable,,Not applicable,,Not under a federation,,10079030.0,,Not applicable,24-03-2022,12-09-2023,St Bart's Road,,,Sandwich,Kent,CT13 0AS,www.sandwich-junior.kent.sch.uk/,1304612227.0,Mr,Martin,Dyson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Sandwich,South Thanet,(England/Wales) Rural town and fringe,E10000016,632790.0,157449.0,Dover 002,Dover 002C,,,,,Outstanding,South-East England and South London,,200001851331.0,,Not applicable,Not applicable,,,E02005042,E01024243,47.0,
|
||||
118551,886,Kent,2632,Sevenoaks Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,587.0,312.0,275.0,15.2,Not applicable,,Not applicable,,Not under a federation,,10073310.0,,Not applicable,19-04-2023,15-04-2024,Bradbourne Park Road,,,Sevenoaks,Kent,TN13 3LB,www.sevenoaks.kent.sch.uk,1732453952.0,Mrs,Cassandra,Malone,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Town and St John's,Sevenoaks,(England/Wales) Urban city and town,E10000016,552671.0,156229.0,Sevenoaks 010,Sevenoaks 010F,,,,,Good,South-East England and South London,,100062548252.0,,Not applicable,Not applicable,,,E02005096,E01024468,89.0,
|
||||
118558,886,Kent,2643,Swalecliffe Community Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,660.0,No Special Classes,19-01-2023,581.0,320.0,261.0,18.2,Supported by a trust,The Coastal Alliance Co-operative Trust,Not applicable,,Not under a federation,,10073102.0,,Not applicable,14-09-2023,24-01-2024,Bridgefield Road,Swalecliffe,,Whitstable,Kent,CT5 2PH,http://www.swalecliffeprimary.org,1227272101.0,Mrs,Sarah,Watson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Swalecliffe,Canterbury,(England/Wales) Urban city and town,E10000016,613028.0,166878.0,Canterbury 005,Canterbury 005E,,,,,Good,South-East England and South London,,100062301012.0,,Not applicable,Not applicable,,,E02005014,E01024115,106.0,
|
||||
118563,886,Kent,2648,Aylesham Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,391.0,191.0,200.0,39.1,Not applicable,,Not applicable,,Not under a federation,,10073309.0,,Not applicable,08-06-2023,16-04-2024,Attlee Avenue,Aylesham,,Canterbury,Kent,CT3 3BS,www.aylesham.kent.sch.uk,1304840392.0,Mr,Darran,Callaghan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Aylesham, Eythorne & Shepherdswell",Dover,(England/Wales) Rural town and fringe,E10000016,623337.0,152432.0,Dover 006,Dover 006E,,,,,Good,South-East England and South London,,10034883661.0,,Not applicable,Not applicable,,,E02005046,E01035314,153.0,
|
||||
118566,886,Kent,2651,Broadwater Down Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,148.0,69.0,79.0,34.5,Not applicable,,Not applicable,,Not under a federation,,10073308.0,,Not applicable,09-03-2023,17-04-2024,Broadwater Lane,,,Tunbridge Wells,Kent,TN2 5RP,www.broadwater-down.kent.sch.uk,1892527588.0,Mr,Alex,Cornelius,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Broadwater,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,557682.0,138106.0,Tunbridge Wells 010,Tunbridge Wells 010A,,,,,Good,South-East England and South London,,100062555286.0,,Not applicable,Not applicable,,,E02005171,E01024795,51.0,
|
||||
118568,886,Kent,2653,West Borough Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,510.0,No Special Classes,19-01-2023,503.0,257.0,246.0,22.2,Not applicable,,Not applicable,,Not under a federation,,10076223.0,,Not applicable,19-10-2022,20-02-2024,Greenway,,,Maidstone,Kent,ME16 8TL,http://www.west-borough.kent.sch.uk/,1622726391.0,Mrs,Lisa,Edinburgh,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Heath,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,573885.0,155150.0,Maidstone 008,Maidstone 008C,,,,,Good,South-East England and South London,,10014309300.0,,Not applicable,Not applicable,,,E02005075,E01024367,102.0,
|
||||
118575,886,Kent,2662,Long Mead Community Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,128.0,50.0,78.0,54.7,Not supported by a trust,,Not applicable,,Not under a federation,,10073101.0,,Not applicable,18-09-2019,31-03-2024,Waveney Road,,,Tonbridge,Kent,TN10 3JU,www.long-mead.kent.sch.uk/,1732350601.0,Mrs,Karen,Follows,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Trench,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558754.0,148444.0,Tonbridge and Malling 009,Tonbridge and Malling 009C,,,,,Good,South-East England and South London,,200000961885.0,,Not applicable,Not applicable,,,E02005157,E01024775,64.0,
|
||||
118585,886,Kent,2674,King's Farm Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,492.0,No Special Classes,19-01-2023,437.0,237.0,200.0,53.2,Not applicable,,Not applicable,,Supported by a federation,Ifield School & King's Farm Primary School Federation,10069504.0,,Not applicable,28-02-2024,22-05-2024,Cedar Avenue,,,Gravesend,Kent,DA12 5JT,www.kings-farm.kent.sch.uk,1474566979.0,Mr,Chris,Jackson,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,13.0,15.0,,,South East,Gravesham,Singlewell,Gravesham,(England/Wales) Urban major conurbation,E10000016,565269.0,171511.0,Gravesham 011,Gravesham 011D,,,,,Good,South-East England and South London,,100062312756.0,,Not applicable,Not applicable,,,E02005065,E01024306,216.0,
|
||||
118590,886,Kent,3010,St Pauls' Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,107.0,52.0,55.0,10.3,Not applicable,,Not applicable,,Supported by a federation,The Compass Federation,10070335.0,,Not applicable,28-01-2020,01-05-2024,School Lane,,,Swanley Village,Kent,BR8 7PJ,www.st-pauls-swanley.kent.sch.uk,1322664324.0,Mr,Benjamin,Hulme,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Swanley Christchurch and Swanley Village,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,552800.0,169829.0,Sevenoaks 001,Sevenoaks 001D,,,,,Good,South-East England and South London,,100062076900.0,,Not applicable,Not applicable,,,E02005087,E01024472,11.0,
|
||||
118592,886,Kent,3015,Fawkham Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,102.0,50.0,52.0,5.9,Not applicable,,Not applicable,,Not under a federation,,10078483.0,,Not applicable,22-11-2023,15-04-2024,Valley Road,Fawkham,,LONGFIELD,Kent,DA3 8NA,http://fawkhamschool.com,1474702312.0,Miss,Mandy,Bridges,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Fawkham and West Kingsdown,Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,558819.0,166693.0,Sevenoaks 007,Sevenoaks 007C,,,,,Good,South-East England and South London,,10035185497.0,,Not applicable,Not applicable,,,E02005093,E01024437,6.0,
|
||||
118597,886,Kent,3022,Benenden Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,211.0,96.0,115.0,11.4,Not applicable,,Not applicable,,Supported by a federation,The 10:10 Primary Federation,10074253.0,,Not applicable,24-02-2022,20-05-2024,Rolvenden Road,,,Cranbrook,,TN17 4EH,http://www.benenden-cep.kent.sch.uk,1580240565.0,Mrs,Lindsay,Roberts,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tunbridge Wells,Benenden and Cranbrook,Maidstone and The Weald,(England/Wales) Rural village,E10000016,580826.0,132744.0,Tunbridge Wells 014,Tunbridge Wells 014A,,,,,Good,South-East England and South London,United Kingdom,10000069843.0,,Not applicable,Not applicable,,,E02005175,E01024789,24.0,
|
||||
118598,886,Kent,3023,Bidborough Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,207.0,109.0,98.0,6.3,Not applicable,,Not applicable,,Not under a federation,,10070333.0,,Not applicable,10-11-2022,20-05-2024,Spring Lane,Bidborough,,Tunbridge Wells,Kent,TN3 0UE,http://www.bidborough.kent.sch.uk,1892529333.0,Mrs,Julie,Burton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Speldhurst and Bidborough,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,556545.0,143127.0,Tunbridge Wells 006,Tunbridge Wells 006E,,,,,Good,South-East England and South London,,100062566429.0,,Not applicable,Not applicable,,,E02005167,E01024854,13.0,
|
||||
118600,886,Kent,3027,Cranbrook Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,199.0,93.0,106.0,32.2,Not applicable,,Not applicable,,Not under a federation,,10070332.0,,Not applicable,22-06-2022,21-05-2024,Carriers Road,,,Cranbrook,Kent,TN17 3JZ,http://www.cranbrook-cep.kent.sch.uk,1580713249.0,Miss,Francesca,Shaw,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Benenden and Cranbrook,Maidstone and The Weald,(England/Wales) Rural town and fringe,E10000016,577605.0,136487.0,Tunbridge Wells 013,Tunbridge Wells 013C,,,,,Good,South-East England and South London,,100062552228.0,,Not applicable,Not applicable,,,E02005174,E01024790,64.0,
|
||||
118601,886,Kent,3029,Goudhurst and Kilndown Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,211.0,107.0,104.0,15.6,Not applicable,,Not applicable,,Supported by a federation,The 10:10 Primary Federation,10078482.0,,Not applicable,20-03-2014,20-05-2024,Beaman Close,Cranbrook Road,Goudhurst,Cranbrook,Kent,TN17 1DZ,www.goudhurst-kilndown.kent.sch.uk,1580211365.0,Mrs,Lindsay,Roberts,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Goudhurst and Lamberhurst,Tunbridge Wells,(England/Wales) Rural village,E10000016,572817.0,137814.0,Tunbridge Wells 011,Tunbridge Wells 011C,,,,,Outstanding,South-East England and South London,,10008665566.0,,Not applicable,Not applicable,,,E02005172,E01024804,33.0,
|
||||
118602,886,Kent,3032,Hawkhurst Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,183.0,100.0,83.0,42.1,Not applicable,,Not applicable,,Not under a federation,,10070331.0,,Not applicable,25-01-2023,15-04-2024,Fowlers Park,Rye Road,Hawkhurst,Cranbrook,Kent,TN18 4JJ,www.hawkhurst.kent.sch.uk,1580753254.0,Mrs,Jodi,Hacker,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Hawkhurst and Sandhurst,Tunbridge Wells,(England/Wales) Rural town and fringe,E10000016,576506.0,130526.0,Tunbridge Wells 014,Tunbridge Wells 014B,,,,,Good,South-East England and South London,,10000066322.0,,Not applicable,Not applicable,,,E02005175,E01024807,77.0,
|
||||
118603,886,Kent,3033,Hildenborough Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,190.0,92.0,98.0,8.4,Not applicable,,Not applicable,,Not under a federation,,10069124.0,,Not applicable,01-03-2023,23-01-2024,Riding Lane,Hildenborough,,Tonbridge,Kent,TN11 9HY,http://www.hildenborough.kent.sch.uk,1732833394.0,Miss,Ruth,Ardrey,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Hildenborough,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,556585.0,148896.0,Tonbridge and Malling 010,Tonbridge and Malling 010D,,,,,Good,South-East England and South London,,200000966230.0,,Not applicable,Not applicable,,,E02005158,E01024754,16.0,
|
||||
118604,886,Kent,3034,Lamberhurst St Mary's CofE (Voluntary Controlled) Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,206.0,95.0,111.0,8.7,Not applicable,,Not applicable,,Not under a federation,,10069123.0,,Not applicable,08-03-2023,01-05-2024,Pearse Place,Lamberhurst,,Tunbridge Wells,Kent,TN3 8EJ,www.lamberhurst.kent.sch.uk,1892890281.0,Mrs,Caroline,Bromley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Goudhurst and Lamberhurst,Tunbridge Wells,(England/Wales) Rural village,E10000016,567565.0,136008.0,Tunbridge Wells 011,Tunbridge Wells 011D,,,,,Good,South-East England and South London,,10000067311.0,,Not applicable,Not applicable,,,E02005172,E01024805,18.0,
|
||||
118606,886,Kent,3037,"St John's Church of England Primary School, Sevenoaks",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,202.0,90.0,112.0,10.9,Not applicable,,Not applicable,,Not under a federation,,10078481.0,,Not applicable,26-04-2023,19-04-2024,Bayham Road,,,Sevenoaks,Kent,TN13 3XD,www.stjohnssevenoaks.co.uk,1732453944.0,Mrs,Therese,Pullan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Eastern,Sevenoaks,(England/Wales) Urban city and town,E10000016,553550.0,155884.0,Sevenoaks 010,Sevenoaks 010C,,,,,Good,South-East England and South London,,10035184681.0,,Not applicable,Not applicable,,,E02005096,E01024461,22.0,
|
||||
118607,886,Kent,3042,Speldhurst Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,213.0,106.0,107.0,8.5,Not applicable,,Not applicable,,Not under a federation,,10069121.0,,Not applicable,07-02-2014,19-03-2024,Langton Road,Speldhurst,,Tunbridge Wells,Kent,TN3 0NP,http://www.speldhurst.kent.sch.uk,1892863044.0,Mrs,Stephanie,Hayward,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tunbridge Wells,Speldhurst and Bidborough,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,555310.0,141309.0,Tunbridge Wells 006,Tunbridge Wells 006D,,,,,Outstanding,South-East England and South London,,100062566310.0,,Not applicable,Not applicable,,,E02005167,E01024853,18.0,
|
||||
118608,886,Kent,3043,Sundridge and Brasted Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,60.0,32.0,28.0,38.3,Not applicable,,Not applicable,,Not under a federation,,10078480.0,,Not applicable,12-10-2023,16-04-2024,Church Road,Sundridge,,Sevenoaks,Kent,TN14 6EA,http://www.sundridge.kent.sch.uk,1959562694.0,Mr,Tom,Hardwick,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Brasted, Chevening and Sundridge",Sevenoaks,(England/Wales) Rural village,E10000016,548465.0,154940.0,Sevenoaks 013,Sevenoaks 013A,,,,,Good,South-East England and South London,,100062548784.0,,Not applicable,Not applicable,,,E02005099,E01024417,23.0,
|
||||
118611,886,Kent,3050,St John's Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,630.0,No Special Classes,19-01-2023,631.0,314.0,317.0,11.6,Not applicable,,Not applicable,,Not under a federation,,10069120.0,,Not applicable,22-03-2023,16-05-2024,Cunningham Road,,St John's Cep,Tunbridge Wells,Kent,TN4 9EW,www.st-johns-school.org.uk/,1892678980.0,Mr,Niall,Dossad,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558728.0,141298.0,Tunbridge Wells 003,Tunbridge Wells 003A,,,,,Good,South-East England and South London,,100062586186.0,,Not applicable,Not applicable,,,E02005164,E01024836,73.0,
|
||||
118613,886,Kent,3052,St Mark's Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,420.0,No Special Classes,19-01-2023,360.0,193.0,167.0,23.1,Not applicable,,Not applicable,,Not under a federation,,10069119.0,,Not applicable,29-06-2022,21-05-2024,Ramslye Road,,,Tunbridge Wells,Kent,TN4 8LN,http://www.st-marks.kent.sch.uk,1892525402.0,Mr,Simon,Bird,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Broadwater,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,556979.0,138070.0,Tunbridge Wells 010,Tunbridge Wells 010B,,,,,Good,South-East England and South London,,100062585738.0,,Not applicable,Not applicable,,,E02005171,E01024796,83.0,
|
||||
118614,886,Kent,3053,St Peter's Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,213.0,116.0,97.0,13.1,Not applicable,,Not applicable,,Not under a federation,,10078479.0,,Not applicable,20-03-2014,27-09-2023,Hawkenbury Road,,,Tunbridge Wells,,TN2 5BW,http://www.st-peters.kent.sch.uk,1892525727.0,Mrs,Joanna,Langton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Park,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,559229.0,139204.0,Tunbridge Wells 009,Tunbridge Wells 009C,,,,,Outstanding,South-East England and South London,United Kingdom,10090053470.0,,Not applicable,Not applicable,,,E02005170,E01024824,28.0,
|
||||
118615,886,Kent,3054,Crockham Hill Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,140.0,No Special Classes,19-01-2023,137.0,70.0,67.0,0.7,Not applicable,,Not applicable,,Not under a federation,,10069118.0,,Not applicable,26-04-2023,23-05-2024,Crockham Hill,,,Edenbridge,Kent,TN8 6RP,http://www.crockhamhill.kent.sch.uk,1732866374.0,Mrs,Lisa,Higgs,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Sevenoaks,Westerham and Crockham Hill,Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,544353.0,150691.0,Sevenoaks 013,Sevenoaks 013D,,,,,Good,South-East England and South London,,10035185257.0,,Not applicable,Not applicable,,,E02005099,E01024484,1.0,
|
||||
118616,886,Kent,3055,Churchill Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,420.0,No Special Classes,19-01-2023,201.0,94.0,107.0,28.4,Not applicable,,Not applicable,,Not under a federation,,10078478.0,,Not applicable,05-12-2019,12-09-2023,Rysted Lane,,,Westerham,Kent,TN16 1EZ,www.churchill.kent.sch.uk,1959562197.0,Mrs,Kathy,Jax,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Westerham and Crockham Hill,Sevenoaks,(England/Wales) Rural town and fringe,E10000016,544398.0,154520.0,Sevenoaks 013,Sevenoaks 013D,,,,,Good,South-East England and South London,,10035185216.0,,Not applicable,Not applicable,,,E02005099,E01024484,57.0,
|
||||
118617,886,Kent,3057,St Peter's Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,194.0,102.0,92.0,14.4,Not applicable,,Not applicable,,Not under a federation,,10069117.0,,Not applicable,20-03-2019,03-06-2024,Mount Pleasant,,,Aylesford,Kent,ME20 7BE,www.stpetersaylesford.kent.sch.uk,1622717335.0,Mr,Jim,Holditch,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford North & North Downs,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,573064.0,159050.0,Tonbridge and Malling 001,Tonbridge and Malling 001A,,,,,Good,South-East England and South London,,100062628349.0,,Not applicable,Not applicable,,,E02005149,E01024719,28.0,
|
||||
118619,886,Kent,3061,Bredhurst Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,140.0,No Special Classes,19-01-2023,130.0,70.0,60.0,5.4,Not applicable,,Not applicable,,Not under a federation,,10069116.0,,Not applicable,24-01-2024,20-05-2024,The Street,Bredhurst,,Gillingham,Kent,ME7 3JY,www.bredhurst.kent.sch.uk/,1634231271.0,Mrs,Michelle,Cox,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Boxley,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,579632.0,162276.0,Maidstone 001,Maidstone 001C,,,,,Good,South-East England and South London,,200003720400.0,,Not applicable,Not applicable,,,E02005068,E01024335,7.0,
|
||||
118620,886,Kent,3062,Burham Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,196.0,No Special Classes,19-01-2023,144.0,74.0,70.0,8.3,Not applicable,,Not applicable,,Not under a federation,,10069115.0,,Not applicable,05-12-2018,14-05-2024,Bell Lane,Burham,,Rochester,Kent,ME1 3SY,http://www.burham.kent.sch.uk,1634861691.0,Mrs,Holly,Goddon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford North & North Downs,Chatham and Aylesford,(England/Wales) Rural village,E10000016,572942.0,161845.0,Tonbridge and Malling 001,Tonbridge and Malling 001F,,,,,Good,South-East England and South London,,200000961192.0,,Not applicable,Not applicable,,,E02005149,E01024728,12.0,
|
||||
118622,886,Kent,3067,Harrietsham Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,390.0,No Special Classes,19-01-2023,309.0,148.0,161.0,19.2,Not applicable,,Not applicable,,Not under a federation,,10070328.0,,Not applicable,21-02-2024,20-05-2024,West Street,Harrietsham,,Maidstone,Kent,ME17 1JZ,www.harrietsham.kent.sch.uk,1622859261.0,Mrs,Jackie,Chambers,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Harrietsham and Lenham,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,586070.0,152776.0,Maidstone 011,Maidstone 011F,,,,,Good,South-East England and South London,,10022893860.0,,Not applicable,Not applicable,,,E02005078,E01034994,59.0,
|
||||
118623,886,Kent,3069,Leeds and Broomfield Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,84.0,40.0,44.0,23.8,Not applicable,,Not applicable,,Not under a federation,,10069114.0,,Not applicable,19-10-2021,17-01-2024,Lower Street,Leeds,,Maidstone,Kent,ME17 1RL,www.leedsandbroomfieldkentsch.co.uk,1622861398.0,Mrs,Fiona,Steer,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Leeds,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,582467.0,153353.0,Maidstone 015,Maidstone 015C,,,,,Good,South-East England and South London,,200003698499.0,,Not applicable,Not applicable,,,E02005082,E01024375,20.0,
|
||||
118625,886,Kent,3072,"Maidstone, St Michael's Church of England Junior School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,178.0,No Special Classes,19-01-2023,178.0,89.0,89.0,18.5,Not applicable,,Not applicable,,Supported by a federation,St Michael's Church of England Federated Infant and Junior School,10078477.0,,Not applicable,22-11-2023,23-04-2024,Douglas Road,,,Maidstone,Kent,ME16 8ER,www.st-michaels-junior.kent.sch.uk,1622751502.0,Mrs,Lisa,Saunders,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Fant,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575266.0,155120.0,Maidstone 006,Maidstone 006G,,,,,Good,South-East England and South London,,200003668897.0,,Not applicable,Not applicable,,,E02005073,E01033091,33.0,
|
||||
118626,886,Kent,3073,St Michael's Church of England Infant School Maidstone,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,120.0,No Special Classes,19-01-2023,121.0,63.0,58.0,17.4,Not applicable,,Not applicable,,Supported by a federation,St Michael's Church of England Federated Infant and Junior School,10079671.0,,Not applicable,29-01-2014,17-04-2024,Douglas Road,,,Maidstone,Kent,ME16 8ER,http://www.st-michaels-infant.kent.sch.uk,1622751398.0,Mrs,Lisa,Saunders,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Fant,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575266.0,155120.0,Maidstone 006,Maidstone 006G,,,,,Outstanding,South-East England and South London,,200003668897.0,,Not applicable,Not applicable,,,E02005073,E01033091,21.0,
|
||||
118629,886,Kent,3081,Thurnham Church of England Infant School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,270.0,No Special Classes,19-01-2023,270.0,140.0,130.0,1.9,Not applicable,,Not applicable,,Not under a federation,,10079670.0,,Not applicable,22-02-2023,25-04-2024,The Landway,Bearsted,,Maidstone,Kent,ME14 4BL,www.thurnham-infant.kent.sch.uk,1622737685.0,Mr,Tony,Pring,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bearsted,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,579425.0,155670.0,Maidstone 005,Maidstone 005A,,,,,Good,South-East England and South London,,200003691727.0,,Not applicable,Not applicable,,,E02005072,E01024330,5.0,
|
||||
118630,886,Kent,3082,Trottiscliffe Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,84.0,No Special Classes,19-01-2023,81.0,39.0,42.0,2.5,Not applicable,,Not applicable,,Not under a federation,,10069113.0,,Not applicable,15-09-2022,21-05-2024,Church Lane,Trottiscliffe,,West Malling,Kent,ME19 5EB,http://www.trottiscliffe.kent.sch.uk/,1732822803.0,Miss,Lucy,Henderson,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Pilgrims with Ightham,Tonbridge and Malling,(England/Wales) Rural village,E10000016,564298.0,160200.0,Tonbridge and Malling 014,Tonbridge and Malling 014F,,,,,Good,South-East England and South London,,200000963789.0,,Not applicable,Not applicable,,,E02006833,E01032829,2.0,
|
||||
118631,886,Kent,3083,Ulcombe Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,63.0,31.0,32.0,50.8,Not applicable,,Not applicable,,Not under a federation,,10069112.0,,Not applicable,27-11-2019,03-06-2024,The Street,Ulcombe,,Maidstone,Kent,ME17 1DU,https://ulcombekentsch.co.uk/,1622842903.0,Ms,Emma,Masters,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,"SEMH - Social, Emotional and Mental Health",MLD - Moderate Learning Difficulty,,,,,,,,,,Resourced provision,6.0,15.0,,,South East,Maidstone,Headcorn,Faversham and Mid Kent,(England/Wales) Rural hamlet and isolated dwellings,E10000016,584797.0,148979.0,Maidstone 017,Maidstone 017C,,,,,Good,South-East England and South London,,10014312333.0,,Not applicable,Not applicable,,,E02005084,E01024366,32.0,
|
||||
118632,886,Kent,3084,Wateringbury Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,175.0,74.0,101.0,16.6,Not applicable,,Not applicable,,Not under a federation,,10078476.0,,Not applicable,08-03-2023,30-11-2023,147 Bow Road,Wateringbury,,Maidstone,Kent,ME18 5EA,www.wateringbury.kent.sch.uk/,1622812199.0,Miss,Debbie,Johnson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"East and West Peckham, Mereworth & Wateringbury",Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,569089.0,152864.0,Tonbridge and Malling 007,Tonbridge and Malling 007D,,,,,Requires improvement,South-East England and South London,,10014312449.0,,Not applicable,Not applicable,,,E02005155,E01024781,29.0,
|
||||
118634,886,Kent,3088,"Wouldham, All Saints Church of England Voluntary Controlled Primary School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Non-selective,420.0,No Special Classes,19-01-2023,401.0,196.0,205.0,13.7,Not applicable,,Not applicable,,Not under a federation,,10075829.0,,Not applicable,26-04-2023,10-10-2023,1 Worrall Drive,Wouldham,,Rochester,,ME1 3GE,http://www.wouldham.kent.sch.uk,1634861434.0,Mrs,Victoria,Baldwin,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford North & North Downs,Chatham and Aylesford,(England/Wales) Rural hamlet and isolated dwellings,E10000016,571413.0,163394.0,Tonbridge and Malling 001,Tonbridge and Malling 001H,,,,,Good,South-East England and South London,United Kingdom,10092972751.0,,Not applicable,Not applicable,,,E02005149,E01035003,55.0,
|
||||
118635,886,Kent,3089,St George's Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,203.0,103.0,100.0,18.7,Not applicable,,Not applicable,,Not under a federation,,10078871.0,,Not applicable,14-12-2022,03-06-2024,Old London Road,Wrotham,,Sevenoaks,Kent,TN15 7DL,http://www.st-georges-wrotham.kent.sch.uk/,1732882401.0,Mrs,Elizabeth,Rye,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tonbridge and Malling,Pilgrims with Ightham,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,560941.0,159482.0,Tonbridge and Malling 006,Tonbridge and Malling 006F,,,,,Good,South-East England and South London,,200000964122.0,,Not applicable,Not applicable,,,E02005154,E01024786,38.0,
|
||||
118636,886,Kent,3090,"St Margaret's, Collier Street Church of England Voluntary Controlled School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,120.0,No Special Classes,19-01-2023,123.0,58.0,65.0,4.1,Not applicable,,Not applicable,,Not under a federation,,10069111.0,,Not applicable,04-05-2022,07-05-2024,Collier Street,Marden,,Tonbridge,Kent,TN12 9RR,www.collier-street.kent.sch.uk/,1892730264.0,Mr,Paul,Ryan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Marden and Yalding,Maidstone and The Weald,(England/Wales) Rural hamlet and isolated dwellings,E10000016,571630.0,146064.0,Maidstone 018,Maidstone 018C,,,,,Good,South-East England and South London,,200003662059.0,,Not applicable,Not applicable,,,E02005085,E01024380,5.0,
|
||||
118637,886,Kent,3091,Laddingford St Mary's Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,101.0,No Special Classes,19-01-2023,81.0,37.0,44.0,25.0,Not applicable,,Not applicable,,Not under a federation,,10069110.0,,Not applicable,08-03-2023,30-04-2024,Darman Lane,Laddingford,,Maidstone,Kent,ME18 6BL,http://www.laddingford.kent.sch.uk,1622871270.0,Mrs,Lucy,Clark,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,Marden and Yalding,Maidstone and The Weald,(England/Wales) Rural hamlet and isolated dwellings,E10000016,568865.0,147811.0,Maidstone 018,Maidstone 018B,,,,,Good,South-East England and South London,,200003718886.0,,Not applicable,Not applicable,,,E02005085,E01024379,20.0,
|
||||
118638,886,Kent,3092,"Yalding, St Peter and St Paul Church of England Voluntary Controlled Primary School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,168.0,No Special Classes,19-01-2023,173.0,87.0,86.0,10.4,Not applicable,,Not applicable,,Not under a federation,,10078475.0,,Not applicable,30-01-2019,05-06-2024,Vicarage Road,Yalding,,Maidstone,Kent,ME18 6DP,http://www.yalding.kent.sch.uk,1622814298.0,Mrs,Sarah,Friend,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Marden and Yalding,Maidstone and The Weald,(England/Wales) Rural town and fringe,E10000016,570095.0,150173.0,Maidstone 014,Maidstone 014D,,,,,Good,South-East England and South London,,200003718898.0,,Not applicable,Not applicable,,,E02005081,E01024378,18.0,
|
||||
118646,886,Kent,3108,Ospringe Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,213.0,113.0,100.0,30.5,Not applicable,,Not applicable,,Not under a federation,,10069108.0,,Not applicable,07-06-2023,16-04-2024,Water Lane,Ospringe,,Faversham,Kent,ME13 8TX,www.ospringeprimary.co.uk/,1795532004.0,Mrs,Amanda,Ralph,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Watling,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,600216.0,160694.0,Swale 014,Swale 014F,,,,,Good,South-East England and South London,,100062380290.0,,Not applicable,Not applicable,,,E02005128,E01024627,65.0,
|
||||
118647,886,Kent,3109,Hernhill Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,212.0,105.0,107.0,10.4,Not applicable,,Not applicable,,Not under a federation,,10078474.0,,Not applicable,07-03-2024,22-05-2024,Fostall,Hernhill,,Faversham,Kent,ME13 9JG,www.hernhill.kent.sch.uk,1227751322.0,Mrs,Sarah,Alexander,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Boughton and Courtenay,Faversham and Mid Kent,(England/Wales) Rural hamlet and isolated dwellings,E10000016,606692.0,161491.0,Swale 017,Swale 017B,,,,,Outstanding,South-East England and South London,,10023196116.0,,Not applicable,Not applicable,,,E02005131,E01024556,22.0,
|
||||
118649,886,Kent,3111,Newington Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,236.0,No Special Classes,19-01-2023,252.0,139.0,113.0,33.3,Not applicable,,Not applicable,,Supported by a federation,The Federation of Lower Halstow Primary and Newington CofE Primary School,10069107.0,,Not applicable,15-05-2019,19-04-2024,School Lane,Newington,,Sittingbourne,Kent,ME9 7LB,www.newington.kent.sch.uk,1795842300.0,Mrs,Tara,Deevoy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,"Hartlip, Newington and Upchurch",Sittingbourne and Sheppey,(England/Wales) Rural town and fringe,E10000016,585949.0,165334.0,Swale 008,Swale 008B,,,,,Good,South-East England and South London,,100062626974.0,,Not applicable,Not applicable,,,E02005122,E01024571,84.0,
|
||||
118651,886,Kent,3117,Teynham Parochial Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,202.0,105.0,97.0,31.2,Not applicable,,Not applicable,,Not under a federation,,10078473.0,,Not applicable,29-03-2023,23-05-2024,Station Road,Teynham,,Sittingbourne,Kent,ME9 9BQ,www.teynham.kent.sch.uk,1795521217.0,Mrs,Elizabeth,Pearson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Teynham and Lynsted,Sittingbourne and Sheppey,(England/Wales) Rural town and fringe,E10000016,595417.0,162735.0,Swale 016,Swale 016D,,,,,Requires improvement,South-East England and South London,,100062627014.0,,Not applicable,Not applicable,,,E02005130,E01024623,63.0,
|
||||
118653,886,Kent,3120,Barham Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,210.0,102.0,108.0,12.4,Not applicable,,Not applicable,,Not under a federation,,10069106.0,,Not applicable,25-01-2023,15-04-2024,Valley Road,Barham,Barham C of E Primary School,CANTERBURY,Kent,CT4 6NX,www.barham.kent.sch.uk/,1227831312.0,Mrs,Alison Higgins,Jo Duhig,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Nailbourne,Canterbury,(England/Wales) Rural village,E10000016,620612.0,150003.0,Canterbury 018,Canterbury 018B,,,,,Good,South-East England and South London,,100062299377.0,,Not applicable,Not applicable,,,E02005027,E01024043,26.0,
|
||||
118654,886,Kent,3122,Bridge and Patrixbourne Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,407.0,199.0,208.0,3.4,Not applicable,,Not applicable,,Not under a federation,,10078472.0,,Not applicable,04-10-2023,18-04-2024,Conyngham Lane,Bridge,,Canterbury,Kent,CT4 5JX,www.bridge.kent.sch.uk,1227830276.0,Mr,James,Tibbles,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Nailbourne,Canterbury,(England/Wales) Rural town and fringe,E10000016,618239.0,154550.0,Canterbury 018,Canterbury 018C,,,,,Good,South-East England and South London,,100062298950.0,,Not applicable,Not applicable,,,E02005027,E01024088,14.0,
|
||||
118655,886,Kent,3123,Chislet Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,95.0,50.0,45.0,12.6,Not applicable,,Not applicable,,Supported by a federation,The Federation of Chislet CE and Hoath Primary Schools,10078870.0,,Not applicable,24-11-2022,18-04-2024,Church Lane,Chislet,,Canterbury,Kent,CT3 4DU,www.chislethoathfederation.co.uk,1227860295.0,Mr,Tim,Whitehouse,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Canterbury,Reculver,North Thanet,(England/Wales) Rural village,E10000016,622377.0,164226.0,Canterbury 010,Canterbury 010D,,,,,Good,South-East England and South London,,10033152283.0,,Not applicable,Not applicable,,,E02005019,E01024087,12.0,
|
||||
118657,886,Kent,3126,Littlebourne Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,99.0,47.0,52.0,26.3,Not applicable,,Not applicable,,Not under a federation,,10069105.0,,Not applicable,23-05-2019,30-05-2024,Church Road,Littlebourne,,Canterbury,Kent,CT3 1XS,http://www.littlebourne.kent.sch.uk,1227721671.0,Mrs,Samantha,Killick,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Little Stour & Adisham,Canterbury,(England/Wales) Rural town and fringe,E10000016,620956.0,157780.0,Canterbury 010,Canterbury 010A,,,,,Good,South-East England and South London,,200000681653.0,,Not applicable,Not applicable,,,E02005019,E01024084,26.0,
|
||||
118659,886,Kent,3129,St Alphege Church of England Infant School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,232.0,No Special Classes,19-01-2023,206.0,105.0,101.0,16.3,Not applicable,,Not applicable,,Not under a federation,,10079668.0,,Not applicable,03-02-2023,03-06-2024,Oxford Street,,,Whitstable,Kent,CT5 1DA,http://www.st-alphege.kent.sch.uk,1227272977.0,Mrs,Liz,Thomas-Friend,Executive Head,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Gorrell,Canterbury,(England/Wales) Urban city and town,E10000016,610681.0,166250.0,Canterbury 008,Canterbury 008C,,,,,Good,South-East England and South London,,100062300537.0,,Not applicable,Not applicable,,,E02005017,E01024070,30.0,
|
||||
118660,886,Kent,3130,Wickhambreaux Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,115.0,No Special Classes,19-01-2023,118.0,55.0,63.0,11.0,Not applicable,,Not applicable,,Not under a federation,,10065385.0,,Not applicable,26-02-2015,20-05-2024,The Street,Wickhambreaux,,Canterbury,Kent,CT3 1RN,www.wickhambreaux-school.ik.org/,1227721300.0,Mrs,Lisa,Crosbie,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Little Stour & Adisham,Canterbury,(England/Wales) Rural hamlet and isolated dwellings,E10000016,622169.0,158684.0,Canterbury 010,Canterbury 010B,,,,,Outstanding,South-East England and South London,,100062298259.0,,Not applicable,Not applicable,,,E02005019,E01024085,13.0,
|
||||
118663,886,Kent,3136,Brabourne Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,104.0,49.0,55.0,11.5,Not applicable,,Not applicable,,Not under a federation,,10078471.0,,Not applicable,19-06-2018,06-06-2024,School Lane,Brabourne,,Ashford,Kent,TN25 5LQ,www.brabourne.kent.sch.uk,1303813276.0,Mr,Andrew,Stapley,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Bircholt,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,609183.0,141706.0,Ashford 010,Ashford 010C,,,,,Good,South-East England and South London,,100062561998.0,,Not applicable,Not applicable,,,E02005005,E01024015,12.0,
|
||||
118664,886,Kent,3137,Brookland Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,87.0,43.0,44.0,16.1,Not applicable,,Not applicable,,Not under a federation,,10078470.0,,Not applicable,03-02-2023,14-05-2024,High Street,Brookland,,Romney Marsh,Kent,TN29 9QR,http://www.brookland.kent.sch.uk,1797344317.0,Mr,Martin,Hacker,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Walland & Denge Marsh,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,599003.0,125887.0,Folkestone and Hythe 011,Folkestone and Hythe 011D,,,,,Good,South-East England and South London,,50000252.0,,Not applicable,Not applicable,,,E02005112,E01024548,14.0,
|
||||
118665,886,Kent,3138,"Chilham, St Mary's Church of England Primary School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,79.0,36.0,43.0,24.1,Not applicable,,Not applicable,,Not under a federation,,10069103.0,,Not applicable,02-02-2022,08-05-2024,School Hill,Chilham,,Canterbury,Kent,CT4 8DE,www.chilham.kent.sch.uk,1227730442.0,Mrs,Delia,Cooper,Interim Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Downs North,Ashford,(England/Wales) Rural village,E10000016,606875.0,153533.0,Ashford 001,Ashford 001B,,,,,Good,South-East England and South London,,10012840696.0,,Not applicable,Not applicable,,,E02004996,E01023987,19.0,
|
||||
118666,886,Kent,3139,High Halden Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,102.0,45.0,57.0,17.6,Not applicable,,Not applicable,,Supported by a federation,Flourish Together Federation,10069102.0,,Not applicable,24-02-2022,19-04-2024,Church Hill,High Halden,,Ashford,Kent,TN26 3JB,http://www.high-halden.kent.sch.uk/,1233850285.0,Mrs,Kelly,Burlton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Weald Central,Ashford,(England/Wales) Rural village,E10000016,590169.0,137144.0,Ashford 011,Ashford 011C,,,,,Good,South-East England and South London,,100062617661.0,,Not applicable,Not applicable,,,E02005006,E01024032,18.0,
|
||||
118672,886,Kent,3145,Woodchurch Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,182.0,No Special Classes,19-01-2023,157.0,88.0,69.0,26.1,Not applicable,,Not applicable,,Supported by a federation,Flourish Together Federation,10069101.0,,Not applicable,17-05-2023,07-05-2024,Bethersden Road,Woodchurch,,Ashford,Kent,TN26 3QJ,http://www.woodchurch.kent.sch.uk,1233860232.0,Mrs,Kelly,Burlton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Weald South,Ashford,(England/Wales) Rural village,E10000016,594165.0,134993.0,Ashford 012,Ashford 012C,,,,,Good,South-East England and South London,,10012848908.0,,Not applicable,Not applicable,,,E02005007,E01024038,41.0,
|
||||
118673,886,Kent,3146,Bodsham Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,103.0,No Special Classes,19-01-2023,82.0,43.0,39.0,6.1,Not applicable,,Not applicable,,Supported by a federation,The Federation of Bodsham Church of England Primary School and Saltwood Church of England Primary School,10069100.0,,Not applicable,26-05-2022,23-04-2024,School Hill,Bodsham,,Ashford,Kent,TN25 5JQ,www.bodsham.kent.sch.uk,1233750374.0,Mr,Paul,Newton,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Folkestone and Hythe,North Downs West,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,611020.0,145724.0,Folkestone and Hythe 001,Folkestone and Hythe 001C,,,,,Good,South-East England and South London,,50010714.0,,Not applicable,Not applicable,,,E02005102,E01024545,5.0,
|
||||
118675,886,Kent,3149,"Folkestone, St Martin's Church of England Primary School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,202.0,90.0,112.0,18.3,Not applicable,,Not applicable,,Supported by a federation,The Federation of St Martin's and Seabrook CEP Schools,10069099.0,,Not applicable,24-04-2015,17-04-2024,Horn Street,,,Folkestone,Kent,CT20 3JJ,http://www.stmartinsfolkestone.com,1303238888.0,Mrs,Elizabeth,Carter,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,618951.0,136429.0,Folkestone and Hythe 005,Folkestone and Hythe 005C,,,,,Outstanding,South-East England and South London,,50023003.0,,Not applicable,Not applicable,,,E02005106,E01024494,37.0,
|
||||
118676,886,Kent,3150,"Folkestone, St Peter's Church of England Primary School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,104.0,47.0,57.0,43.3,Not applicable,,Not applicable,,Not under a federation,,10078469.0,,Not applicable,26-06-2019,13-05-2024,The Durlocks,,,Folkestone,Kent,CT19 6AL,www.stpetersfolkestone.com/,1303255400.0,Mrs,Toni,Browne,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Folkestone Harbour,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,623352.0,136165.0,Folkestone and Hythe 014,Folkestone and Hythe 014A,,,,,Good,South-East England and South London,,,,Not applicable,Not applicable,,,E02006879,E01024504,45.0,
|
||||
118678,886,Kent,3153,Seabrook Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,104.0,45.0,59.0,13.5,Not applicable,,Not applicable,,Supported by a federation,The Federation of St Martin's and Seabrook CEP Schools,10078468.0,,Not applicable,08-11-2023,17-04-2024,Seabrook Road,,,Hythe,Kent,CT21 5RL,www.seabrookprimaryschool.co.uk/,1303238429.0,Mrs,Elizabeth,Carter,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Hythe,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,618674.0,134958.0,Folkestone and Hythe 005,Folkestone and Hythe 005F,,,,,Good,South-East England and South London,,50022207.0,,Not applicable,Not applicable,,,E02005106,E01024528,14.0,
|
||||
118679,886,Kent,3154,Lyminge Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,196.0,97.0,99.0,8.7,Not applicable,,Not applicable,,Not under a federation,,10069098.0,,Not applicable,09-11-2023,04-06-2024,Church Road,Lyminge,,Folkestone,Kent,CT18 8JA,www.lymingeprimaryschool.co.uk/,1303862367.0,Mr,Matt,Day,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs West,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,616220.0,141085.0,Folkestone and Hythe 001,Folkestone and Hythe 001D,,,,,Good,South-East England and South London,,50021153.0,,Not applicable,Not applicable,,,E02005102,E01024547,17.0,
|
||||
118680,886,Kent,3155,Lympne Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,203.0,104.0,99.0,6.4,Not applicable,,Not applicable,,Not under a federation,,10070327.0,,Not applicable,14-10-2021,24-05-2024,Octavian Drive,Lympne,,Hythe,Kent,CT21 4JG,www.lympne.kent.sch.uk,1303268041.0,Mrs,Melanie,Nash,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Hythe Rural,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,612226.0,135150.0,Folkestone and Hythe 009,Folkestone and Hythe 009C,,,,,Good,South-East England and South London,,50012208.0,,Not applicable,Not applicable,,,E02005110,E01024536,13.0,
|
||||
118681,886,Kent,3158,Stelling Minnis Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,83.0,48.0,35.0,18.1,Not applicable,,Not applicable,,Not under a federation,,10078467.0,,Not applicable,09-06-2022,03-06-2024,Bossingham Road,Stelling Minnis,,Canterbury,Kent,CT4 6DU,www.stelling-minnis.kent.sch.uk,1227709218.0,Mrs,Julie,Simmons,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs West,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,614898.0,148564.0,Folkestone and Hythe 001,Folkestone and Hythe 001A,,,,,Good,South-East England and South London,,50014408.0,,Not applicable,Not applicable,,,E02005102,E01024490,15.0,
|
||||
118682,886,Kent,3159,Stowting Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,103.0,52.0,51.0,7.8,Not applicable,,Not applicable,,Not under a federation,,10069097.0,,Not applicable,13-11-2019,20-03-2024,Stowting,Stowting Hill,,Ashford,Kent,TN25 6BE,http://www.stowting.kent.sch.uk,1303862375.0,Mrs,Sarah,Uden,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs West,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,612365.0,142429.0,Folkestone and Hythe 001,Folkestone and Hythe 001C,,,,,Good,South-East England and South London,,50012509.0,,Not applicable,Not applicable,,,E02005102,E01024545,8.0,
|
||||
118683,886,Kent,3160,Selsted Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,99.0,54.0,45.0,9.1,Not applicable,,Not applicable,,Not under a federation,,10069096.0,,Not applicable,02-11-2022,15-05-2024,Selsted,"Wootton Lane, Selsted",Wootton Lane,Dover,Kent,CT15 7HH,www.selstedschool.org/,1303844286.0,Mrs,Angela,Woodgate,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs East,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,622244.0,144587.0,Folkestone and Hythe 001,Folkestone and Hythe 001B,,,,,Good,South-East England and South London,,50044106.0,,Not applicable,Not applicable,,,E02005102,E01024544,9.0,
|
||||
118685,886,Kent,3167,Eastry Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,218.0,No Special Classes,19-01-2023,165.0,87.0,78.0,26.8,Not applicable,,Not applicable,,Not under a federation,,10069095.0,,Not applicable,14-06-2023,08-05-2024,Cooks Lea,Eastry,,Sandwich,Kent,CT13 0LR,www.eastry.kent.sch.uk/,1304611360.0,Mrs,Sarah,Moss,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dover,Eastry Rural,Dover,(England/Wales) Rural town and fringe,E10000016,630665.0,154869.0,Dover 002,Dover 002A,,,,,Good,South-East England and South London,,100062284627.0,,Not applicable,Not applicable,,,E02005042,E01024202,44.0,
|
||||
118686,886,Kent,3168,Goodnestone Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,70.0,No Special Classes,19-01-2023,53.0,21.0,32.0,9.4,Not applicable,,Not applicable,,Not under a federation,,10069094.0,,Not applicable,17-01-2019,14-05-2024,The Street,Goodnestone,,Canterbury,Kent,CT3 1PQ,www.goodnestone.kent.sch.uk/,1304840329.0,Mrs,Victoria,Solly,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Little Stour & Ashstone,South Thanet,(England/Wales) Rural village,E10000016,625516.0,154670.0,Dover 001,Dover 001B,,,,,Good,South-East England and South London,,100062298258.0,,Not applicable,Not applicable,,,E02005041,E01024207,5.0,
|
||||
118687,886,Kent,3169,Guston Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Non-selective,180.0,No Special Classes,19-01-2023,151.0,81.0,70.0,9.3,Not applicable,,Not applicable,,Not under a federation,,10075828.0,,Not applicable,21-10-2021,16-04-2024,Burgoyne Heights,Guston,,Dover,Kent,CT15 5LR,www.guston.kent.sch.uk/,1304206847.0,Mrs,Deby,Day,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Guston, Kingsdown & St Margaret's-at-Cliffe",Dover,(England/Wales) Rural village,E10000016,632307.0,143088.0,Dover 012,Dover 012B,,,,,Good,South-East England and South London,,100062620447.0,,Not applicable,Not applicable,,,E02005052,E01024238,14.0,
|
||||
118688,886,Kent,3171,Nonington Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,86.0,No Special Classes,19-01-2023,38.0,19.0,19.0,57.9,Not applicable,,Not applicable,,Not under a federation,,10078869.0,,Not applicable,21-04-2022,14-05-2024,Church Street,Nonington,,Dover,Kent,CT15 4LB,www.noningtonprimary.co.uk/,1304840348.0,Mrs,Victoria,Solly,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SEMH - Social, Emotional and Mental Health",,,,,,,,,,,,,Resourced provision,,8.0,,,South East,Dover,"Aylesham, Eythorne & Shepherdswell",Dover,(England/Wales) Rural hamlet and isolated dwellings,E10000016,625273.0,152175.0,Dover 006,Dover 006A,,,,,Requires improvement,South-East England and South London,,100062287271.0,,Not applicable,Not applicable,,,E02005046,E01024190,22.0,
|
||||
118691,886,Kent,3175,Shepherdswell Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,202.0,104.0,98.0,11.4,Not applicable,,Not applicable,,Supported by a federation,The Federation of Shepherdswell Church of England and Eythorne Elvington,10069093.0,,Not applicable,20-10-2021,02-05-2024,Coldred Road,Shepherdswell,,DOVER,Kent,CT15 7LH,www.shepherdswell.kent.sch.uk/,1304830312.0,Mr,Mark,Lamb,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Aylesham, Eythorne & Shepherdswell",Dover,(England/Wales) Rural town and fringe,E10000016,626077.0,147755.0,Dover 006,Dover 006D,,,,,Good,South-East England and South London,United Kingdom,100062288370.0,,Not applicable,Not applicable,,,E02005046,E01024204,23.0,
|
||||
118693,886,Kent,3178,Birchington Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,570.0,No Special Classes,19-01-2023,475.0,235.0,240.0,25.3,Not applicable,,Not applicable,,Not under a federation,,10078464.0,,Not applicable,25-09-2019,05-06-2024,Park Lane,,,Birchington,Kent,CT7 0AS,www.birchington-primary.com/,1843841046.0,Ms,Kathleen,Barham,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Birchington South,North Thanet,(England/Wales) Urban city and town,E10000016,630372.0,168670.0,Thanet 007,Thanet 007A,,,,,Good,South-East England and South London,,100062303819.0,,Not applicable,Not applicable,,,E02005138,E01024641,120.0,
|
||||
118694,886,Kent,3179,"Margate, Holy Trinity and St John's Church of England Primary School",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,407.0,208.0,199.0,55.3,Not applicable,,Not applicable,,Not under a federation,,10069092.0,,Not applicable,29-03-2023,21-05-2024,St John's Road,,,Margate,Kent,CT9 1LU,www.holytrinitymargate.co.uk/,1843223237.0,Mr,Rob,Garratt,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,16.0,16.0,,,South East,Thanet,Margate Central,North Thanet,(England/Wales) Urban city and town,E10000016,635618.0,170614.0,Thanet 001,Thanet 001G,,,,,Good,South-East England and South London,,200003082079.0,,Not applicable,Not applicable,,,E02005132,E01035317,225.0,
|
||||
118695,886,Kent,3181,St Saviour's Church of England Junior School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,384.0,Not applicable,19-01-2023,375.0,196.0,179.0,30.9,Not applicable,,Not applicable,,Not under a federation,,10078463.0,,Not applicable,18-10-2023,13-12-2023,Elm Grove,,St. Saviour's C.E Junior School Elm Grove,Westgate on Sea,Kent,CT8 8LD,www.stsavioursjunior.com,1843831707.0,Mr,Nick,Bonell,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Westgate-on-Sea,North Thanet,(England/Wales) Urban city and town,E10000016,632054.0,169794.0,Thanet 007,Thanet 007C,,,,,Good,South-East England and South London,,100062304387.0,,Not applicable,Not applicable,,,E02005138,E01024714,116.0,
|
||||
118696,886,Kent,3182,Minster Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,389.0,212.0,177.0,22.1,Not applicable,,Not applicable,,Not under a federation,,10069091.0,,Not applicable,18-01-2023,23-04-2024,Molineux Road,Minster-in-Thanet,,Ramsgate,Kent,CT12 4PS,http://www.minster-ramsgate.kent.sch.uk/,1843821384.0,Mr,Paul,McCarthy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Thanet Villages,North Thanet,(England/Wales) Rural town and fringe,E10000016,630821.0,164492.0,Thanet 014,Thanet 014B,,,,,Good,South-East England and South London,,200001487980.0,,Not applicable,Not applicable,,,E02005145,E01024702,86.0,
|
||||
118697,886,Kent,3183,Monkton Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,106.0,49.0,57.0,23.6,Not applicable,,Not applicable,,Not under a federation,,10069090.0,,Not applicable,30-01-2024,20-05-2024,Monkton Street,Monkton,,Ramsgate,Kent,CT12 4JQ,http://www.monkton.kent.sch.uk,1843821394.0,Mr,Paul,McCarthy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Thanet Villages,North Thanet,(England/Wales) Rural village,E10000016,628701.0,165070.0,Thanet 014,Thanet 014C,,,,,Good,South-East England and South London,,10022964433.0,,Not applicable,Not applicable,,,E02005145,E01024703,25.0,
|
||||
118698,886,Kent,3186,St Nicholas At Wade Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,195.0,86.0,109.0,15.9,Not applicable,,Not applicable,,Not under a federation,,10078462.0,,Not applicable,02-10-2019,21-05-2024,Down Barton Road,St Nicholas-At-Wade,,Birchington,Kent,CT7 0PY,www.st-nicholas-birchington.kent.sch.uk/,1843847253.0,Mrs,Taralee,Kennedy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Thanet Villages,North Thanet,(England/Wales) Rural village,E10000016,626348.0,166666.0,Thanet 014,Thanet 014C,,,,,Good,South-East England and South London,,100062303823.0,,Not applicable,Not applicable,,,E02005145,E01024703,31.0,
|
||||
118701,886,Kent,3198,Frittenden Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,98.0,44.0,54.0,23.5,Not applicable,,Not applicable,,Not under a federation,,10069089.0,,Not applicable,24-11-2022,26-04-2024,Frittenden Primary School,,Frittenden,Cranbrook,Kent,TN17 2DD,http://www.frittenden.kent.sch.uk,1580852250.0,Ms,nichola,costello,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Frittenden and Sissinghurst,Maidstone and The Weald,(England/Wales) Rural village,E10000016,581389.0,140963.0,Tunbridge Wells 013,Tunbridge Wells 013E,,,,,Requires improvement,South-East England and South London,,10008667029.0,,Not applicable,Not applicable,,,E02005174,E01024803,23.0,
|
||||
118702,886,Kent,3199,Egerton Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,188.0,103.0,85.0,20.7,Not applicable,,Not applicable,,Not under a federation,,10069088.0,,Not applicable,12-10-2023,16-04-2024,Stisted Way,Egerton,,Ashford,Kent,TN27 9DR,http://www.egerton.kent.sch.uk,1233756274.0,Mrs,Julia,Walker,Interim Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Weald North,Ashford,(England/Wales) Rural village,E10000016,590598.0,147325.0,Ashford 002,Ashford 002F,,,,,Good,South-East England and South London,,100062563941.0,,Not applicable,Not applicable,,,E02004997,E01024035,39.0,
|
||||
118704,886,Kent,3201,St Lawrence Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,84.0,No Special Classes,19-01-2023,76.0,35.0,41.0,3.9,Not applicable,,Not applicable,,Not under a federation,,10078461.0,,Not applicable,28-09-2022,05-02-2024,Church Road,Stone Street,,Sevenoaks,Kent,TN15 0LN,http://www.st-lawrence-sevenoaks.kent.sch.uk,1732761393.0,Mr,Daniel,Eaton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Seal and Weald,Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,557394.0,154844.0,Sevenoaks 012,Sevenoaks 012A,,,,,Good,South-East England and South London,,10035185576.0,,Not applicable,Not applicable,,,E02005098,E01024458,3.0,
|
||||
118705,886,Kent,3282,Boughton-under-Blean and Dunkirk Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Methodist,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,203.0,100.0,103.0,24.1,Not applicable,,Not applicable,,Not under a federation,,10078141.0,,Not applicable,11-07-2019,03-06-2024,School Lane,Boughton-under-Blean,,Faversham,Kent,ME13 9AW,www.bad.kent.sch.uk,1227751431.0,Mr,Simon,Way,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Boughton and Courtenay,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,605832.0,159483.0,Swale 017,Swale 017C,,,,,Good,South-East England and South London,,200002540885.0,,Not applicable,Not applicable,,,E02005131,E01024557,49.0,
|
||||
118706,886,Kent,3284,Lady Joanna Thornhill Endowed Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,418.0,198.0,220.0,11.2,Not applicable,,Not applicable,,Not under a federation,,10078140.0,,Not applicable,05-02-2015,18-04-2024,Bridge Street,Wye,,Ashford,Kent,TN25 5EA,www.ladyj.kent.sch.uk/,1233812781.0,Mrs,Rachael,Foster,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Wye with Hinxhill,Ashford,(England/Wales) Rural town and fringe,E10000016,605082.0,146710.0,Ashford 001,Ashford 001D,,,,,Outstanding,South-East England and South London,,10012868707.0,,Not applicable,Not applicable,,,E02004996,E01024040,47.0,
|
||||
118707,886,Kent,3289,St Peter's Methodist Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Methodist,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,209.0,108.0,101.0,21.5,Not applicable,,Not applicable,,Not under a federation,,10078139.0,,Not applicable,12-12-2018,01-05-2024,St Peter's Grove,,,Canterbury,Kent,CT1 2DH,http://www.st-peters-canterbury.kent.sch.uk,1227464392.0,Mrs,Kristina,Dyer,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Westgate,Canterbury,(England/Wales) Urban city and town,E10000016,614679.0,157910.0,Canterbury 020,Canterbury 020F,,,,,Good,South-East England and South London,,200000676740.0,,Not applicable,Not applicable,,,E02006856,E01032807,45.0,
|
||||
118709,886,Kent,3294,St Matthew's High Brooms Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,420.0,No Special Classes,19-01-2023,354.0,178.0,176.0,43.2,Not applicable,,Not applicable,,Not under a federation,,10069087.0,,Not applicable,19-07-2018,15-04-2024,Powder Mill Lane,High Brooms,,Tunbridge Wells,Kent,TN4 9DY,www.st-matthews-school.org,1892528098.0,Mrs,Claire,Harris,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Southborough and High Brooms,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558927.0,141801.0,Tunbridge Wells 003,Tunbridge Wells 003D,,,,,Good,South-East England and South London,,100062586205.0,,Not applicable,Not applicable,,,E02005164,E01024847,153.0,
|
||||
118710,886,Kent,3295,Herne Church of England Infant and Nursery School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,270.0,No Special Classes,19-01-2023,316.0,171.0,145.0,7.8,Not applicable,,Not applicable,,Not under a federation,,10079667.0,,Not applicable,29-09-2021,21-05-2024,Palmer Close,Herne,,Herne Bay,Kent,CT6 7AH,www.herne-infant.kent.sch.uk/,1227740793.0,Mrs,E,Thomas-Friend,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Herne & Broomfield,North Thanet,(England/Wales) Urban city and town,E10000016,618672.0,165903.0,Canterbury 006,Canterbury 006C,,,,,Outstanding,South-East England and South London,,200000680285.0,,Not applicable,Not applicable,,,E02005015,E01024075,21.0,
|
||||
118711,886,Kent,3296,Langafel Church of England Voluntary Controlled Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,331.0,No Special Classes,19-01-2023,325.0,175.0,150.0,32.0,Not applicable,,Not applicable,,Not under a federation,,10078868.0,,Not applicable,03-10-2018,26-02-2024,Main Road,,,Longfield,Kent,DA3 7PW,www.langafel.kent.sch.uk/,1474703398.0,Mrs,Catherine,Maynard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,30.0,29.0,,,South East,Dartford,"Longfield, New Barn & Southfleet",Dartford,(England/Wales) Urban city and town,E10000016,561395.0,168619.0,Dartford 013,Dartford 013D,,,,,Good,South-East England and South London,,200000538032.0,,Not applicable,Not applicable,,,E02005040,E01024160,104.0,
|
||||
118712,886,Kent,3297,Southborough CofE Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,630.0,No Special Classes,19-01-2023,615.0,309.0,306.0,17.6,Not applicable,,Not applicable,,Not under a federation,,10074251.0,,Not applicable,21-06-2018,30-04-2024,Broomhill Park Road,Southborough,,Tunbridge Wells,Kent,TN4 0JY,http://www.southborough.kent.sch.uk,1892529682.0,Mrs,Emma,Savage,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tunbridge Wells,Southborough and High Brooms,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,557592.0,141923.0,Tunbridge Wells 002,Tunbridge Wells 002C,,,,,Good,South-East England and South London,,100062585335.0,,Not applicable,Not applicable,,,E02005163,E01024846,108.0,
|
||||
118713,886,Kent,3303,St Katharine's Knockholt Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,201.0,No Special Classes,19-01-2023,170.0,80.0,90.0,4.7,Not applicable,,Not applicable,,Not under a federation,,10069086.0,,Not applicable,16-11-2022,12-09-2023,Main Road,Knockholt,,Sevenoaks,Kent,TN14 7LS,www.knockholt.kent.sch.uk/,1959532237.0,Miss,Sarah-Jane,Tormey,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Halstead, Knockholt and Badgers Mount",Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,546750.0,158787.0,Sevenoaks 008,Sevenoaks 008D,,,,,Good,South-East England and South London,,100061014242.0,,Not applicable,Not applicable,,,E02005094,E01024440,8.0,
|
||||
118715,886,Kent,3307,"Chevening, St Botolph's Church of England Voluntary Aided Primary School",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,179.0,87.0,92.0,2.8,Not applicable,,Not applicable,,Not under a federation,,10078460.0,,Not applicable,27-11-2019,18-04-2024,Chevening Road,Chipstead,,Sevenoaks,Kent,TN13 2SA,http://www.chevening.kent.sch.uk,1732452895.0,Miss,Karen,Minnis,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Brasted, Chevening and Sundridge",Sevenoaks,(England/Wales) Urban city and town,E10000016,549789.0,156509.0,Sevenoaks 011,Sevenoaks 011A,,,,,Good,South-East England and South London,,10013771396.0,,Not applicable,Not applicable,,,E02005097,E01024416,5.0,
|
||||
118716,886,Kent,3308,Colliers Green Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,112.0,No Special Classes,19-01-2023,112.0,52.0,60.0,4.5,Not applicable,,Not applicable,,Not under a federation,,10069085.0,,Not applicable,07-03-2019,11-01-2024,Colliers Green,,,Cranbrook,Kent,TN17 2LR,www.colliers-green.kent.sch.uk/,1580211335.0,Dr,Josephine,Hopkins,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Goudhurst and Lamberhurst,Maidstone and The Weald,(England/Wales) Rural hamlet and isolated dwellings,E10000016,575881.0,138768.0,Tunbridge Wells 011,Tunbridge Wells 011E,,,,,Good,South-East England and South London,,10000064641.0,,Not applicable,Not applicable,,,E02005172,E01024806,5.0,
|
||||
118717,886,Kent,3309,Sissinghurst Voluntary Aided Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,175.0,No Special Classes,19-01-2023,171.0,88.0,83.0,16.4,Not applicable,,Not applicable,,Not under a federation,,10073480.0,,Not applicable,01-03-2023,03-06-2024,Common Road,Sissinghurst,,Cranbrook,Kent,TN17 2BH,www.sissinghurst.kent.sch.uk/,1580713895.0,Mrs,Sarah,Holman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tunbridge Wells,Frittenden and Sissinghurst,Maidstone and The Weald,(England/Wales) Rural village,E10000016,578960.0,137839.0,Tunbridge Wells 013,Tunbridge Wells 013E,,,,,Requires improvement,South-East England and South London,,10000070036.0,,Not applicable,Not applicable,,,E02005174,E01024803,28.0,
|
||||
118718,886,Kent,3312,Hever Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,205.0,No Special Classes,19-01-2023,152.0,66.0,86.0,17.1,Not applicable,,Not applicable,,Not under a federation,,10069084.0,,Not applicable,23-03-2022,29-04-2024,Hever Road,Hever,,Edenbridge,Kent,TN8 7NH,www.hever.kent.sch.uk/,1732862304.0,Mrs,Helene,Bligh,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Cowden and Hever,Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,547634.0,144758.0,Sevenoaks 015,Sevenoaks 015A,,,,,Requires improvement,South-East England and South London,,100062593515.0,,Not applicable,Not applicable,,,E02005101,E01024420,26.0,
|
||||
118720,886,Kent,3314,Penshurst Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,84.0,46.0,38.0,13.1,Not applicable,,Not applicable,,Not under a federation,,10069082.0,,Not applicable,17-11-2022,04-06-2024,High Street,Penshurst,,Tonbridge,Kent,TN11 8BX,www.penshurstschool.org.uk,1892870446.0,Mrs,Susan,Elliott,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Penshurst, Fordcombe and Chiddingstone",Tonbridge and Malling,(England/Wales) Rural village,E10000016,552486.0,143627.0,Sevenoaks 015,Sevenoaks 015D,,,,,Good,South-East England and South London,,10035181676.0,,Not applicable,Not applicable,,,E02005101,E01024456,11.0,
|
||||
118721,886,Kent,3317,"Lady Boswell's Church of England Voluntary Aided Primary School, Sevenoaks",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,436.0,No Special Classes,19-01-2023,425.0,203.0,222.0,4.9,Not applicable,,Not applicable,,Not under a federation,,10079894.0,,Not applicable,25-05-2022,24-05-2024,Plymouth Drive,,,Sevenoaks,Kent,TN13 3RW,http://www.ladyboswells.kent.sch.uk,1732452851.0,Mrs,"Hannah Pullen,",Mrs Sharon Saunders,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Town and St John's,Sevenoaks,(England/Wales) Urban city and town,E10000016,553202.0,155064.0,Sevenoaks 012,Sevenoaks 012F,,,,,Outstanding,South-East England and South London,,10035184700.0,,Not applicable,Not applicable,,,E02005098,E01024471,21.0,
|
||||
118722,886,Kent,3318,Ide Hill Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,154.0,No Special Classes,19-01-2023,153.0,85.0,68.0,5.9,Not applicable,,Not applicable,,Not under a federation,,10069081.0,,Not applicable,04-04-2019,13-05-2024,Sundridge Road,Ide Hill,,Sevenoaks,Kent,TN14 6JT,https://idehill.eschools.co.uk/,1732750389.0,Miss,Elizabeth,Alexander,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Sevenoaks,"Brasted, Chevening and Sundridge",Sevenoaks,(England/Wales) Rural village,E10000016,548483.0,151962.0,Sevenoaks 013,Sevenoaks 013B,,,,,Good,South-East England and South London,,50002011896.0,,Not applicable,Not applicable,,,E02005099,E01024419,9.0,
|
||||
118724,886,Kent,3320,St Barnabas CofE VA Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,202.0,96.0,106.0,34.2,Not applicable,,Not applicable,,Not under a federation,,10069080.0,,Not applicable,24-01-2024,20-05-2024,Quarry Road,,,Tunbridge Wells,Kent,TN1 2EY,www.st-barnabas.kent.sch.uk/,1892522958.0,Mrs,Moira,Duncombe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,St James',Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558893.0,140276.0,Tunbridge Wells 008,Tunbridge Wells 008D,,,,,Good,South-East England and South London,,100062543182.0,,Not applicable,Not applicable,,,E02005169,E01024833,69.0,
|
||||
118725,886,Kent,3322,St James' Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not Recorded,01-01-1900,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,630.0,No Special Classes,19-01-2023,631.0,302.0,329.0,7.8,Not applicable,,Not applicable,,Not under a federation,,10079666.0,,Not applicable,06-03-2024,22-05-2024,Sandrock Road,,,Tunbridge Wells,Kent,TN2 3PR,https://st-james.kent.sch.uk,1892523006.0,Mr,John,Tutt,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Park,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,559230.0,139720.0,Tunbridge Wells 008,Tunbridge Wells 008C,,,,,Good,South-East England and South London,,10000066049.0,,Not applicable,Not applicable,,,E02005169,E01024823,49.0,
|
||||
118726,886,Kent,3323,Hunton Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,102.0,41.0,61.0,17.6,Not applicable,,Not applicable,,Not under a federation,,10069079.0,,Not applicable,12-05-2021,15-04-2024,Bishops Lane,Hunton,,Maidstone,Kent,ME15 0SJ,http://www.hunton.kent.sch.uk,1622820360.0,Mrs,Anita,Makey,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Coxheath and Hunton,Maidstone and The Weald,(England/Wales) Rural hamlet and isolated dwellings,E10000016,571802.0,149308.0,Maidstone 018,Maidstone 018A,,,,,Good,South-East England and South London,,200003720206.0,,Not applicable,Not applicable,,,E02005085,E01024345,18.0,
|
||||
118728,886,Kent,3325,Platt Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,173.0,85.0,88.0,16.2,Not applicable,,Not applicable,,Not under a federation,,10069078.0,,Not applicable,24-04-2019,22-02-2024,Platinum Way,St Mary's Platt,,Sevenoaks,Kent,TN15 8FH,http://www.platt.kent.sch.uk,1732882596.0,Mrs,Emma,Smith,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Borough Green & Platt,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,562312.0,157440.0,Tonbridge and Malling 006,Tonbridge and Malling 006B,,,,,Good,South-East England and South London,United Kingdom,10094697223.0,,Not applicable,Not applicable,,,E02005154,E01024724,28.0,
|
||||
118730,886,Kent,3328,Bapchild and Tonge Church of England Primary School and Nursery,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,245.0,136.0,109.0,11.4,Not applicable,,Not applicable,,Not under a federation,,10069077.0,,Not applicable,17-07-2019,07-05-2024,School Lane,Bapchild,,Sittingbourne,Kent,ME9 9NL,www.bapchildprimary.co.uk,1795424143.0,Mr,Christian,Kelly,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,West Downs,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,592811.0,163003.0,Swale 013,Swale 013D,,,,,Good,South-East England and South London,,10035063550.0,,Not applicable,Not applicable,,,E02005127,E01024629,28.0,
|
||||
118734,886,Kent,3332,Hartlip Endowed Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,108.0,59.0,49.0,15.7,Not applicable,,Not applicable,,Not under a federation,,10069076.0,,Not applicable,21-04-2022,05-03-2024,The Street,Hartlip,,Sittingbourne,Kent,ME9 7TL,www.hartlip.kent.sch.uk,1795842473.0,Mrs,Tracey,Jerome,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,"Hartlip, Newington and Upchurch",Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,583965.0,164247.0,Swale 008,Swale 008A,,,,,Good,South-East England and South London,,200002532833.0,,Not applicable,Not applicable,,,E02005122,E01024570,17.0,
|
||||
118735,886,Kent,3337,Tunstall Church of England (Aided) Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,422.0,200.0,222.0,2.1,Not applicable,,Not applicable,,Not under a federation,,10069075.0,,Not applicable,24-05-2023,23-05-2024,Tunstall Road,,,Sittingbourne,Kent,ME10 1YG,http://www.tunstall.kent.sch.uk/,1795472895.0,Mrs,Rebecca,Andrews,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Woodstock,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,589964.0,161884.0,Swale 013,Swale 013E,,,,,Outstanding,South-East England and South London,,,,Not applicable,Not applicable,,,E02005127,E01024631,9.0,
|
||||
118736,886,Kent,3338,Herne Church of England Junior School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,360.0,No Special Classes,19-01-2023,360.0,184.0,176.0,13.6,Not applicable,,Not applicable,,Not under a federation,,10073479.0,,Not applicable,01-11-2023,25-03-2024,School Lane,Herne,,Herne Bay,Kent,CT6 7AL,http://www.herne-junior.kent.sch.uk,1227374069.0,Mr,Mal,Saunders,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Herne & Broomfield,North Thanet,(England/Wales) Urban city and town,E10000016,618444.0,165965.0,Canterbury 006,Canterbury 006C,,,,,Outstanding,South-East England and South London,,200000683363.0,,Not applicable,Not applicable,,,E02005015,E01024075,49.0,
|
||||
118737,886,Kent,3339,Whitstable and Seasalter Endowed Church of England Junior School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,228.0,No Special Classes,19-01-2023,210.0,107.0,103.0,16.7,Not applicable,,Not applicable,,Not under a federation,,10073478.0,,Not applicable,07-12-2022,15-04-2024,High Street,,,Whitstable,Kent,CT5 1AY,http://www.whitstable-endowed.kent.sch.uk,1227273630.0,Miss,Ellen,Taylor,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Gorrell,Canterbury,(England/Wales) Urban city and town,E10000016,610765.0,166471.0,Canterbury 008,Canterbury 008C,,,,,Outstanding,South-East England and South London,,100062619665.0,,Not applicable,Not applicable,,,E02005017,E01024070,35.0,
|
||||
118738,886,Kent,3340,"Ashford, St Mary's Church of England Primary School",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,420.0,215.0,205.0,25.0,Not applicable,,Not applicable,,Not under a federation,,10069074.0,,Not applicable,29-01-2020,25-01-2024,Western Avenue,,,Ashford,Kent,TN23 1ND,www.st-marys-ashford.kent.sch.uk/,1233625531.0,Mrs,Nicola,Hirst,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Victoria,Ashford,(England/Wales) Urban city and town,E10000016,600448.0,143067.0,Ashford 016,Ashford 016C,,,,,Good,South-East England and South London,,100062559492.0,,Not applicable,Not applicable,,,E02007047,E01023992,105.0,
|
||||
118740,886,Kent,3346,Wittersham Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,140.0,No Special Classes,19-01-2023,141.0,66.0,75.0,17.0,Not applicable,,Not applicable,,Not under a federation,,10069073.0,,Not applicable,28-01-2020,24-04-2024,The Street,Wittersham,,Tenterden,Kent,TN30 7EA,www.wittersham.kent.sch.uk,1797270329.0,Mr,George,Hawkins,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Isle of Oxney,Ashford,(England/Wales) Rural village,E10000016,589703.0,126905.0,Ashford 014,Ashford 014B,,,,,Good,South-East England and South London,,100062567595.0,,Not applicable,Not applicable,,,E02005009,E01023998,24.0,
|
||||
118741,886,Kent,3347,Elham Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,140.0,No Special Classes,19-01-2023,127.0,58.0,69.0,18.1,Not applicable,,Not applicable,,Not under a federation,,10069072.0,,Not applicable,19-07-2022,25-04-2024,Vicarage Lane,Elham,,Canterbury,Kent,CT4 6TT,http://www.elhamprimary.co.uk,1303840325.0,Mr,Dan,File,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs East,Folkestone and Hythe,(England/Wales) Rural village,E10000016,617633.0,143734.0,Folkestone and Hythe 001,Folkestone and Hythe 001A,,,,,Good,South-East England and South London,,50021810.0,,Not applicable,Not applicable,,,E02005102,E01024490,23.0,
|
||||
118744,886,Kent,3350,Saltwood CofE Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,222.0,No Special Classes,19-01-2023,208.0,105.0,103.0,7.7,Not applicable,,Not applicable,,Supported by a federation,The Federation of Bodsham Church of England Primary School and Saltwood Church of England Primary School,10069071.0,,Not applicable,11-05-2022,29-01-2024,Grange Road,Saltwood,,Hythe,Kent,CT21 4QS,http://www.saltwood.kent.sch.uk,1303266058.0,Mr,Paul,Newton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Folkestone and Hythe,Hythe,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,615799.0,135758.0,Folkestone and Hythe 008,Folkestone and Hythe 008D,,,,,Good,South-East England and South London,,50017103.0,,Not applicable,Not applicable,,,E02005109,E01024550,16.0,
|
||||
118745,886,Kent,3351,Ash Cartwright and Kelsey Church of England Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,158.0,89.0,69.0,31.5,Not applicable,,Not applicable,,Not under a federation,,10073477.0,,Not applicable,25-09-2019,15-04-2024,School Road,Ash,,Canterbury,Kent,CT3 2JD,www.ashckschool.org,1304812539.0,Mrs,Fiona,Crascall,Interim Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Little Stour & Ashstone,South Thanet,(England/Wales) Rural town and fringe,E10000016,628453.0,158532.0,Dover 001,Dover 001A,,,,,Good,South-East England and South London,,100062298485.0,,Not applicable,Not applicable,,,E02005041,E01024206,45.0,
|
||||
118748,886,Kent,3356,"Dover, St Mary's Church of England Primary School",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,155.0,75.0,80.0,66.5,Not applicable,,Not applicable,,Not under a federation,,10069069.0,,Not applicable,16-11-2022,06-06-2024,Laureston Place,,,Dover,Kent,CT16 1QX,www.st-marys-dover.kent.sch.uk/,1304206887.0,Ms,Helen,Comfort,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Town & Castle,Dover,(England/Wales) Urban city and town,E10000016,632228.0,141684.0,Dover 012,Dover 012E,,,,,Requires improvement,South-East England and South London,,100062288992.0,,Not applicable,Not applicable,,,E02005052,E01033209,103.0,
|
||||
118750,886,Kent,3360,St Peter-in-Thanet CofE Junior School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,360.0,No Special Classes,19-01-2023,365.0,190.0,175.0,32.3,Not applicable,,Not applicable,,Not under a federation,,10073476.0,,Not applicable,11-05-2023,05-06-2024,Grange Road,St Peter's,,Broadstairs,Kent,CT10 3EP,www.stpetersthanet.co.uk,1843861430.0,Mr,Tim,Whitehouse,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Beacon Road,South Thanet,(England/Wales) Urban city and town,E10000016,638767.0,169105.0,Thanet 009,Thanet 009A,,,,,Outstanding,South-East England and South London,,200003081114.0,,Not applicable,Not applicable,,,E02005140,E01024635,118.0,
|
||||
118751,886,Kent,3364,"Ramsgate, Holy Trinity Church of England Primary School",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,210.0,113.0,97.0,12.4,Not applicable,,Not applicable,,Not under a federation,,10073475.0,,Not applicable,29-09-2021,21-05-2024,Dumpton Park Drive,,,Broadstairs,Kent,CT10 1RR,www.ramsgateholytrinity.co.uk,1843860744.0,Mrs,Erin,Price,Interim Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Viking,South Thanet,(England/Wales) Urban city and town,E10000016,639141.0,166318.0,Thanet 010,Thanet 010E,,,,,Outstanding,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005141,E01024708,26.0,
|
||||
118754,886,Kent,3373,St Mary's Church of England Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,247.0,128.0,119.0,41.3,Not applicable,,Not applicable,,Not under a federation,,10072082.0,,Not applicable,07-12-2022,12-09-2023,St Mary's Road,,,Swanley,Kent,BR8 7BU,http://www.st-marys-swanley.kent.sch.uk,1322665212.0,Mrs,Amanda,McGarrigle,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Swanley St Mary's,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,550895.0,168371.0,Sevenoaks 002,Sevenoaks 002A,,,,,Good,South-East England and South London,,200002881970.0,,Not applicable,Not applicable,,,E02005088,E01024476,102.0,
|
||||
118764,886,Kent,3722,St Ethelbert's Catholic Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,209.0,88.0,121.0,33.5,Not applicable,,Not applicable,,Not under a federation,,10072744.0,,Not applicable,13-06-2019,03-06-2024,"St Ethelbert's Catholic Primary School, Dane Park Road",,,Ramsgate,Kent,CT11 7LS,www.stethelbertsschool.co.uk,1843585555.0,Mr,Simon,Marshall,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Eastcliff,South Thanet,(England/Wales) Urban city and town,E10000016,638595.0,165666.0,Thanet 012,Thanet 012A,,,,,Good,South-East England and South London,,100062282199.0,,Not applicable,Not applicable,,,E02005143,E01024669,70.0,
|
||||
118765,886,Kent,3728,St Anselm's Catholic Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,209.0,91.0,118.0,9.6,Not applicable,,Not applicable,,Not under a federation,,10072743.0,,Not applicable,19-06-2019,21-05-2024,Littlebrook Manor Way,Temple Hill,,Dartford,Kent,DA1 5EA,http://www.st-anselms.kent.sch.uk/,1322225173.0,Mrs,Laura,White,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Temple Hill,Dartford,(England/Wales) Urban major conurbation,E10000016,555174.0,174821.0,Dartford 001,Dartford 001C,,,,,Good,South-East England and South London,,200000531818.0,,Not applicable,Not applicable,,,E02005028,E01024154,20.0,
|
||||
118768,886,Kent,3733,"Our Lady's Catholic Primary School, Dartford",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,218.0,No Special Classes,19-01-2023,217.0,106.0,111.0,6.5,Not applicable,,Not applicable,,Not under a federation,,10072740.0,,Not applicable,12-02-2020,16-04-2024,King Edward Avenue,,,Dartford,Kent,DA1 2HX,http://www.our-ladys.kent.sch.uk,1322222759.0,Miss,Isabel,Quinn,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,West Hill,Dartford,(England/Wales) Urban major conurbation,E10000016,553550.0,174313.0,Dartford 003,Dartford 003E,,,,,Good,South-East England and South London,,10009429007.0,,Not applicable,Not applicable,,,E02005030,E01024184,14.0,
|
||||
118777,886,Kent,3749,"St Thomas' Catholic Primary School, Canterbury",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,209.0,108.0,101.0,18.7,Not applicable,,Not applicable,,Not under a federation,,10072737.0,,Not applicable,20-04-2023,12-09-2023,99 Military Road,,,Canterbury,Kent,CT1 1NE,www.st-thomas-canterbury.kent.sch.uk,1227462539.0,Miss,Lisa,D'Agostini,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,615358.0,158108.0,Canterbury 014,Canterbury 014E,,,,,Good,South-East England and South London,,200000678008.0,,Not applicable,Not applicable,,,E02005023,E01024093,39.0,
|
||||
118785,886,Kent,4026,Dartford Science & Technology College,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,Does not apply,Does not apply,Not applicable,Non-selective,950.0,No Special Classes,19-01-2023,876.0,11.0,865.0,20.8,Not supported by a trust,,Not applicable,,Not under a federation,,10001855.0,,Not applicable,16-03-2022,07-05-2024,Heath Lane,,,Dartford,Kent,DA1 2LY,http://www.dstc.kent.sch.uk,1322224309.0,Miss,Joanne,Sangster,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Princes,Dartford,(England/Wales) Urban major conurbation,E10000016,553232.0,173727.0,Dartford 003,Dartford 003F,,,,,Good,South-East England and South London,,100062308838.0,,Not applicable,Not applicable,,,E02005030,E01024185,155.0,
|
||||
118788,886,Kent,4040,Northfleet School for Girls,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,Does not apply,Does not apply,Not applicable,Non-selective,1145.0,No Special Classes,19-01-2023,1251.0,30.0,1221.0,33.0,Supported by a trust,Northfleet Schools Co-Operative Trust,Not applicable,,Not under a federation,,10004754.0,,Not applicable,02-03-2022,27-03-2024,Hall Road,Northfleet,,Gravesend,Kent,DA11 8AQ,http://www.nsfg.org.uk,1474831020.0,Mr,C,Norwood,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Painters Ash,Gravesham,(England/Wales) Urban major conurbation,E10000016,562828.0,172592.0,Gravesham 006,Gravesham 006E,,,,,Good,South-East England and South London,,100062310786.0,,Not applicable,Not applicable,,,E02005060,E01024288,334.0,
|
||||
118789,886,Kent,4043,Tunbridge Wells Girls' Grammar School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1039.0,No Special Classes,19-01-2023,984.0,0.0,984.0,2.5,Not supported by a trust,,Not applicable,,Not under a federation,,10007075.0,,Not applicable,20-09-2023,17-04-2024,Southfield Road,,,Tunbridge Wells,Kent,TN4 9UJ,http://www.twggs.kent.sch.uk/,1892520902.0,Mrs,Linda,Wybar,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558061.0,140816.0,Tunbridge Wells 007,Tunbridge Wells 007D,,,,,Outstanding,South-East England and South London,,100062585962.0,,Not applicable,Not applicable,,,E02005168,E01024838,18.0,
|
||||
118790,886,Kent,4045,Tunbridge Wells Grammar School for Boys,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,Does not apply,Does not apply,Not applicable,Selective,1308.0,No Special Classes,19-01-2023,1636.0,1586.0,50.0,5.5,Not applicable,,Not applicable,,Not under a federation,,10007076.0,,Not applicable,25-11-2021,21-05-2024,St John's Road,,,Tunbridge Wells,Kent,TN4 9XB,http://www.twgsb.org.uk/,1892529551.0,Ms,Amanda,Simpson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558237.0,141498.0,Tunbridge Wells 002,Tunbridge Wells 002A,,,,,Good,South-East England and South London,,10008662060.0,,Not applicable,Not applicable,,,E02005163,E01024837,71.0,
|
||||
118806,886,Kent,4109,Dover Grammar School for Girls,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,Does not apply,Does not apply,Not applicable,Selective,885.0,No Special Classes,19-01-2023,882.0,24.0,858.0,15.2,Not applicable,,Not applicable,,Not under a federation,,10002018.0,,Not applicable,15-11-2013,08-04-2024,Frith Road,,,Dover,Kent,CT16 2PZ,http://dggs.kent.sch.uk/,1304206625.0,Mr,Robert,Benson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,St Radigunds,Dover,(England/Wales) Urban city and town,E10000016,631484.0,142361.0,Dover 012,Dover 012D,,,,,Outstanding,South-East England and South London,,100062289176.0,,Not applicable,Not applicable,,,E02005052,E01024247,103.0,
|
||||
118835,886,Kent,4522,Maidstone Grammar School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1416.0,No Special Classes,19-01-2023,1425.0,1341.0,84.0,5.4,Not supported by a trust,,Not applicable,,Not under a federation,,10004156.0,,Not applicable,16-01-2019,15-04-2024,Barton Road,,,Maidstone,Kent,ME15 7BT,www.mgs.kent.sch.uk,1622752101.0,Mr,Mark,Tomkins,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,High Street,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576678.0,154844.0,Maidstone 010,Maidstone 010A,,,,,Good,South-East England and South London,,200003683439.0,,Not applicable,Not applicable,,,E02005077,E01024371,57.0,
|
||||
118836,886,Kent,4523,Maidstone Grammar School for Girls,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1240.0,No Special Classes,19-01-2023,1197.0,64.0,1133.0,7.4,Not supported by a trust,,Not applicable,,Not under a federation,,10004157.0,,Not applicable,08-03-2023,29-05-2024,Buckland Road,,,Maidstone,Kent,ME16 0SF,http://www.mggs.org/,1622752103.0,Miss,Deborah,Stanley,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bridge,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575292.0,156397.0,Maidstone 006,Maidstone 006B,,,,,Outstanding,South-East England and South London,,200003670344.0,,Not applicable,Not applicable,,,E02005073,E01024340,65.0,
|
||||
118840,886,Kent,4534,Simon Langton Girls' Grammar School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1082.0,No Special Classes,19-01-2023,1244.0,33.0,1211.0,6.0,Not applicable,,Not applicable,,Not under a federation,,10005848.0,,Not applicable,27-09-2023,23-04-2024,Old Dover Road,,,Canterbury,Kent,CT1 3EW,http://www.langton.kent.sch.uk/,1227463711.0,Mr,Paul,Pollard,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,616129.0,156292.0,Canterbury 016,Canterbury 016C,,,,,Good,South-East England and South London,,200000682990.0,,Not applicable,Not applicable,,,E02005025,E01024046,53.0,
|
||||
118843,886,Kent,4622,The Judd School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1261.0,No Special Classes,19-01-2023,1472.0,1283.0,189.0,2.7,Not applicable,,Not applicable,,Not under a federation,,10006720.0,,Not applicable,07-05-2015,23-05-2024,Brook Street,,,Tonbridge,Kent,TN9 2PN,http://judd.online,1732770880.0,Mr,Jonathan,Wood,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,21.0,22.0,,,South East,Tonbridge and Malling,Judd,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558333.0,145666.0,Tonbridge and Malling 013,Tonbridge and Malling 013A,,,,,Outstanding,South-East England and South London,,100062594180.0,,Not applicable,Not applicable,,,E02005161,E01024757,26.0,
|
||||
118846,886,Kent,5200,Snodland CofE Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,420.0,No Special Classes,19-01-2023,407.0,220.0,187.0,30.2,Not applicable,,Not applicable,,Not under a federation,,10069067.0,,Not applicable,19-10-2022,21-05-2024,Roberts Road,,,Snodland,Kent,ME6 5HL,www.snodland.kent.sch.uk,1634241251.0,Mrs,Holley,Hunt,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Snodland West & Holborough Lakes,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,569746.0,161866.0,Tonbridge and Malling 002,Tonbridge and Malling 002E,,,,,Good,South-East England and South London,,200000958756.0,,Not applicable,Not applicable,,,E02005150,E01024773,123.0,
|
||||
118847,886,Kent,5201,Borough Green Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,315.0,No Special Classes,19-01-2023,265.0,131.0,134.0,22.3,Not supported by a trust,,Not applicable,,Not under a federation,,10069648.0,,Not applicable,07-03-2024,22-05-2024,School Approach,Borough Green,,Sevenoaks,Kent,TN15 8JZ,http://www.bgpschool.kent.sch.uk,1732883459.0,Mrs,Karen,Jackson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Borough Green & Platt,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,561085.0,157405.0,Tonbridge and Malling 006,Tonbridge and Malling 006C,,,,,Good,South-East England and South London,,200000965163.0,,Not applicable,Not applicable,,,E02005154,E01024725,59.0,
|
||||
118849,886,Kent,5203,Roseacre Junior School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,396.0,No Special Classes,19-01-2023,423.0,217.0,206.0,3.3,Not supported by a trust,,Not applicable,,Not under a federation,,10069647.0,,Not applicable,03-11-2022,05-06-2024,The Landway,Bearsted,,Maidstone,Kent,ME14 4BL,http://www.roseacre.kent.sch.uk,1622737843.0,Mr,Duncan,Garrett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bearsted,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,579372.0,155693.0,Maidstone 005,Maidstone 005A,,,,,Outstanding,South-East England and South London,,200003691727.0,,Not applicable,Not applicable,,,E02005072,E01024330,14.0,
|
||||
118852,886,Kent,5206,Herne Bay Junior School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,500.0,No Special Classes,19-01-2023,426.0,210.0,216.0,42.0,Not supported by a trust,,Not applicable,,Not under a federation,,10069646.0,,Not applicable,29-01-2020,09-05-2024,Kings Road,,,Herne Bay,Kent,CT6 5DA,http://www.hernebay-jun.kent.sch.uk/,1227374608.0,Mrs,Melody,Kingman,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Heron,North Thanet,(England/Wales) Urban city and town,E10000016,617911.0,167922.0,Canterbury 001,Canterbury 001A,,,,,Good,South-East England and South London,,200000697542.0,,Not applicable,Not applicable,,,E02005010,E01024078,179.0,
|
||||
118853,886,Kent,5207,"St Francis' Catholic Primary School, Maidstone",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,420.0,No Special Classes,19-01-2023,418.0,194.0,224.0,15.3,Not applicable,,Not applicable,,Not under a federation,,10072733.0,,Not applicable,18-09-2018,05-06-2024,Queen's Road,,,Maidstone,Kent,ME16 0LB,www.st-francis.kent.sch.uk,1622771540.0,Mrs,Elisabeth,Blanden,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bridge,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574157.0,155660.0,Maidstone 003,Maidstone 003F,,,,,Good,South-East England and South London,,200003717931.0,,Not applicable,Not applicable,,,E02005070,E01024341,64.0,
|
||||
118858,886,Kent,5212,Ditton Infant School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,174.0,85.0,89.0,24.1,Not supported by a trust,,Not applicable,,Not under a federation,,10078307.0,,Not applicable,05-10-2022,24-01-2024,Pear Tree Avenue,Ditton,,Aylesford,Kent,ME20 6EB,www.ditton-inf.kent.sch.uk/,1732844107.0,,Claire,Lewer,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford South & Ditton,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,571309.0,158026.0,Tonbridge and Malling 005,Tonbridge and Malling 005C,,,,,Good,South-East England and South London,,200000961077.0,,Not applicable,Not applicable,,,E02005153,E01024735,42.0,
|
||||
118859,886,Kent,5213,"Holy Trinity Church of England Primary School, Dartford",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,420.0,No Special Classes,19-01-2023,423.0,214.0,209.0,15.6,Not applicable,,Not applicable,,Not under a federation,,10069066.0,,Not applicable,03-02-2023,29-05-2024,Chatsworth Road,,,Dartford,Kent,DA1 5AF,http://www.holytrinitydartford.co.uk,1322224474.0,Mrs,Vikki,Wall,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Burnham,Dartford,(England/Wales) Urban major conurbation,E10000016,553399.0,174917.0,Dartford 003,Dartford 003A,,,,,Good,South-East England and South London,,200000530383.0,,Not applicable,Not applicable,,,E02005030,E01024180,66.0,
|
||||
118860,886,Kent,5214,"St Bartholomew's Catholic Primary School, Swanley",Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,350.0,No Special Classes,19-01-2023,324.0,158.0,166.0,19.4,Not applicable,,Not applicable,,Not under a federation,,10072732.0,,Not applicable,05-05-2022,07-05-2024,Sycamore Drive,,,Swanley,Kent,BR8 7AY,http://www.st-bartholomewsrc-pri.kent.sch.uk/,1322663119.0,Mrs,Giovanna,McRae,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Swanley White Oak,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,551351.0,168878.0,Sevenoaks 002,Sevenoaks 002D,,,,,Good,South-East England and South London,,100062276753.0,,Not applicable,Not applicable,,,E02005088,E01024480,63.0,
|
||||
118864,886,Kent,5218,Greatstone Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,364.0,No Special Classes,19-01-2023,300.0,141.0,159.0,24.6,Not supported by a trust,,Not applicable,,Supported by a federation,The Lightyear Federation,10069645.0,,Not applicable,25-05-2022,15-05-2024,Baldwin Road,Greatstone,,New Romney,Kent,TN28 8SY,www.greatstoneschool.co.uk,1797363916.0,Mr,Matt,Rawling,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Walland & Denge Marsh,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,607768.0,122481.0,Folkestone and Hythe 013,Folkestone and Hythe 013A,,,,,Good,South-East England and South London,,50003937.0,,Not applicable,Not applicable,,,E02005114,E01024532,67.0,
|
||||
118867,886,Kent,5221,Wincheap Foundation Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,445.0,Not applicable,19-01-2023,432.0,219.0,213.0,31.5,Not supported by a trust,,Not applicable,,Not under a federation,,10073718.0,,Not applicable,09-12-2021,24-04-2024,Hollow Lane,,,Canterbury,Kent,CT1 3SD,www.wincheap.kent.sch.uk,1227464134.0,Mrs,Nicola,Dawson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,17.0,20.0,,,South East,Canterbury,Wincheap,Canterbury,(England/Wales) Urban city and town,E10000016,614237.0,156789.0,Canterbury 019,Canterbury 019D,,,,,Good,South-East England and South London,,10033162353.0,,Not applicable,Not applicable,,,E02006855,E01035310,136.0,
|
||||
118869,886,Kent,5223,Brookfield Junior School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,256.0,No Special Classes,19-01-2023,246.0,128.0,118.0,31.3,Not applicable,,Not applicable,,Supported by a federation,Flourish,10079029.0,,Not applicable,29-03-2023,23-04-2024,Brookfield Junior School,Swallow Road,Larkfield,Aylesford,Kent,ME20 6PY,www.flourishfederation.co.uk,1732843667.0,Mr,Nathaniel,South,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Larkfield,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,570167.0,158770.0,Tonbridge and Malling 003,Tonbridge and Malling 003F,,,,,Good,South-East England and South London,,100062628494.0,,Not applicable,Not applicable,,,E02005151,E01024765,77.0,
|
||||
118871,886,Kent,5225,Harcourt Primary School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,178.0,87.0,91.0,34.3,Not supported by a trust,,Not applicable,,Not under a federation,,10069644.0,,Not applicable,06-10-2021,13-04-2024,Biggins Wood Road,,,Folkestone,Kent,CT19 4NE,www.harcourt.kent.sch.uk/,1303275294.0,Mr,Anthony,Silk,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,620022.0,137406.0,Folkestone and Hythe 002,Folkestone and Hythe 002A,,,,,Good,South-East England and South London,,50030583.0,,Not applicable,Not applicable,,,E02005103,E01024492,61.0,
|
||||
118879,886,Kent,5407,Thamesview School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,910.0,Has Special Classes,19-01-2023,955.0,498.0,457.0,40.0,Not supported by a trust,,Not applicable,,Not under a federation,,10006569.0,,Not applicable,20-06-2018,03-06-2024,Thong Lane,,,Gravesend,Kent,DA12 4LF,http://www.thamesviewsch.co.uk/,1474566552.0,Mr,George,Rorke,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,,,,,South East,Gravesham,Riverview Park,Gravesham,(England/Wales) Urban major conurbation,E10000016,566808.0,172052.0,Gravesham 008,Gravesham 008B,,,,,Good,South-East England and South London,,100062312649.0,,Not applicable,Not applicable,,,E02005062,E01024298,354.0,
|
||||
118884,886,Kent,5412,Simon Langton Grammar School for Boys,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1280.0,No Special Classes,19-01-2023,1247.0,1117.0,130.0,5.0,Not supported by a trust,,Not applicable,,Not under a federation,,10005849.0,,Not applicable,14-11-2013,28-05-2024,Langton Lane,Nackington Road,,Canterbury,Kent,CT4 7AS,http://www.thelangton.org.uk/,1227463567.0,Dr,Ken,Moffat,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,41.0,35.0,,,South East,Canterbury,Wincheap,Canterbury,(England/Wales) Urban city and town,E10000016,615323.0,155825.0,Canterbury 019,Canterbury 019E,,,,,Outstanding,South-East England and South London,,,,Not applicable,Not applicable,,,E02006855,E01035311,39.0,
|
||||
118897,886,Kent,5425,The Malling School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1003.0,No Special Classes,19-01-2023,1010.0,547.0,463.0,23.0,Supported by a trust,The Malling Holmesdale Federation,Not applicable,,Not under a federation,,10006748.0,,Not applicable,29-03-2023,04-06-2024,Beech Road,East Malling,,West Malling,Kent,ME19 6DH,www.themallingschool.kent.sch.uk,1732840995.0,Mr,John,Vennart,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,,,,,,,,,,,,Resourced provision,124.0,121.0,,,South East,Tonbridge and Malling,"East Malling, West Malling & Offham",Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,569778.0,157407.0,Tonbridge and Malling 014,Tonbridge and Malling 014A,,,,,Good,South-East England and South London,,200000960428.0,,Not applicable,Not applicable,,,E02006833,E01024740,208.0,
|
||||
118898,886,Kent,5426,The Archbishop's School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Non-selective,900.0,No Special Classes,19-01-2023,722.0,382.0,340.0,45.6,Not supported by a trust,,Not applicable,,Not under a federation,,10006581.0,,Not applicable,23-11-2023,23-04-2024,St Stephens Hill,,,Canterbury,Kent,CT2 7AP,http://www.archbishops-school.co.uk/,1227765805.0,Mr,David,Elliott,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,SpLD - Specific Learning Difficulty,VI - Visual Impairment,OTH - Other Difficulty/Disability,HI - Hearing Impairment,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,"SEMH - Social, Emotional and Mental Health",PD - Physical Disability,MLD - Moderate Learning Difficulty,,,,,Resourced provision,7.0,7.0,,,South East,Canterbury,St Stephen's,Canterbury,(England/Wales) Urban city and town,E10000016,614417.0,159483.0,Canterbury 013,Canterbury 013B,,,,,Good,South-East England and South London,,100062619198.0,,Not applicable,Not applicable,,,E02005022,E01024100,284.0,
|
||||
118919,886,Kent,5447,St George's Church of England Foundation School,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,All-through,4.0,19,No boarders,No Nursery Classes,Has a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Non-selective,1375.0,No Special Classes,19-01-2023,1633.0,788.0,845.0,26.9,Not supported by a trust,,Not applicable,,Not under a federation,,10006163.0,,Not applicable,13-06-2019,21-05-2024,Westwood Road,,,Broadstairs,Kent,CT10 2LH,http://www.stgeorges-school.org.uk/,1843861696.0,Mr,Adam,Mirams,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,St Peters,South Thanet,(England/Wales) Urban city and town,E10000016,637425.0,167972.0,Thanet 011,Thanet 011E,,,,,Good,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005142,E01024691,404.0,
|
||||
118928,886,Kent,5456,Northfleet Technology College,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Non-selective,989.0,No Special Classes,19-01-2023,903.0,900.0,3.0,29.2,Supported by a trust,Northfleet Schools Co-Operative Trust,Not applicable,,Not under a federation,,10004755.0,,Not applicable,22-09-2022,21-05-2024,Colyer Road,Northfleet,,Gravesend,Kent,DA11 8BG,http://www.ntc.kent.sch.uk/,1474533802.0,Mr,Steven,Gallears,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Northfleet & Springhead,Gravesham,(England/Wales) Urban major conurbation,E10000016,563003.0,173050.0,Gravesham 006,Gravesham 006B,,,,,Good,South-East England and South London,,10012024936.0,,Not applicable,Not applicable,,,E02005060,E01024281,219.0,
|
||||
118931,886,Kent,5459,Dover Grammar School for Boys,Foundation school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,880.0,No Special Classes,19-01-2023,835.0,822.0,13.0,15.3,Not supported by a trust,,Not applicable,,Not under a federation,,10002017.0,,Not applicable,16-10-2019,21-05-2024,Astor Avenue,,,Dover,Kent,CT17 0DQ,http://www.dgsb.co.uk,1304206117.0,Mr,Philip,Horstrup,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Tower Hamlets,Dover,(England/Wales) Urban city and town,E10000016,630305.0,141711.0,Dover 011,Dover 011H,,,,,Good,South-East England and South London,,10034874352.0,,Not applicable,Not applicable,,,E02005051,E01024248,103.0,
|
||||
118933,886,Kent,5461,St John's Catholic Comprehensive,Voluntary aided school,Local authority maintained schools,"Open, but proposed to close",Not applicable,,Academy Converter,31-08-2024,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,1184.0,No Special Classes,19-01-2023,1294.0,663.0,631.0,22.5,Not applicable,,Not applicable,,Not under a federation,,10006200.0,,Not applicable,15-05-2018,06-06-2024,Rochester Road,,,Gravesend,Kent,DA12 2JW,http://www.stjohnscs.com,1474534718.0,Mr,Matthew,Barron,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Denton,Gravesham,(England/Wales) Urban major conurbation,E10000016,565876.0,173278.0,Gravesham 003,Gravesham 003C,,,,,Good,South-East England and South London,,100062311894.0,,Not applicable,Not applicable,,,E02005057,E01024293,218.0,
|
||||
118937,886,Kent,6000,Ashford School,Other independent school,Independent schools,Open,Not applicable,01-01-1918,Not applicable,,Not applicable,,18,Boarding school,Has Nursery Classes,Has a sixth form,Mixed,Christian,Christian,Not applicable,Not applicable,1084.0,No Special Classes,20-01-2022,1050.0,550.0,500.0,0.0,Not applicable,,Not applicable,,Not applicable,,10008077.0,,Not applicable,,28-03-2024,East Hill,,,Ashford,Kent,TN24 8PB,https://www.ashfordschool.co.uk,1233625171.0,Mr,Michael,Hall,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Ashford,Victoria,Ashford,(England/Wales) Urban city and town,E10000016,601413.0,142875.0,Ashford 005,Ashford 005G,ISI,281.0,40.0,,,South-East England and South London,,100062560535.0,,Not applicable,Not applicable,,,E02005000,E01034986,0.0,
|
||||
118938,886,Kent,6001,Wellesley Haddon Dene School,Other independent school,Independent schools,Open,Not applicable,01-01-1929,Not applicable,,Not applicable,2.0,13,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Not applicable,320.0,No Special Classes,20-01-2022,210.0,126.0,84.0,0.0,Not applicable,,Not applicable,,Not applicable,,10015812.0,,Not applicable,,22-04-2024,114 Ramsgate Road,Broadstairs,Kent,,,CT10 2DG,www.wellesleyhouse.org,1843862991.0,Mrs,Joanne,Parpworth,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Thanet,Viking,South Thanet,(England/Wales) Urban city and town,E10000016,638726.0,167390.0,Thanet 010,Thanet 010C,ISI,3.0,20.0,Alpha Schools,,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005141,E01024706,0.0,
|
||||
118939,886,Kent,6002,Benenden School,Other independent school,Independent schools,Open,Not applicable,01-01-1926,Not applicable,,Not applicable,10.0,19,Boarding school,No Nursery Classes,Has a sixth form,Girls,None,None,Not applicable,Not applicable,620.0,No Special Classes,20-01-2022,546.0,0.0,546.0,0.0,Not applicable,,Not applicable,,Not applicable,,10014830.0,,Not applicable,,07-05-2024,Cranbrook Road,Benenden,,Cranbrook,Kent,TN17 4AA,http://www.benenden.school,1580240592.0,Mrs,S,Price,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Benenden and Cranbrook,Maidstone and The Weald,(England/Wales) Rural village,E10000016,580246.0,133803.0,Tunbridge Wells 014,Tunbridge Wells 014A,ISI,,127.0,Benenden School (Kent) Ltd,,South-East England and South London,,100062552564.0,,Not applicable,Not applicable,,,E02005175,E01024789,0.0,
|
||||
118940,886,Kent,6003,Dover College,Other independent school,Independent schools,Open,Not applicable,01-01-1909,Not applicable,,Not applicable,3.0,18,Boarding school,Has Nursery Classes,Has a sixth form,Mixed,Church of England,Church of England,Not applicable,Non-selective,478.0,No Special Classes,20-01-2022,313.0,172.0,141.0,0.0,Not applicable,,Not applicable,,Not applicable,,10008199.0,,Not applicable,,03-05-2024,Effingham Crescent,,,Dover,Kent,CT17 9RH,www.dovercollege.org.uk,1304205969.0,Mr,Simon,Fisher,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Dover,Town & Castle,Dover,(England/Wales) Urban city and town,E10000016,631537.0,141684.0,Dover 013,Dover 013B,ISI,,110.0,Corporation of Dover College,,South-East England and South London,,100062290526.0,,Not applicable,Not applicable,,,E02005053,E01024215,0.0,
|
||||
118941,886,Kent,6004,Northbourne Park School,Other independent school,Independent schools,Open,Not applicable,01-01-1942,Not applicable,,Not applicable,2.0,13,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Church of England,Not applicable,Non-selective,205.0,No Special Classes,20-01-2022,193.0,99.0,94.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018503.0,,Not applicable,,30-01-2024,Betteshanger,,,DEAL,Kent,CT14 0NW,http://www.northbournepark.com,1304611215.0,Mr,Mark,Hammond,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Dover,Eastry Rural,Dover,(England/Wales) Rural village,E10000016,631045.0,152538.0,Dover 005,Dover 005A,ISI,,38.0,,,South-East England and South London,,10034873947.0,,Not applicable,Not applicable,,,E02005045,E01024201,0.0,
|
||||
118942,886,Kent,6005,Marlborough House School,Other independent school,Independent schools,Open,Not applicable,01-01-1932,Not applicable,,Not applicable,2.0,13,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,Christian,Not applicable,Non-selective,350.0,No Special Classes,20-01-2022,248.0,119.0,129.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018722.0,,Not applicable,,04-06-2024,Hawkhurst,,,Cranbrook,Kent,TN18 4PY,www.marlboroughhouseschool.co.uk,1580753555.0,Mr,Eddy,Newton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Hawkhurst and Sandhurst,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,575350.0,130678.0,Tunbridge Wells 014,Tunbridge Wells 014E,ISI,,63.0,Marlborough House Sch Trust Ltd,,South-East England and South London,,100062106196.0,,Not applicable,Not applicable,,,E02005175,E01024810,0.0,
|
||||
118943,886,Kent,6006,St Ronan's School,Other independent school,Independent schools,Open,Not applicable,01-01-1917,Not applicable,,Not applicable,2.0,13,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Non-selective,468.0,No Special Classes,20-01-2022,455.0,241.0,214.0,0.0,Not applicable,,Not applicable,,Not applicable,,10017665.0,,Not applicable,,04-06-2024,Water Lane,Hawkhurst,,Cranbrook,Kent,TN18 5DJ,www.saintronans.co.uk,1580752271.0,Mr,William,Trelawney-Vernon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Hawkhurst and Sandhurst,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,577801.0,130807.0,Tunbridge Wells 014,Tunbridge Wells 014E,ISI,,85.0,St Ronan's (Hawkhurst),,South-East England and South London,,10024135777.0,,Not applicable,Not applicable,,,E02005175,E01024810,0.0,
|
||||
118944,886,Kent,6007,Gad's Hill School,Other independent school,Independent schools,Open,Not applicable,01-01-1948,Not applicable,,Not applicable,3.0,16,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,755.0,No Special Classes,20-01-2022,384.0,194.0,190.0,0.0,Not applicable,,Not applicable,,Not applicable,,10015390.0,,Not applicable,,15-05-2024,Higham,,,Rochester,Kent,ME3 7PA,www.gadshill.org,1474822366.0,Mr,Paul,Savage,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Gravesham,Higham & Shorne,Gravesham,(England/Wales) Rural town and fringe,E10000016,570991.0,170882.0,Gravesham 010,Gravesham 010B,ISI,6.0,30.0,Gad's Hill School,,South-East England and South London,,10012012444.0,,Not applicable,Not applicable,,,E02005064,E01024266,0.0,
|
||||
118946,886,Kent,6009,Kent College Pembury,Other independent school,Independent schools,Open,Not applicable,01-01-1933,Not applicable,,Not applicable,3.0,20,Boarding school,Has Nursery Classes,Has a sixth form,Girls,Methodist,Methodist,Not applicable,Selective,678.0,No Special Classes,20-01-2022,512.0,19.0,493.0,0.0,Not applicable,,Not applicable,,Not applicable,,10008307.0,,Not applicable,,08-05-2024,Old Church Road,Pembury,,Tunbridge Wells,Kent,TN2 4AX,http://www.kent-college.co.uk/,1892822006.0,Miss,Katrina,Handford,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Pembury,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,562692.0,143044.0,Tunbridge Wells 004,Tunbridge Wells 004B,ISI,7.0,155.0,Methodist Independent Schools Trust,,South-East England and South London,,10008667752.0,,Not applicable,Not applicable,,,E02005165,E01024825,0.0,
|
||||
118947,886,Kent,6010,St Lawrence College,Other independent school,Independent schools,Open,Not applicable,01-01-1912,Not applicable,,Not applicable,10.0,18,Boarding school,No Nursery Classes,Has a sixth form,Mixed,Christian/Evangelical,Church of England,Not applicable,Not applicable,500.0,No Special Classes,20-01-2022,409.0,218.0,191.0,0.0,Not applicable,,Not applicable,,Not applicable,,10017219.0,,Not applicable,,18-04-2024,College Road,,,Ramsgate,Kent,CT11 7AE,www.slcuk.com,1843572900.0,Mr,Barney,Durrant,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Thanet,Eastcliff,South Thanet,(England/Wales) Urban city and town,E10000016,637926.0,165987.0,Thanet 015,Thanet 015C,ISI,1.0,,Corporation of St Lawrence College,,South-East England and South London,,100062281951.0,,Not applicable,Not applicable,,,E02005146,E01024668,0.0,
|
||||
118949,886,Kent,6011,Beechwood School,Other independent school,Independent schools,Open,Not applicable,01-01-1943,Not applicable,,Not applicable,3.0,19,Boarding school,Has Nursery Classes,Has a sixth form,Mixed,None,Christian,Not applicable,Non-selective,420.0,No Special Classes,20-01-2022,309.0,161.0,148.0,0.0,Not applicable,,Not applicable,,Not applicable,,10014837.0,,Not applicable,,07-06-2024,12 Pembury Road,,,Tunbridge Wells,Kent,TN2 3QD,http://www.beechwood.org.uk,1892532747.0,Mr,Justin,Foster-Gandey,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Park,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,560091.0,139893.0,Tunbridge Wells 009,Tunbridge Wells 009B,ISI,,64.0,Alpha Schools,,South-East England and South London,,100062554131.0,,Not applicable,Not applicable,,,E02005170,E01024822,0.0,
|
||||
118950,886,Kent,6012,Holmewood House School,Other independent school,Independent schools,Open,Not applicable,01-01-1951,Not applicable,,Not applicable,2.0,13,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,540.0,No Special Classes,20-01-2022,449.0,242.0,207.0,0.0,Not applicable,,Not applicable,,Not applicable,,10015872.0,,Not applicable,,07-06-2024,Barrow Lane,Langton Green,,Tunbridge Wells,Kent,TN3 0EB,www.holmewoodhouse.co.uk,1892860000.0,Mrs,Ruth,O'Sullivan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Speldhurst and Bidborough,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,555212.0,138621.0,Tunbridge Wells 006,Tunbridge Wells 006C,ISI,4.0,139.0,,,South-East England and South London,,100062108061.0,,Not applicable,Not applicable,,,E02005167,E01024852,0.0,
|
||||
118951,886,Kent,6013,Rose Hill School,Other independent school,Independent schools,Open,Not applicable,01-01-1949,Not applicable,,Not applicable,3.0,13,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,320.0,No Special Classes,20-01-2022,265.0,154.0,111.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018845.0,,Not applicable,,15-05-2024,Coniston Avenue,,,Tunbridge Wells,Kent,TN4 9SY,,1892525591.0,Ms,Emma,Neville,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Culverden,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,557231.0,140251.0,Tunbridge Wells 007,Tunbridge Wells 007B,ISI,6.0,82.0,Grange Rose Hill School Ltd,,South-East England and South London,,100062585961.0,,Not applicable,Not applicable,,,E02005168,E01024800,0.0,
|
||||
118952,886,Kent,6014,Sevenoaks School,Other independent school,Independent schools,Open,Not applicable,01-01-1918,Not applicable,,Not applicable,11.0,18,Boarding school,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Not applicable,1250.0,No Special Classes,20-01-2022,1181.0,624.0,557.0,0.0,Not applicable,,Not applicable,,Not applicable,,10005765.0,,Not applicable,,08-05-2024,High Street,Sevenoaks,Kent,Sevenoaks,Kent,TN13 1HU,www.sevenoaksschool.org,1732455133.0,Mr,Jesse,Elzinga,Head,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Town and St John's,Sevenoaks,(England/Wales) Urban city and town,E10000016,553207.0,154098.0,Sevenoaks 012,Sevenoaks 012F,ISI,,169.0,,,South-East England and South London,,100062546708.0,,Not applicable,Not applicable,,,E02005098,E01024471,0.0,
|
||||
118953,886,Kent,6015,Sevenoaks Preparatory School,Other independent school,Independent schools,Open,Not applicable,26-03-1958,Not applicable,,Not applicable,3.0,14,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Non-selective,444.0,No Special Classes,20-01-2022,381.0,181.0,200.0,0.0,Not applicable,,Not applicable,,Not applicable,,10017399.0,,Not applicable,,09-04-2024,Fawke Cottage,Godden Green,,Sevenoaks,Kent,TN15 0JU,http://www.theprep.org.uk,1732762336.0,Mr,Luke,Harrison,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Seal and Weald,Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,555440.0,154482.0,Sevenoaks 012,Sevenoaks 012A,ISI,,63.0,,,South-East England and South London,,10035182256.0,,Not applicable,Not applicable,,,E02005098,E01024458,0.0,
|
||||
118954,886,Kent,6016,St Michael's Prep School,Other independent school,Independent schools,Open,Not applicable,01-01-1945,Not applicable,,Not applicable,2.0,13,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Non-selective,488.0,No Special Classes,20-01-2022,476.0,244.0,232.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018594.0,,Not applicable,,02-05-2024,St Michael's Preparatory School,Otford Court,Row Dow,Otford,,TN14 5RY,www.stmichaels.kent.sch.uk,1959522137.0,Mr,Nik,Pears,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Otford and Shoreham,Sevenoaks,(England/Wales) Rural town and fringe,E10000016,554260.0,159647.0,Sevenoaks 009,Sevenoaks 009D,ISI,2.0,71.0,,,South-East England and South London,United Kingdom,10035184625.0,,Not applicable,Not applicable,,,E02005095,E01024453,0.0,
|
||||
118955,886,Kent,6017,The New Beacon School,Other independent school,Independent schools,Open,Not applicable,01-01-1917,Not applicable,,Not applicable,2.0,14,Boarding school,Has Nursery Classes,Does not have a sixth form,Boys,None,None,Not applicable,Non-selective,397.0,No Special Classes,20-01-2022,348.0,347.0,1.0,0.0,Not applicable,,Not applicable,,Not applicable,,10017253.0,,Not applicable,,29-04-2024,Brittains Lane,,,Sevenoaks,Kent,TN13 2PB,www.newbeacon.org.uk,1732452131.0,Mrs,Sarah,Brownsdon,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Kippington,Sevenoaks,(England/Wales) Urban city and town,E10000016,552009.0,153717.0,Sevenoaks 011,Sevenoaks 011E,ISI,1.0,77.0,Tonbridge School of High Street,,South-East England and South London,,100062074386.0,,Not applicable,Not applicable,,,E02005097,E01024464,0.0,
|
||||
118957,886,Kent,6018,Radnor House Sevenoaks,Other independent school,Independent schools,Open,New Provision,26-02-1997,Not applicable,,Not applicable,2.0,18,No boarders,Has Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Selective,750.0,No Special Classes,20-01-2022,533.0,308.0,225.0,0.0,Not applicable,,Not applicable,,Not applicable,,10008175.0,,Not applicable,,25-04-2024,Combe Bank Drive,Sevenoaks,,,Kent,TN14 6AE,www.radnor-sevenoaks.org,1959563720.0,Mr,David,Paton,Head,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Brasted, Chevening and Sundridge",Sevenoaks,(England/Wales) Rural village,E10000016,548115.0,155824.0,Sevenoaks 013,Sevenoaks 013A,ISI,,37.0,Radnor House Sevenoaks (Holdings) Ltd,,South-East England and South London,United Kingdom,10035181671.0,,Not applicable,Not applicable,,,E02005099,E01024417,0.0,
|
||||
118958,886,Kent,6019,Sutton Valence School,Other independent school,Independent schools,Open,Not applicable,01-01-1908,Not applicable,,Not applicable,2.0,19,Boarding school,Has Nursery Classes,Has a sixth form,Mixed,Christian,Christian,Not applicable,Not applicable,880.0,No Special Classes,20-01-2022,868.0,516.0,352.0,0.0,Not applicable,,Not applicable,,Not applicable,,10017608.0,,Not applicable,,15-05-2024,North Street,Sutton Valence,,Maidstone,Kent,ME17 3HL,http://www.svs.org.uk/index.html,1622845203.0,Mr,James,Thomas,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Maidstone,Sutton Valence and Langley,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,581234.0,149347.0,Maidstone 017,Maidstone 017D,ISI,2.0,29.0,United Westminster Grey Coat Foundation,,South-East England and South London,,200003719418.0,,Not applicable,Not applicable,,,E02005084,E01024411,0.0,
|
||||
118959,886,Kent,6020,Tonbridge School,Other independent school,Independent schools,Open,Not applicable,01-01-1918,Not applicable,,Not applicable,13.0,18,Boarding school,No Nursery Classes,Has a sixth form,Boys,None,Church of England,Not applicable,Not applicable,820.0,No Special Classes,20-01-2022,794.0,794.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10006936.0,,Not applicable,,10-05-2024,,,,Tonbridge,Kent,TN9 1JP,www.tonbridge-school.co.uk,1732365555.0,Mr,James,Priory,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Judd,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559065.0,147021.0,Tonbridge and Malling 012,Tonbridge and Malling 012A,ISI,,150.0,,,South-East England and South London,,200000969928.0,,Not applicable,Not applicable,,,E02005160,E01024732,0.0,
|
||||
118960,886,Kent,6021,Somerhill,Other independent school,Independent schools,Open,Not applicable,01-01-1935,Not applicable,,Not applicable,2.0,13,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Christian,Not applicable,Non-selective,654.0,No Special Classes,20-01-2022,608.0,383.0,225.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018591.0,,Not applicable,,21-05-2024,Tudeley Road,Tonbridge,Kent,,,TN11 0NJ,www.somerhill.org,1732352124.0,Mr,Duncan,Sinclair,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Capel,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,560772.0,145340.0,Tunbridge Wells 001,Tunbridge Wells 001A,ISI,,41.0,Somerhill Charitable Trust Limited,,South-East England and South London,,10008663663.0,,Not applicable,Not applicable,,,E02005162,E01024798,0.0,
|
||||
118965,886,Kent,6024,Steephill School,Other independent school,Independent schools,Open,Not applicable,09-10-1957,Not applicable,,Not applicable,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Non-selective,125.0,No Special Classes,20-01-2022,132.0,59.0,73.0,0.0,Not applicable,,Not applicable,,Not applicable,,10070236.0,,Not applicable,,03-04-2024,Off Castle Hill,Fawkham,,Longfield,Kent,DA3 7BG,www.steephill.co.uk,1474702107.0,Mr,John,Abbott,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Fawkham and West Kingsdown,Sevenoaks,(England/Wales) Urban city and town,E10000016,559847.0,168076.0,Sevenoaks 007,Sevenoaks 007C,ISI,1.0,38.0,,,South-East England and South London,,10035182828.0,,Not applicable,Not applicable,,,E02005093,E01024437,0.0,
|
||||
118967,886,Kent,6026,Bronte School,Other independent school,Independent schools,Open,Not applicable,17-10-1957,Not applicable,,Not applicable,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Not applicable,160.0,No Special Classes,20-01-2022,148.0,72.0,76.0,0.0,Not applicable,,Not applicable,,Not applicable,,10070235.0,,Not applicable,,04-06-2024,7 Pelham Road,,,Gravesend,Kent,DA11 0HN,,1474533805.0,Mrs,Emma,Wood,Headmistress,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Gravesham,Pelham,Gravesham,(England/Wales) Urban major conurbation,E10000016,564341.0,173911.0,Gravesham 002,Gravesham 002C,ISI,1.0,18.0,Nicholas Clements,,South-East England and South London,,200001873251.0,,Not applicable,Not applicable,,,E02005056,E01024290,0.0,
|
||||
118971,886,Kent,6029,The Granville School,Other independent school,Independent schools,Open,Not applicable,31-03-1958,Not applicable,,Not applicable,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Inter- / non- denominational,Not applicable,Non-selective,215.0,No Special Classes,20-01-2022,162.0,7.0,155.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080538.0,,Not applicable,,29-05-2024,2 Bradbourne Park Road,,,Sevenoaks,Kent,TN13 3LJ,www.granvilleschool.org,1732453039.0,Mrs,Louise,Lawrance,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Town and St John's,Sevenoaks,(England/Wales) Urban city and town,E10000016,552345.0,155661.0,Sevenoaks 012,Sevenoaks 012E,ISI,,13.0,,,South-East England and South London,,100062546713.0,,Not applicable,Not applicable,,,E02005098,E01024470,0.0,
|
||||
118973,886,Kent,6031,Hilden Grange School,Other independent school,Independent schools,Open,Not applicable,01-01-1957,Not applicable,,Not applicable,2.0,13,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Selective,370.0,No Special Classes,20-01-2022,298.0,193.0,105.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018656.0,,Not applicable,,11-04-2024,Dry Hill Park Road,,,Tonbridge,Kent,TN10 3BX,www.hildengrange.co.uk,1732352706.0,Mr,Malcolm,Gough,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Cage Green & Angel,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558953.0,147481.0,Tonbridge and Malling 012,Tonbridge and Malling 012A,ISI,1.0,84.0,Inspired Education Group,,South-East England and South London,,100062543570.0,,Not applicable,Not applicable,,,E02005160,E01024732,0.0,
|
||||
118974,886,Kent,6032,Hilden Oaks Preparatory School and Nursery,Other independent school,Independent schools,Open,Not applicable,17-10-1957,Not applicable,,Not applicable,,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,222.0,No Special Classes,20-01-2022,166.0,70.0,96.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080536.0,,Not applicable,,22-05-2024,38 Dry Hill Park Road,,,Tonbridge,Kent,TN10 3BU,www.hildenoaks.co.uk,1732353941.0,Mrs,Katy,Joiner,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Cage Green & Angel,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558953.0,147481.0,Tonbridge and Malling 012,Tonbridge and Malling 012B,ISI,,27.0,Derick Walker,,South-East England and South London,,100062543569.0,,Not applicable,Not applicable,,,E02005160,E01024733,0.0,
|
||||
118975,886,Kent,6033,The Mead School,Other independent school,Independent schools,Open,Not applicable,16-10-1957,Not applicable,,Not applicable,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Christian,Christian,Not applicable,Non-selective,250.0,No Special Classes,20-01-2022,238.0,111.0,127.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080535.0,,Not applicable,,10-04-2024,16 Frant Road,,,Tunbridge Wells,Kent,TN2 5SN,www.themeadschool.co.uk,1892525837.0,Mrs,Catherine,Openshaw,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Pantiles and St Mark's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558193.0,138430.0,Tunbridge Wells 012,Tunbridge Wells 012B,ISI,2.0,19.0,The Mead School Ltd,,South-East England and South London,,10000066049.0,,Not applicable,Not applicable,,,E02005173,E01024817,0.0,
|
||||
118977,886,Kent,6035,Chartfield School,Other independent school,Independent schools,Open,Not applicable,08-10-1957,Not applicable,,Not applicable,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Christian,Not applicable,Non-selective,84.0,No Special Classes,20-01-2022,42.0,22.0,20.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080534.0,,Not applicable,21-09-2023,20-02-2024,45 Minster Road,,,Westgate-on-Sea,Kent,CT8 8DA,www.chartfieldschool.org.uk,1843831716.0,Miss,Sarah,Neale,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Thanet,Westgate-on-Sea,North Thanet,(England/Wales) Urban city and town,E10000016,632551.0,169556.0,Thanet 007,Thanet 007E,Ofsted,,5.0,Chartfield School Ltd,Requires improvement,South-East England and South London,,,,Not applicable,Not applicable,,,E02005138,E01024716,0.0,
|
||||
118978,886,Kent,6036,Bethany School,Other independent school,Independent schools,Open,Not applicable,06-11-1951,Not applicable,,Not applicable,11.0,18,Boarding school,No Nursery Classes,Has a sixth form,Mixed,Christian,Christian,Not applicable,Non-selective,450.0,No Special Classes,20-01-2022,348.0,234.0,114.0,0.0,Not applicable,,Not applicable,,Not applicable,,10013351.0,,Not applicable,,24-04-2024,Goudhurst,,,Cranbrook,Kent,TN17 1LB,http://www.bethanyschool.org.uk,1580211273.0,Mr,Francie,Healy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Goudhurst and Lamberhurst,Tunbridge Wells,(England/Wales) Rural hamlet and isolated dwellings,E10000016,573928.0,140377.0,Tunbridge Wells 011,Tunbridge Wells 011E,ISI,,240.0,Bethany School Limited,,South-East England and South London,,10008665682.0,,Not applicable,Not applicable,,,E02005172,E01024806,0.0,
|
||||
118981,886,Kent,6038,Solefield School,Other independent school,Independent schools,Open,Not applicable,07-10-1957,Not applicable,,Not applicable,3.0,13,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England,Not applicable,Non-selective,190.0,No Special Classes,20-01-2022,151.0,151.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018776.0,,Not applicable,,08-05-2024,Solefields Road,,,Sevenoaks,Kent,TN13 1PH,http://www.solefieldschool.org,1732452142.0,Mrs,Helen,McClure,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Kippington,Sevenoaks,(England/Wales) Urban city and town,E10000016,553072.0,153650.0,Sevenoaks 012,Sevenoaks 012C,ISI,4.0,55.0,,,South-East England and South London,,,,Not applicable,Not applicable,,,E02005098,E01024462,0.0,
|
||||
118984,886,Kent,6039,Russell House School,Other independent school,Independent schools,Open,Not applicable,01-01-1952,Not applicable,,Not applicable,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,218.0,No Special Classes,20-01-2022,183.0,88.0,95.0,0.0,Not applicable,,Not applicable,,Not applicable,,10071116.0,,Not applicable,,23-05-2024,Station Road,Otford,,Sevenoaks,Kent,TN14 5QU,www.russellhouseschool.co.uk,1959522352.0,Mr,Craig,McCarthy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Otford and Shoreham,Sevenoaks,(England/Wales) Rural hamlet and isolated dwellings,E10000016,552991.0,159400.0,Sevenoaks 009,Sevenoaks 009D,ISI,1.0,12.0,Dr Yvonne Lindsay RHS Ltd,,South-East England and South London,,100062621727.0,,Not applicable,Not applicable,,,E02005095,E01024453,0.0,
|
||||
118986,886,Kent,6040,St Lawrence College Junior School,Other independent school,Independent schools,Open,Not applicable,01-01-1954,Not applicable,,Not applicable,2.0,11,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England/Christian,Not applicable,Not applicable,220.0,No Special Classes,20-01-2022,160.0,98.0,62.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080532.0,,Not applicable,,07-05-2024,College Road,,,Ramsgate,Kent,CT11 7AF,https://www.slcuk.com/,1843572912.0,Mrs,Ellen,Rowe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Thanet,Eastcliff,South Thanet,(England/Wales) Urban city and town,E10000016,637926.0,165987.0,Thanet 015,Thanet 015C,ISI,,36.0,Corporation of St Lawrence College,,South-East England and South London,,100062099015.0,,Not applicable,Not applicable,,,E02005146,E01024668,0.0,
|
||||
118990,886,Kent,6043,The Dulwich School Cranbrook,Other independent school,Independent schools,Open,Not applicable,01-01-1920,Not applicable,,Not applicable,2.0,16,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,Christian,Not applicable,Non-selective,592.0,No Special Classes,20-01-2022,343.0,175.0,168.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018413.0,,Not applicable,,08-05-2024,Coursehorn,,,CRANBROOK,Kent,TN17 3NP,www.dulwichcranbrook.org,1580712179.0,Mrs,Sophie,Bradshaw,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Benenden and Cranbrook,Maidstone and The Weald,(England/Wales) Rural hamlet and isolated dwellings,E10000016,579266.0,135858.0,Tunbridge Wells 013,Tunbridge Wells 013A,ISI,1.0,119.0,Dulwich Prep Cranbrook,,South-East England and South London,,10000064585.0,,Not applicable,Not applicable,,,E02005174,E01024787,0.0,
|
||||
118991,886,Kent,6044,Cobham Hall,Other independent school,Independent schools,Open,Not applicable,18-10-1962,Not applicable,,Not applicable,11.0,19,Boarding school,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,250.0,No Special Classes,20-01-2022,137.0,1.0,136.0,0.0,Not applicable,,Not applicable,,Not applicable,,10001524.0,,Not applicable,,23-04-2024,Cobham Hall,Brewers Road,Gravesend,,Kent,DA12 3BL,www.cobhamhall.com,1474823371.0,Headteacher,Wendy,Barrett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Gravesham,"Istead Rise, Cobham & Luddesdown",Gravesham,(England/Wales) Rural hamlet and isolated dwellings,E10000016,568367.0,168915.0,Gravesham 010,Gravesham 010E,ISI,3.0,27.0,The Mill Hill School Foundation,,South-East England and South London,United Kingdom,10012014498.0,,Not applicable,Not applicable,,,E02005064,E01024301,0.0,
|
||||
118992,886,Kent,6045,Spring Grove School 2003 Ltd,Other independent school,Independent schools,Open,Not applicable,03-11-1967,Not applicable,,Not applicable,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,245.0,No Special Classes,20-01-2022,225.0,107.0,118.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080530.0,,Not applicable,,08-05-2024,Harville Road,Wye,,Ashford,Kent,TN25 5EZ,www.springgroveschool.co.uk,1233812337.0,Mrs,Therésa,Jaggard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Ashford,Wye with Hinxhill,Ashford,(England/Wales) Rural hamlet and isolated dwellings,E10000016,604242.0,146688.0,Ashford 001,Ashford 001E,ISI,1.0,19.0,,,South-East England and South London,,200004393352.0,,Not applicable,Not applicable,,,E02004996,E01024041,0.0,
|
||||
118996,886,Kent,6048,The King's School Canterbury,Other independent school,Independent schools,Open,Not applicable,01-01-1908,Not applicable,,Not applicable,13.0,18,Boarding school,No Nursery Classes,Has a sixth form,Mixed,Church of England,Church of England,Not applicable,Selective,960.0,No Special Classes,20-01-2022,943.0,485.0,458.0,0.0,Not applicable,,Not applicable,,Not applicable,,10008320.0,,Not applicable,,08-05-2024,25 The Precincts,,,Canterbury,Kent,CT1 2ES,www.kings-school.co.uk,1227595501.0,Ms,Jude,Lowson,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Canterbury,Westgate,Canterbury,(England/Wales) Urban city and town,E10000016,615194.0,158056.0,Canterbury 020,Canterbury 020D,ISI,,220.0,The King’s School Governors,,South-East England and South London,,100062279503.0,,Not applicable,Not applicable,,,E02006856,E01024124,0.0,
|
||||
118998,886,Kent,6050,St Edmund's School Canterbury,Other independent school,Independent schools,Open,Not applicable,01-01-1918,Not applicable,,Not applicable,2.0,19,Boarding school,Has Nursery Classes,Has a sixth form,Mixed,Church of England,Christian,Not applicable,Not applicable,670.0,No Special Classes,20-01-2022,631.0,335.0,296.0,0.0,Not applicable,,Not applicable,,Not applicable,,10013984.0,,Not applicable,,26-04-2024,St Thomas Hill,,,Canterbury,Kent,CT2 8HU,http://www.stedmunds.org.uk/,1227475600.0,Mr,Edward,O'Connor,Head,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,Not applicable,,,,,South East,Canterbury,Blean Forest,Canterbury,(England/Wales) Urban city and town,E10000016,613410.0,159177.0,Canterbury 012,Canterbury 012E,ISI,11.0,80.0,,,South-East England and South London,,100062619208.0,,Not applicable,Not applicable,,,E02005021,E01024123,0.0,
|
||||
119001,886,Kent,6053,Kent College (Canterbury),Other independent school,Independent schools,Open,Not applicable,30-09-1980,Not applicable,,Not applicable,11.0,19,Boarding school,Not applicable,Has a sixth form,Mixed,Methodist,Methodist,Not applicable,Not applicable,600.0,Has Special Classes,20-01-2022,574.0,311.0,263.0,0.0,Not applicable,,Not applicable,,Not applicable,,10016349.0,,Not applicable,,23-04-2024,Whitstable Road,,,Canterbury,Kent,CT2 9DT,,1227763231.0,Mr,Mark,Turnbull,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Canterbury,Blean Forest,Canterbury,(England/Wales) Urban city and town,E10000016,613117.0,159367.0,Canterbury 012,Canterbury 012D,ISI,3.0,36.0,Methodist Independent Schools Trust,,South-East England and South London,,100062293085.0,,Not applicable,Not applicable,,,E02005021,E01024068,0.0,
|
||||
119002,886,Kent,6054,Walthamstow Hall,Other independent school,Independent schools,Open,Not applicable,30-09-1980,Not applicable,,Not applicable,2.0,19,No boarders,Has Nursery Classes,Has a sixth form,Mixed,None,Christian,Not applicable,Not applicable,700.0,No Special Classes,20-01-2022,549.0,0.0,549.0,0.0,Not applicable,,Not applicable,,Not applicable,,10007324.0,,Not applicable,,30-04-2024,Holly Bush Lane,,,Sevenoaks,Kent,TN13 3UL,www.walthamstow-hall.co.uk,1732451334.0,Ms,Louise,Chamberlain,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Eastern,Sevenoaks,(England/Wales) Urban city and town,E10000016,553388.0,155609.0,Sevenoaks 010,Sevenoaks 010C,ISI,84.0,,,,South-East England and South London,,100062547957.0,,Not applicable,Not applicable,,,E02005096,E01024461,0.0,
|
||||
119007,886,Kent,6058,Sackville School,Other independent school,Independent schools,Open,Not applicable,17-09-1987,Not applicable,,Not applicable,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Not applicable,242.0,No Special Classes,20-01-2022,206.0,158.0,48.0,0.0,Not applicable,,Not applicable,,Not applicable,,10017557.0,,Not applicable,,10-04-2024,Tonbridge Road,Hildenborough,,Tonbridge,Kent,TN11 9HN,www.sackvilleschool.co.uk,1732838888.0,Mrs,Leoni,Ellis,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Hildenborough,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,556338.0,148693.0,Tonbridge and Malling 010,Tonbridge and Malling 010D,ISI,51.0,23.0,,,South-East England and South London,,200000963023.0,,Not applicable,Not applicable,,,E02005158,E01024754,0.0,
|
||||
119008,886,Kent,6059,St Faith's At Ash School Limited,Other independent school,Independent schools,Open,Not applicable,28-10-1987,Not applicable,,Not applicable,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,260.0,No Special Classes,20-01-2022,228.0,116.0,112.0,0.0,Not applicable,,Not applicable,,Not applicable,,10075226.0,,Not applicable,,22-04-2024,5 The Street,Ash,,Canterbury,Kent,CT3 2HH,www.stfaithsprep.com,1304813409.0,Mrs,Helen,Coombs,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,SpLD - Specific Learning Difficulty,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,MSI - Multi-Sensory Impairment,,,,,,,,,,Resourced provision,28.0,30.0,,,South East,Dover,Little Stour & Ashstone,South Thanet,(England/Wales) Rural town and fringe,E10000016,628411.0,158389.0,Dover 001,Dover 001B,ISI,4.0,19.0,St Faith's At Ash School Ltd,,South-East England and South London,,100060886066.0,,Not applicable,Not applicable,,,E02005041,E01024207,0.0,
|
||||
119010,886,Kent,6061,Junior King's School,Other independent school,Independent schools,Open,Not applicable,16-10-1989,Not applicable,,Not applicable,2.0,14,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Church of England,Not applicable,Not applicable,386.0,No Special Classes,20-01-2022,381.0,209.0,172.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018042.0,,Not applicable,,08-05-2024,Milner Court,Sturry,,Canterbury,Kent,CT2 0AY,www.junior-kings.co.uk,1227714000.0,Mrs,Emma,Karolyi,Head,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Canterbury,Sturry,Canterbury,(England/Wales) Rural town and fringe,E10000016,617596.0,160193.0,Canterbury 011,Canterbury 011E,ISI,1.0,16.0,,,South-East England and South London,,200000682998.0,,Not applicable,Not applicable,,,E02005020,E01024113,0.0,
|
||||
119014,886,Kent,6064,Lorenden Preparatory School,Other independent school,Independent schools,Open,Not applicable,12-10-1993,Not applicable,,Not applicable,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Christian,Not applicable,Non-selective,140.0,No Special Classes,20-01-2022,120.0,60.0,60.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080527.0,,Not applicable,,12-04-2024,Painters Forstal Road,Painters Forstal,,,,ME13 0EN,www.lorenden.org,1795590030.0,Mr,Richard,McIntosh,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Swale,East Downs,Faversham and Mid Kent,(England/Wales) Rural hamlet and isolated dwellings,E10000016,599351.0,159200.0,Swale 016,Swale 016A,ISI,1.0,19.0,Methodist Independent Schools Trust,,South-East England and South London,,200002534108.0,,Not applicable,Not applicable,,,E02005130,E01024565,0.0,
|
||||
119020,886,Kent,6069,"Kent College Nursery, Infant and Junior School",Other independent school,Independent schools,Open,Not applicable,23-09-1980,Not applicable,,Not applicable,3.0,11,Boarding school,Has Nursery Classes,Does not have a sixth form,Mixed,None,Church of England/Methodist,Not applicable,Non-selective,240.0,Has Special Classes,20-01-2022,222.0,122.0,100.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080525.0,,Not applicable,,23-04-2024,Harbledown,Canterbury,,Canterbury,Kent,CT2 9AQ,,1227762436.0,Mr,Simon,James,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Canterbury,Chartham & Stone Street,Canterbury,(England/Wales) Rural village,E10000016,612417.0,158066.0,Canterbury 012,Canterbury 012C,ISI,,22.0,,,South-East England and South London,,200000690375.0,,Not applicable,Not applicable,,,E02005021,E01024067,0.0,
|
||||
130938,886,Kent,2682,New Ash Green Primary School,Community school,Local authority maintained schools,Open,Not applicable,01-09-1997,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,413.0,212.0,201.0,13.3,Not applicable,,Not applicable,,Not under a federation,,10075572.0,,Not applicable,25-02-2022,01-02-2024,North Square,New Ash Green,,Longfield,Kent,DA3 8JT,http://www.new-ash.kent.sch.uk,1474873858.0,Mrs,Caroline,Cain,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Ash and New Ash Green,Sevenoaks,(England/Wales) Urban city and town,E10000016,560624.0,165724.0,Sevenoaks 016,Sevenoaks 016A,,,,,Good,South-East England and South London,,100062314601.0,,Not applicable,Not applicable,,,E02006832,E01024413,55.0,
|
||||
130948,886,Kent,3298,St. Edmund's Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,01-09-1996,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,Not applicable,19-01-2023,169.0,88.0,81.0,30.2,Not applicable,,Not applicable,,Supported by a federation,The Compass Federation,10075571.0,,Not applicable,14-11-2018,09-04-2024,Fawkham Road,West Kingsdown,,Sevenoaks,Kent,TN15 6JP,www.st-edmunds.kent.sch.uk,1474853484.0,Mr,Benjamin,Hulme,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Fawkham and West Kingsdown,Sevenoaks,(England/Wales) Rural town and fringe,E10000016,558036.0,162680.0,Sevenoaks 007,Sevenoaks 007A,,,,,Good,South-East England and South London,,100062550147.0,,Not applicable,Not applicable,,,E02005093,E01024435,51.0,
|
||||
130952,886,Kent,2680,Kings Hill School Primary and Nursery,Community school,Local authority maintained schools,Open,Not applicable,01-09-1997,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,485.0,229.0,256.0,7.4,Not applicable,,Not applicable,,Not under a federation,,10075570.0,,Not applicable,21-02-2024,20-05-2024,Crispin Way,Kings Hill,,West Malling,Kent,ME19 4LS,www.kingshillschool.org.uk/,1732842739.0,Miss,Lottie,Barnden,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Kings Hill,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,567411.0,155232.0,Tonbridge and Malling 007,Tonbridge and Malling 007F,,,,,Outstanding,South-East England and South London,,10002912179.0,,Not applicable,Not applicable,,,E02005155,E01032826,36.0,
|
||||
131020,886,Kent,3902,Hythe Bay CofE Primary School,Voluntary controlled school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2006,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,Has Special Classes,19-01-2023,297.0,170.0,127.0,38.7,Not applicable,,Not applicable,,Not under a federation,,10075218.0,,Not applicable,25-01-2023,09-04-2024,Cinque Ports Avenue,,,Hythe,Kent,CT21 6HS,www.hythebay.kent.sch.uk,1303267802.0,Mrs,Carolyn,Chivers,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,21.0,22.0,,,South East,Folkestone and Hythe,Hythe,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,615731.0,134394.0,Folkestone and Hythe 010,Folkestone and Hythe 010B,,,,,Good,South-East England and South London,,50113532.0,,Not applicable,Not applicable,,,E02005111,E01024524,115.0,
|
||||
131181,886,Kent,6073,Beech Grove School,Other independent school,Independent schools,Open,Not applicable,21-02-1997,Not applicable,,Not applicable,4.0,19,Boarding school,No Nursery Classes,Has a sixth form,Mixed,Christian,Christian,Not applicable,Selective,120.0,Not applicable,20-01-2022,83.0,48.0,35.0,0.0,Not applicable,,Not applicable,,Not applicable,,10043893.0,,Not applicable,06-12-2019,03-06-2024,Forest Drive,,Nonington,Dover,Kent,CT15 4FB,beechgroveschool.co.uk,1304842980.0,Mr,Jeffrey,Maendel,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Dover,"Aylesham, Eythorne & Shepherdswell",Dover,(England/Wales) Rural village,E10000016,626361.0,152647.0,Dover 006,Dover 006A,Ofsted,,5.0,Church Communities UK,Good,South-East England and South London,,100060912465.0,,Not applicable,Not applicable,,,E02005046,E01024190,0.0,
|
||||
131411,886,Kent,6075,The Worthgate School,Other independent school,Independent schools,Open,Not applicable,06-11-1997,Not applicable,,Not applicable,13.0,22,Boarding school,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Selective,500.0,Not applicable,20-01-2022,304.0,136.0,168.0,0.0,Not applicable,,Not applicable,,Not applicable,,10008526.0,,Not applicable,,13-05-2024,68 New Dover Road,,,Canterbury,Kent,CT1 3LQ,www.worthgateschool.com,1227866540.0,Dr,Ian,Gross,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,615887.0,156832.0,Canterbury 016,Canterbury 016C,ISI,,14.0,CEG Colleges Limited,,South-East England and South London,,100062280087.0,,Not applicable,Not applicable,,,E02005025,E01024046,0.0,
|
||||
131567,886,Kent,6113,St Helens Montessori School,Other independent school,Independent schools,Open,New Provision,13-04-2006,Not applicable,,Not applicable,2.0,12,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Christian,Not applicable,Not applicable,130.0,Not applicable,20-01-2022,45.0,19.0,26.0,0.0,Not applicable,,Not applicable,,Not applicable,,10071874.0,,Not applicable,08-07-2021,15-04-2024,Lower Road,East Farleigh,,Maidstone,Kent,ME15 0JT,www.sthelensmontessori.co.uk,1622721731.0,Miss,Jeannelle,Dening Smitherman,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Maidstone,Coxheath and Hunton,Maidstone and The Weald,(England/Wales) Rural village,E10000016,572488.0,153442.0,Maidstone 014,Maidstone 014B,Ofsted,,1.0,Marie-Elise Jeannelle Dening-Smitherman,Good,South-East England and South London,,200003673434.0,,Not applicable,Not applicable,,,E02005081,E01024346,0.0,
|
||||
132764,886,Kent,2689,The Craylands School,Community school,Local authority maintained schools,Open,New Provision,01-09-2003,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,417.0,224.0,193.0,23.7,Not applicable,,Not applicable,,Not under a federation,,10074200.0,,Not applicable,25-09-2019,01-05-2024,Craylands Lane,,,Swanscombe,Kent,DA10 0LP,http://www.craylands.kent.sch.uk,1322388230.0,Mr,Kris,Hiscock,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Swanscombe,Dartford,(England/Wales) Urban major conurbation,E10000016,560051.0,174464.0,Dartford 004,Dartford 004D,,,,,Good,South-East England and South London,,10002021725.0,,Not applicable,Not applicable,,,E02005031,E01024178,99.0,
|
||||
133177,886,Kent,3904,Castle Hill Community Primary School,Community school,Local authority maintained schools,Open,Fresh Start,01-01-2007,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,406.0,Has Special Classes,19-01-2023,406.0,220.0,186.0,54.8,Not applicable,,Not applicable,,Not under a federation,,10076676.0,,Not applicable,13-10-2021,21-05-2024,Sidney Street,,,Folkestone,Kent,CT19 6HG,www.castlehill.kent.sch.uk/,1303251583.0,Mr,Peter,Talbot,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,,,,,,,,,,,,,Resourced provision,15.0,12.0,,,South East,Folkestone and Hythe,East Folkestone,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,623192.0,137077.0,Folkestone and Hythe 004,Folkestone and Hythe 004A,,,,,Requires improvement,South-East England and South London,,50049270.0,,Not applicable,Not applicable,,,E02005105,E01024499,194.0,
|
||||
133627,886,Kent,3299,The John Wesley Church of England Methodist Voluntary Aided Primary School,Voluntary aided school,Local authority maintained schools,Open,New Provision,01-09-2007,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England/Methodist,Does not apply,Diocese of Canterbury,Not applicable,420.0,Not applicable,19-01-2023,462.0,231.0,231.0,20.1,Not applicable,,Not applicable,,Not under a federation,,10069422.0,,Not applicable,11-11-2021,18-03-2024,Wesley School Road,Cuckoo Lane,Singleton,Ashford,Kent,TN23 5LW,www.john-wesley.org.uk,1233614660.0,Miss,Rachael,Harrington,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,10.0,14.0,,,South East,Ashford,Washford,Ashford,(England/Wales) Urban city and town,E10000016,598815.0,141017.0,Ashford 007,Ashford 007E,,,,,Good,South-East England and South London,,10012841657.0,,Not applicable,Not applicable,,,E02005002,E01024017,93.0,
|
||||
133961,886,Kent,3893,Phoenix Community Primary School,Foundation school,Local authority maintained schools,Open,Result of Amalgamation,01-04-2003,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,213.0,95.0,118.0,43.7,Supported by a trust,CARE Foundation Trust,Not applicable,,Not under a federation,,10074158.0,,Not applicable,29-06-2022,12-03-2024,Belmont Road,Kennington,,Ashford,Kent,TN24 9LS,www.phoenix-primary.kent.sch.uk/,1233622510.0,Mr,Leon,Robichaud,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Bybrook,Ashford,(England/Wales) Urban city and town,E10000016,601583.0,144521.0,Ashford 015,Ashford 015C,,,,,Good,South-East England and South London,,100062561305.0,,Not applicable,Not applicable,,,E02007046,E01023984,93.0,
|
||||
134057,886,Kent,2065,The Discovery School,Community school,Local authority maintained schools,Open,New Provision,01-09-2003,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,625.0,317.0,308.0,7.5,Not applicable,,Not applicable,,Not under a federation,,10074152.0,,Not applicable,22-02-2023,22-05-2024,Discovery Drive,Kings Hill,,West Malling,Kent,ME19 4GJ,www.discovery.kent.sch.uk,1732847000.0,Miss,Tina,Gobell,Interim Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Kings Hill,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,568757.0,155312.0,Tonbridge and Malling 007,Tonbridge and Malling 007G,,,,,Outstanding,South-East England and South London,,10002911481.0,,Not applicable,Not applicable,,,E02005155,E01032827,47.0,
|
||||
134452,886,Kent,6104,OneSchool Global UK - Maidstone Campus,Other independent school,Independent schools,Open,New Provision,29-08-2003,Not applicable,,Not applicable,7.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Plymouth Brethren Christian Church,Christian,Not applicable,Not applicable,225.0,Not applicable,20-01-2022,163.0,81.0,82.0,0.0,Not applicable,,Not applicable,,Not applicable,,10018081.0,,Not applicable,,06-06-2024,Heath Road,Linton,,Maidstone,Kent,ME17 4HT,https://www.oneschoolglobal.com/campus/united-kingdom/maidstone/,3000700507.0,Mrs,Keryn,Van Der Westhuizen,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Maidstone,Coxheath and Hunton,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575491.0,154914.0,Maidstone 018,Maidstone 018A,ISI,2.0,10.0,OneSchool Global UK,,South-East England and South London,,10014308592.0,,Not applicable,Not applicable,,,E02005085,E01024345,0.0,
|
||||
134515,886,Kent,3896,Downsview Community Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-01-2004,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,174.0,97.0,77.0,39.7,Not applicable,,Not applicable,,Not under a federation,,10074133.0,,Not applicable,26-04-2023,13-09-2023,Beech Avenue,,,Swanley,Kent,BR8 8AU,www.downsview-primary.kent.sch.uk/,1322662594.0,Mr,Richard,Moore,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Swanley Christchurch and Swanley Village,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,552107.0,168558.0,Sevenoaks 003,Sevenoaks 003B,,,,,Requires improvement,South-East England and South London,,100062276972.0,,Not applicable,Not applicable,,,E02005089,E01024473,69.0,
|
||||
134582,886,Kent,6097,Kent College International Study Centre,Other independent school,Independent schools,Open,New Provision,20-08-2003,Not applicable,,Not applicable,11.0,18,Boarding school,No Nursery Classes,Has a sixth form,Mixed,None,Methodist,Not applicable,Not applicable,100.0,Not applicable,20-01-2022,18.0,14.0,4.0,0.0,Not applicable,,Not applicable,,Not applicable,,10016222.0,,Not applicable,,23-04-2024,Whitstable Road,,,Canterbury,Kent,CT2 9DT,,1227763231.0,Mr,Mark,Turnbull,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Canterbury,Blean Forest,Canterbury,(England/Wales) Urban city and town,E10000016,613117.0,159367.0,Canterbury 012,Canterbury 012D,ISI,,,Methodist Secondary Trustees,,South-East England and South London,,100062293085.0,,Not applicable,Not applicable,,,E02005021,E01024068,0.0,
|
||||
134857,886,Kent,3898,Greenfields Community Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,11-04-2005,Not applicable,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,367.0,Not applicable,19-01-2023,420.0,241.0,179.0,40.3,Not applicable,,Not applicable,,Not under a federation,,10071741.0,,Not applicable,15-05-2019,11-04-2024,Oxford Road,Shepway,,Maidstone,Kent,ME15 8DF,http://www.greenfieldscps.kent.sch.uk,1622758538.0,Mr,Daniel,Andrews,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Shepway North,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,578070.0,153905.0,Maidstone 010,Maidstone 010D,,,,,Good,South-East England and South London,,200003717312.0,,Not applicable,Not applicable,,,E02005077,E01024395,143.0,
|
||||
135106,886,Kent,3906,Palace Wood Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-01-2007,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,417.0,225.0,192.0,16.5,Not applicable,,Not applicable,,Not under a federation,,10072360.0,,Not applicable,15-09-2022,13-09-2023,Ash Grove,Allington,,Maidstone,Kent,ME16 0AB,www.palacewoodprimary.org.uk/,1622750084.0,Mrs,Clare,Cairns,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,Allington,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574102.0,156747.0,Maidstone 003,Maidstone 003D,,,,,Good,South-East England and South London,,200003659713.0,,Not applicable,Not applicable,,,E02005070,E01024323,69.0,
|
||||
135118,886,Kent,3907,Hextable Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2007,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,609.0,320.0,289.0,17.9,Not applicable,,Not applicable,,Not under a federation,,10072357.0,,Not applicable,27-09-2023,04-06-2024,Rowhill Road,Hextable,,Swanley,Kent,BR8 7RL,www.hextable-primary.kent.sch.uk/,1322663792.0,Mrs,Suzie,Hall,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Hextable,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,551783.0,170689.0,Sevenoaks 001,Sevenoaks 001A,,,,,Good,South-East England and South London,,10013768802.0,,Not applicable,Not applicable,,,E02005087,E01024445,109.0,
|
||||
135125,886,Kent,3909,Ashford Oaks Community Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2008,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,460.0,238.0,222.0,51.0,Not applicable,,Not applicable,,Not under a federation,,10071720.0,,Not applicable,29-03-2023,15-04-2024,Oak Tree Road,,,Ashford,Kent,TN23 4QR,http://www.ashfordoaks.kent.sch.uk/,1223631259.0,Mr,Phil,Chantler,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,"SEMH - Social, Emotional and Mental Health",MLD - Moderate Learning Difficulty,,,,,,,,,,Resourced provision,13.0,8.0,,,South East,Ashford,Beaver,Ashford,(England/Wales) Urban city and town,E10000016,599912.0,141837.0,Ashford 007,Ashford 007B,,,,,Good,South-East England and South London,,100062559273.0,,Not applicable,Not applicable,,,E02005002,E01023975,221.0,
|
||||
135130,886,Kent,3910,Joy Lane Primary Foundation School,Foundation school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2007,Not applicable,,Primary,1.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Has Special Classes,19-01-2023,619.0,327.0,292.0,24.7,Supported by a trust,The Coastal Alliance Co-operative Trust,Not applicable,,Not under a federation,,10076634.0,,Not applicable,19-10-2018,02-04-2024,Joy Lane,,,Whitstable,Kent,CT5 4LT,www.joylane.kent.sch.uk/,1227261430.0,Ms,Debra,Hines,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,34.0,30.0,,,South East,Canterbury,Seasalter,Canterbury,(England/Wales) Urban city and town,E10000016,610128.0,165477.0,Canterbury 008,Canterbury 008E,,,,,Good,South-East England and South London,,200002882935.0,,Not applicable,Not applicable,,,E02005017,E01024106,153.0,
|
||||
135164,886,Kent,3913,Rusthall St Paul's CofE VA Primary School,Voluntary aided school,Local authority maintained schools,"Open, but proposed to close",Result of Amalgamation,01-09-2007,Academy Converter,31-08-2024,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,150.0,No Special Classes,19-01-2023,138.0,79.0,59.0,31.9,Not applicable,,Not applicable,,Not under a federation,,10072353.0,,Not applicable,20-04-2023,13-05-2024,High Street,Rusthall,,Tunbridge Wells,Kent,TN4 8RZ,www.rusthall-cep.kent.sch.uk,1892520582.0,Mrs,Lyndsay,Smurthwaite,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Rusthall,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,555920.0,139686.0,Tunbridge Wells 010,Tunbridge Wells 010C,,,,,Requires improvement,South-East England and South London,,10008659425.0,,Not applicable,Not applicable,,,E02005171,E01024830,44.0,
|
||||
135197,886,Kent,3916,Green Park Community Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-08-2007,Not applicable,,Primary,4.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,390.0,206.0,184.0,45.6,Not applicable,,Not applicable,,Not under a federation,,10076633.0,,Not applicable,25-05-2023,17-04-2024,The Linces,Buckland,Green Park Community Primary School,Dover,Kent,CT16 2BN,www.greenparkcps.co.uk/,1304822663.0,Mr,Richard,Hawkins,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,,,,,South East,Dover,Buckland,Dover,(England/Wales) Urban city and town,E10000016,630734.0,143609.0,Dover 011,Dover 011D,,,,,Outstanding,South-East England and South London,,100062289415.0,,Not applicable,Not applicable,,,E02005051,E01024196,178.0,
|
||||
135212,886,Kent,3917,Garlinge Primary School and Nursery,Foundation school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2007,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,840.0,No Special Classes,19-01-2023,769.0,406.0,363.0,41.6,Supported by a trust,Thanet Endeavour Learning Trust,Not applicable,,Not under a federation,,10076631.0,,Not applicable,29-11-2023,08-05-2024,Westfield Road,,,Margate,Kent,CT9 5PA,www.garlingeprimary.co.uk,1843221877.0,Mr,James,Williams,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,PD - Physical Disability,,,,,,,,,,,,Resourced provision,22.0,23.0,,,South East,Thanet,Garlinge,North Thanet,(England/Wales) Urban city and town,E10000016,633980.0,169704.0,Thanet 005,Thanet 005C,,,,,Good,South-East England and South London,,100062307988.0,,Not applicable,Not applicable,,,E02005136,E01024674,310.0,
|
||||
135214,886,Kent,3918,Newington Community Primary School,Community school,Local authority maintained schools,"Open, but proposed to close",Result of Amalgamation,01-09-2007,Academy Converter,30-06-2024,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,694.0,No Special Classes,19-01-2023,703.0,370.0,333.0,43.3,Not applicable,,Not applicable,,Not under a federation,,10071712.0,,Not applicable,16-03-2017,16-05-2024,Princess Margaret Avenue,,,Ramsgate,Kent,CT12 6HX,www.newington-ramsgate.org.uk/,1843593412.0,Headteacher,Hannah,Tudor,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Newington,South Thanet,(England/Wales) Urban city and town,E10000016,636335.0,165855.0,Thanet 013,Thanet 013A,,,,,Outstanding,South-East England and South London,United Kingdom,100062284319.0,,Not applicable,Not applicable,,,E02005144,E01024682,290.0,
|
||||
135290,886,Kent,6909,The Marsh Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2007,Not applicable,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1110.0,No Special Classes,19-01-2023,1064.0,533.0,531.0,30.0,Supported by a multi-academy trust,SKINNERS' ACADEMIES TRUST,Linked to a sponsor,The Skinners' Company,Not applicable,,10021032.0,,Not applicable,16-11-2022,30-05-2024,Station Road,,,New Romney,Kent,TN28 8BB,www.marshacademy.org.uk,1797364593.0,Mr,Shaun,Simmons,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,20.0,24.0,,,South East,Folkestone and Hythe,New Romney,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,607023.0,124940.0,Folkestone and Hythe 012,Folkestone and Hythe 012C,,,,,Good,South-East England and South London,,50002925.0,,Not applicable,Not applicable,,,E02005113,E01024539,270.0,
|
||||
135297,886,Kent,6910,The Leigh Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2007,Not applicable,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1500.0,No Special Classes,19-01-2023,1359.0,768.0,591.0,23.6,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10021033.0,,Not applicable,26-04-2023,30-04-2024,Green Street Green Road,,,Dartford,Kent,DA1 1QE,http://www.leighacademy.org.uk/,1322620400.0,Mrs,Julia,Collins,Academy Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,"SLCN - Speech, language and Communication",,,,,,,,,,,,Resourced provision,16.0,16.0,,,South East,Dartford,Brent,Dartford,(England/Wales) Urban major conurbation,E10000016,555487.0,173173.0,Dartford 008,Dartford 008B,,,,,Good,South-East England and South London,,100062308614.0,,Not applicable,Not applicable,,,E02005035,E01024136,272.0,
|
||||
135305,886,Kent,6911,Spires Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2007,Not applicable,,Secondary,11.0,16,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,750.0,No Special Classes,19-01-2023,690.0,324.0,366.0,37.4,Supported by a multi-academy trust,EDUCATION FOR THE 21ST CENTURY,Linked to a sponsor,Education for the 21st Century,Not applicable,,10021093.0,,Not applicable,11-01-2023,26-09-2023,Bredlands Lane,Sturry,,Canterbury,Kent,CT2 0HD,http://www.spiresacademy.com/,1227710392.0,Mrs,Anna,Burden,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Sturry,Canterbury,(England/Wales) Rural hamlet and isolated dwellings,E10000016,619494.0,161797.0,Canterbury 011,Canterbury 011B,,,,,Good,South-East England and South London,,10090317463.0,,Not applicable,Not applicable,,,E02005020,E01024110,258.0,
|
||||
135371,886,Kent,6913,Cornwallis Academy,Academy sponsor led,Academies,Open,New Provision,03-09-2007,Not applicable,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1825.0,No Special Classes,19-01-2023,1337.0,761.0,576.0,27.8,Supported by a multi-academy trust,FUTURE SCHOOLS TRUST,Linked to a sponsor,Future Schools Trust,Not applicable,,10021031.0,,Not applicable,12-01-2023,22-04-2024,Hubbards Lane,Linton,,Maidstone,Kent,ME17 4HX,http://www.futureschoolstrust.com/,1622743152.0,Mrs,Samantha,McMahon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Loose,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575978.0,150975.0,Maidstone 016,Maidstone 016D,,,,,Good,South-East England and South London,,200003720219.0,,Not applicable,Not applicable,,,E02005083,E01024376,277.0,
|
||||
135372,886,Kent,6912,New Line Learning Academy,Academy sponsor led,Academies,Open,New Provision,03-09-2007,Not applicable,,Secondary,11.0,16,No boarders,Not applicable,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1050.0,No Special Classes,19-01-2023,719.0,372.0,347.0,43.7,Supported by a multi-academy trust,FUTURE SCHOOLS TRUST,Linked to a sponsor,Future Schools Trust,Not applicable,,10021098.0,,Not applicable,13-11-2019,21-05-2024,Boughton Lane,,,Maidstone,Kent,ME15 9QL,https://www.newlinelearning.com/,1622743286.0,Ms,Sharry,Mackie,Interim Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,VI - Visual Impairment,PD - Physical Disability,,,,,,,,,,,,Resourced provision,6.0,12.0,,,South East,Maidstone,South,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576813.0,153053.0,Maidstone 012,Maidstone 012D,,,,,Good,South-East England and South London,,200003677498.0,,Not applicable,Not applicable,,,E02005079,E01024404,313.0,
|
||||
135432,886,Kent,1123,The Rosewood School,Pupil referral unit,Local authority maintained schools,Open,New Provision,01-11-2007,Not applicable,,Not applicable,11.0,18,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,120.0,Has Special Classes,19-01-2023,0.0,0.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10025535.0,,Not applicable,23-06-2022,17-11-2023,40 Teddington Drive,Leybourne,,West Malling,Kent,ME19 5FF,http://www.trs.kent.sch.uk/,1732875694.0,Mrs,Tina,Hamer,Executive Headteacher,Not applicable,,,,Provides places for Teen Mothers,,Does not have child care facilities,PRU Does have Provision for SEN,PRU Does not have EBD provision,120.0,PRU offers full time provision,Does not offer tuition by another provider,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"Birling, Leybourne & Ryarsh",Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,567730.0,159105.0,Tonbridge and Malling 014,Tonbridge and Malling 014G,,,,,Good,South-East England and South London,,100062387483.0,,Not applicable,Not applicable,,,E02006833,E01035010,0.0,
|
||||
135462,886,Kent,1124,Birchwood,Pupil referral unit,Local authority maintained schools,Open,,01-01-2008,Not applicable,,Not applicable,14.0,16,Not applicable,Not applicable,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,44.0,Has Special Classes,19-01-2023,0.0,0.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10025590.0,,Not applicable,06-02-2019,07-05-2024,Bowen Road,,,Folkestone,Kent,CT19 4FP,www.birchwoodpru.kent.sch.uk,3000658450.0,Ms,Jane,Waters,Headteacher,Not applicable,,,,Does not provide places for Teen Mothers,,Does not have child care facilities,PRU Does have Provision for SEN,PRU Does have EBD provision,92.0,PRU offers full time provision,Does not offer tuition by another provider,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,620521.0,137026.0,Folkestone and Hythe 006,Folkestone and Hythe 006B,,,,,Good,South-East England and South London,United Kingdom,50125207.0,,Not applicable,Not applicable,,,E02005107,E01024512,0.0,
|
||||
135465,886,Kent,1127,Maidstone and Malling Alternative Provision,Pupil referral unit,Local authority maintained schools,Open,,01-01-2008,Not applicable,,Not applicable,12.0,17,Not applicable,Not applicable,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,52.0,Not applicable,19-01-2023,0.0,0.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10025522.0,,Not applicable,06-11-2019,05-06-2024,8 Bower Mount Road,,,Maidstone,Kent,ME16 8AU,www.m-map.co.uk,1622753772.0,Mrs,Stacie,Smith,Headteacher,Not applicable,,,,Does not provide places for Teen Mothers,,Does not have child care facilities,PRU Does have Provision for SEN,PRU Does have EBD provision,52.0,PRU offers full time provision,PRU does offer tuition by another provider,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Bridge,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574902.0,155729.0,Maidstone 006,Maidstone 006B,,,,,Good,South-East England and South London,,200003728228.0,,Not applicable,Not applicable,,,E02005073,E01024340,0.0,
|
||||
135466,886,Kent,1128,Enterprise Learning Alliance,Pupil referral unit,Local authority maintained schools,Open,,01-01-2008,Not applicable,,Not applicable,11.0,16,Not applicable,Not applicable,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,140.0,Not applicable,19-01-2023,0.0,0.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10025536.0,,Not applicable,06-06-2019,10-05-2024,Westwood Centre,Westwood Industrial Estate,Enterprise Road,Margate,Kent,CT9 4JA,www.ela.kent.sch.uk,1843606666.0,Mrs,Micheala,Clay,Executive Head Teacher,Not applicable,,,,Does not provide places for Teen Mothers,,Does not have child care facilities,PRU Does have Provision for SEN,PRU Does have EBD provision,168.0,PRU offers full time provision,Does not offer tuition by another provider,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Salmestone,North Thanet,(England/Wales) Urban city and town,E10000016,636075.0,168581.0,Thanet 004,Thanet 004E,,,,,Good,South-East England and South London,,10013309226.0,,Not applicable,Not applicable,,,E02005135,E01024696,0.0,
|
||||
135467,886,Kent,1129,Two Bridges School,Pupil referral unit,Local authority maintained schools,Open,,01-01-2008,Not applicable,,Not applicable,11.0,18,Not applicable,Not applicable,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,,Not applicable,19-01-2023,0.0,0.0,0.0,0.0,Not applicable,,Not applicable,,Not applicable,,10025613.0,,Not applicable,28-02-2024,22-05-2024,Charles Street,Southborough,Two Bridges School,Tunbridge Wells,Kent,TN4 0DS,www.twobridgesschool.com,1892518461.0,Mrs,Kate,Middleton,Head Teacher,Not applicable,,,,Does not provide places for Teen Mothers,,Does not have child care facilities,PRU Does have Provision for SEN,PRU Does have EBD provision,94.0,PRU offers full time provision,PRU does offer tuition by another provider,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Southborough and High Brooms,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558073.0,141848.0,Tunbridge Wells 002,Tunbridge Wells 002C,,,,,Special Measures,South-East England and South London,,100062585348.0,,Not applicable,Not applicable,,,E02005163,E01024846,0.0,
|
||||
135630,886,Kent,6914,Longfield Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2008,Not applicable,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1150.0,No Special Classes,19-01-2023,1035.0,533.0,502.0,25.1,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10024300.0,,Not applicable,27-09-2023,08-05-2024,Main Road,,,Longfield,Kent,DA3 7PH,http://www.longfieldacademy.org,1474700700.0,Dr,Felix,Donkor,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,37.0,40.0,,,South East,Dartford,"Longfield, New Barn & Southfleet",Dartford,(England/Wales) Urban city and town,E10000016,560573.0,168906.0,Dartford 013,Dartford 013B,,,,,Good,South-East England and South London,,200000538016.0,,Not applicable,Not applicable,,,E02005040,E01024158,224.0,
|
||||
135721,886,Kent,6915,Oasis Academy Isle of Sheppey,Academy sponsor led,Academies,"Open, but proposed to close",New Provision,01-09-2009,Result of Amalgamation/Merger,31-08-2024,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,2450.0,Not applicable,19-01-2023,1486.0,763.0,723.0,49.2,Supported by a multi-academy trust,OASIS COMMUNITY LEARNING,Linked to a sponsor,Oasis Community Learning,Not applicable,,10027535.0,,Not applicable,08-06-2022,28-03-2024,Minster Road,,,Minster-on-Sea,Kent,ME12 3JQ,www.oasisacademyisleofsheppey.org,1795873591.0,Mr,Andrew,Booth,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Queenborough and Halfway,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,593655.0,172623.0,Swale 004,Swale 004A,,,,,Special Measures,South-East England and South London,,200002529794.0,,Not applicable,Not applicable,,,E02005118,E01024595,665.0,
|
||||
135888,886,Kent,6916,Skinners' Kent Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2009,Not applicable,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1150.0,Not applicable,19-01-2023,1084.0,580.0,504.0,22.8,Supported by a multi-academy trust,SKINNERS' ACADEMIES TRUST,Linked to a sponsor,The Skinners' Company,Not applicable,,10027550.0,,Not applicable,11-05-2023,21-05-2024,Sandown Park,,,Tunbridge Wells,Kent,TN2 4PY,https://www.skinnerskentacademy.org.uk/,1892534377.0,Miss,Hannah,Knowles,Executive Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Sherwood,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,560482.0,140402.0,Tunbridge Wells 005,Tunbridge Wells 005C,,,,,Good,South-East England and South London,,10008671585.0,,Not applicable,Not applicable,,,E02005166,E01024842,215.0,
|
||||
136128,886,Kent,6905,Knole Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2010,Not applicable,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1550.0,No Special Classes,19-01-2023,1373.0,671.0,702.0,17.9,Supported by a single-academy trust,KNOLE ACADEMY TRUST,Linked to a sponsor,Gordon Phillips (Knole academy),Not applicable,,10030458.0,,Not applicable,23-11-2022,10-05-2024,Knole Academy,Bradbourne Vale Road,,Sevenoaks,Kent,TN13 3LE,www.knoleacademy.org,1732454608.0,Mr,David,Collins,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Sevenoaks Northern,Sevenoaks,(England/Wales) Urban city and town,E10000016,552379.0,156485.0,Sevenoaks 011,Sevenoaks 011F,,,,,Good,South-East England and South London,,100062548177.0,,Not applicable,Not applicable,,,E02005097,E01024467,217.0,
|
||||
136177,886,Kent,6918,Duke of York's Royal Military School,Academy sponsor led,Academies,Open,New Provision,01-09-2010,Not applicable,,Secondary,11.0,18,Boarding school,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,722.0,No Special Classes,19-01-2023,501.0,299.0,202.0,1.3,Supported by a single-academy trust,DYRMS - AN ACADEMY WITH MILITARY TRADITIONS,Linked to a sponsor,Ministry of Defence,Not applicable,,10030792.0,,Not applicable,09-02-2023,30-05-2024,Duke of York's Royal Military School,,,Dover,Kent,CT15 5EQ,www.doyrms.com,1304245023.0,Mr,Alex,Foreman,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Guston, Kingsdown & St Margaret's-at-Cliffe",Dover,(England/Wales) Rural village,E10000016,632730.0,143466.0,Dover 012,Dover 012B,,,,,Good,South-East England and South London,,10034881145.0,,Not applicable,Not applicable,,,E02005052,E01024238,5.0,
|
||||
136197,886,Kent,6919,The John Wallis Church of England Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2010,Not applicable,,All-through,3.0,19,No boarders,Has Nursery Classes,Has a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Non-selective,1790.0,No Special Classes,19-01-2023,1763.0,889.0,874.0,37.8,Supported by a single-academy trust,"THE JOHN WALLIS CHURCH OF ENGLAND ACADEMY, ASHFORD",Linked to a sponsor,The Diocese of Canterbury Academies Company Limited,Not applicable,,10030997.0,,Not applicable,01-02-2024,20-05-2024,Millbank Road,Kingsnorth,,Ashford,Kent,TN23 3HG,http://www.thejohnwallisacademy.org,1233623465.0,Mr,Damian,McBeath,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Stanhope,Ashford,(England/Wales) Urban city and town,E10000016,599919.0,140348.0,Ashford 008,Ashford 008B,,,,,Good,South-East England and South London,,100062558283.0,,Not applicable,Not applicable,,,E02005003,E01024019,619.0,
|
||||
136205,886,Kent,6920,Wilmington Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2010,Not applicable,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1400.0,No Special Classes,19-01-2023,1388.0,867.0,521.0,17.8,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10031094.0,,Not applicable,05-05-2023,22-04-2024,Common Lane,,,Wilmington,Kent,DA2 7DR,www.wilmingtonacademy.org.uk,1322272111.0,Mr,Michael,Gore,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,17.0,17.0,,,South East,Dartford,Maypole & Leyton Cross,Dartford,(England/Wales) Urban major conurbation,E10000016,552526.0,172176.0,Dartford 011,Dartford 011D,,,,,Outstanding,South-East England and South London,,200000534711.0,,Not applicable,Not applicable,,,E02005038,E01024189,213.0,
|
||||
136251,886,Kent,3920,Goat Lees Primary School,Foundation school,Local authority maintained schools,Open,New Provision,01-09-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,,Not applicable,210.0,No Special Classes,19-01-2023,212.0,112.0,100.0,41.5,Not supported by a trust,,Not applicable,,Not under a federation,,10072306.0,,Not applicable,22-01-2020,05-06-2024,Hurst Road,Kennington,,Ashford,Kent,TN24 9RR,www.goatlees.kent.sch.uk/,1233630201.0,Ms,Teresa,Adams,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Goat Lees,Ashford,(England/Wales) Urban city and town,E10000016,600985.0,145239.0,Ashford 001,Ashford 001F,,,,,Good,South-East England and South London,,200004392081.0,,Not applicable,Not applicable,,,E02004996,E01032810,88.0,
|
||||
136270,886,Kent,3912,Westlands Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2010,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,540.0,No Special Classes,19-01-2023,578.0,303.0,275.0,32.4,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10031923.0,,Not applicable,26-06-2019,16-05-2024,Homewood Avenue,,,Sittingbourne,Kent,ME10 1XN,www.westlandsprimary.org.uk/,1795470862.0,Mrs,Victoria,Pettett,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Homewood,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,589625.0,163383.0,Swale 012,Swale 012E,,,,,Good,South-East England and South London,,200002527704.0,,Not applicable,Not applicable,,,E02005126,E01024632,187.0,
|
||||
136286,886,Kent,5434,Westlands School,Academy converter,Academies,Open,Academy Converter,01-09-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1604.0,Has Special Classes,19-01-2023,1784.0,935.0,849.0,22.3,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10031371.0,,Not applicable,27-02-2019,13-05-2024,Westlands Avenue,,,Sittingbourne,Kent,ME10 1PF,http://www.westlands.org.uk/,1795477475.0,Miss,Christina,Honess,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,SpLD - Specific Learning Difficulty,PD - Physical Disability,,,,,,,,,,,,Resourced provision and SEN unit,10.0,10.0,28.0,32.0,South East,Swale,Borden and Grove Park,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,588904.0,163864.0,Swale 012,Swale 012C,,,,,Good,South-East England and South London,,100062375039.0,,Not applicable,Not applicable,,,E02005126,E01024569,356.0,
|
||||
136302,886,Kent,5421,The Canterbury Academy,Academy converter,Academies,Open,Academy Converter,07-10-2010,,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1300.0,Has Special Classes,19-01-2023,1844.0,949.0,895.0,27.6,Supported by a multi-academy trust,THE CANTERBURY ACADEMY TRUST,-,,Not applicable,,10031579.0,,Not applicable,22-02-2023,18-04-2024,Knight Avenue,,,Canterbury,Kent,CT2 8QA,http://www.canterbury.kent.sch.uk/,1227463971.0,Mr,Jon,Watson,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,,,,,,,,,,,Resourced provision,47.0,47.0,,,South East,Canterbury,Westgate,Canterbury,(England/Wales) Urban city and town,E10000016,613730.0,157724.0,Canterbury 020,Canterbury 020C,,,,,Requires improvement,South-East England and South London,,100062292855.0,,Not applicable,Not applicable,,,E02006856,E01024122,311.0,
|
||||
136304,886,Kent,4031,Orchards Academy,Academy converter,Academies,Open,Academy Converter,01-11-2010,,,Secondary,11.0,18,No boarders,Not applicable,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,828.0,No Special Classes,19-01-2023,585.0,280.0,305.0,39.8,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10032208.0,,Not applicable,02-07-2021,30-04-2024,St Mary's Road,,,Swanley,Kent,BR8 7TE,http://www.orchards-tkat.org,1322665231.0,Mr,Andy,Lazenby,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,6.0,12.0,,,South East,Sevenoaks,Swanley St Mary's,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,551053.0,168589.0,Sevenoaks 002,Sevenoaks 002A,,,,,Good,South-East England and South London,,200002881968.0,,Not applicable,Not applicable,,,E02005088,E01024476,233.0,
|
||||
136305,886,Kent,4080,Highsted Grammar School,Academy converter,Academies,Open,Academy Converter,01-10-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,830.0,No Special Classes,19-01-2023,889.0,7.0,882.0,8.1,Supported by a single-academy trust,HIGHSTED ACADEMY TRUST,-,,Not applicable,,10031571.0,,Not applicable,18-01-2023,07-06-2024,Highsted Road,,,Sittingbourne,Kent,ME10 4PT,http://www.highsted.kent.sch.uk,1795424223.0,Ms,Anne,Kelly,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Woodstock,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,590779.0,162957.0,Swale 013,Swale 013A,,,,,Good,South-East England and South London,,100062376278.0,,Not applicable,Not applicable,,,E02005127,E01024606,58.0,
|
||||
136317,886,Kent,5463,Sandwich Technology School,Academy converter,Academies,Open,Academy Converter,01-11-2010,,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1368.0,No Special Classes,19-01-2023,1353.0,684.0,669.0,29.2,Supported by a single-academy trust,SANDWICH TECHNOLOGY SCHOOL,-,,Not applicable,,10032210.0,,Not applicable,02-05-2019,16-04-2024,Deal Road,,,Sandwich,Kent,CT13 0FA,http://www.sandwich-tech.kent.sch.uk,1304610000.0,Mrs,Tracey,Savage,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Sandwich,South Thanet,(England/Wales) Rural town and fringe,E10000016,632791.0,157060.0,Dover 002,Dover 002B,,,,,Good,South-East England and South London,,10034879953.0,,Not applicable,Not applicable,,,E02005042,E01024242,354.0,
|
||||
136324,886,Kent,5414,Fulston Manor School,Academy converter,Academies,Open,Academy Converter,01-10-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1104.0,No Special Classes,19-01-2023,1340.0,661.0,679.0,20.9,Supported by a multi-academy trust,FULSTON MANOR ACADEMIES TRUST,Linked to a sponsor,Fulston Manor Academies Trust,Not applicable,,10031570.0,,Not applicable,15-11-2023,05-06-2024,Brenchley Road,,,Sittingbourne,Kent,ME10 4EG,http://www.fulstonmanor.kent.sch.uk,1795475228.0,Mrs,Susie,Burden,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Woodstock,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,590750.0,162857.0,Swale 013,Swale 013G,,,,,Requires improvement,South-East England and South London,,100062376597.0,,Not applicable,Not applicable,,,E02005127,E01032737,222.0,
|
||||
136344,886,Kent,2654,The Canterbury Primary School,Academy converter,Academies,Open,Academy Converter,07-10-2010,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,None,Does not apply,Not applicable,Not applicable,435.0,Has Special Classes,19-01-2023,408.0,209.0,199.0,41.9,Supported by a multi-academy trust,THE CANTERBURY ACADEMY TRUST,-,,Not applicable,,10031917.0,,Not applicable,08-12-2022,09-04-2024,City View,Franklyn Road,,Canterbury,Kent,CT2 8PT,www.canterbury.kent.sch.uk/,1227462883.0,Mrs,Bev,Farrell,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,17.0,15.0,,,South East,Canterbury,Westgate,Canterbury,(England/Wales) Urban city and town,E10000016,613518.0,157596.0,Canterbury 020,Canterbury 020E,,,,,Good,South-East England and South London,,100062293053.0,,Not applicable,Not applicable,,,E02006856,E01024126,171.0,
|
||||
136349,886,Kent,5455,Leigh Academy Tonbridge,Academy converter,Academies,Open,Academy Converter,01-12-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Non-selective,1020.0,No Special Classes,19-01-2023,477.0,476.0,1.0,25.7,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10031580.0,,Not applicable,07-12-2022,29-04-2024,Brook Street,,,Tonbridge,Kent,TN9 2PH,http://leighacademytonbridge.org.uk,1732500600.0,Mr,Michael,Crow,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Judd,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,558063.0,145659.0,Tonbridge and Malling 013,Tonbridge and Malling 013A,,,,,Good,South-East England and South London,,200000962881.0,,Not applicable,Not applicable,,,E02005161,E01024757,109.0,
|
||||
136351,886,Kent,2656,Meopham Community Academy,Academy converter,Academies,Open,Academy Converter,01-12-2010,,,Primary,2.0,11,No boarders,Has Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,467.0,240.0,227.0,6.9,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10032229.0,,Not applicable,16-10-2018,18-04-2024,Longfield Road,Meopham,,Gravesend,Kent,DA13 0JW,http://www.meophamca.com,1474812259.0,Mr,Thomas,Waterman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Gravesham,Meopham North,Gravesham,(England/Wales) Rural town and fringe,E10000016,564320.0,166643.0,Gravesham 012,Gravesham 012D,,,,,Good,South-East England and South London,,100062313197.0,,Not applicable,Not applicable,,,E02005066,E01024271,30.0,
|
||||
136359,886,Kent,5406,Dartford Grammar School,Academy converter,Academies,Open,Academy Converter,01-12-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1550.0,No Special Classes,19-01-2023,1516.0,1291.0,225.0,5.9,Supported by a single-academy trust,DARTFORD GRAMMAR SCHOOL,-,,Not applicable,,10032228.0,,Not applicable,07-12-2022,07-05-2024,West Hill,,,Dartford,Kent,DA1 2HW,http://www.dartfordgrammarschool.org.uk,1322223039.0,Mr,Julian,Metcalf,Headmaster,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dartford,West Hill,Dartford,(England/Wales) Urban major conurbation,E10000016,553364.0,174104.0,Dartford 003,Dartford 003F,,,,,Outstanding,South-East England and South London,,200000540995.0,,Not applicable,Not applicable,,,E02005030,E01024185,54.0,
|
||||
136379,886,Kent,4092,Highworth Grammar School,Academy converter,Academies,Open,Academy Converter,01-01-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,Does not apply,Does not apply,Not applicable,Selective,1227.0,No Special Classes,19-01-2023,1542.0,100.0,1442.0,6.7,Supported by a single-academy trust,HIGHWORTH GRAMMAR SCHOOL TRUST,-,,Not applicable,,10032347.0,,Not applicable,14-06-2013,21-05-2024,Maidstone Road,,,Ashford,Kent,TN24 8UD,http://www.highworth.kent.sch.uk/,1233624910.0,Mr,Duncan,Beer,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Furley,Ashford,(England/Wales) Urban city and town,E10000016,600386.0,143340.0,Ashford 015,Ashford 015B,,,,,Outstanding,South-East England and South London,,100062560536.0,,Not applicable,Not applicable,,,E02007046,E01023981,73.0,
|
||||
136382,886,Kent,5462,Chatham & Clarendon Grammar School,Academy converter,Academies,Open,Academy Converter,01-01-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Selective,1600.0,No Special Classes,19-01-2023,1503.0,766.0,737.0,13.4,Supported by a multi-academy trust,CHATHAM & CLARENDON GRAMMAR SCHOOL,-,,Not applicable,,10032384.0,,Not applicable,16-05-2018,14-05-2024,Chatham Street,,,Ramsgate,Kent,CT11 7PS,http://www.ccgrammarschool.co.uk,1843591075.0,Mrs,Debra,Liddicoat,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Eastcliff,South Thanet,(England/Wales) Urban city and town,E10000016,638046.0,165251.0,Thanet 015,Thanet 015D,,,,,Good,South-East England and South London,,100062281955.0,,Not applicable,Not applicable,,,E02005146,E01024670,135.0,
|
||||
136393,886,Kent,2608,St Stephen's Junior School,Academy converter,Academies,Open,Academy Converter,01-01-2011,,,Primary,7.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,404.0,218.0,186.0,30.0,Supported by a single-academy trust,ST STEPHEN'S ACADEMY CANTERBURY,-,,Not applicable,,10032362.0,,Not applicable,01-03-2023,21-05-2024,Hales Drive,St Stephens,,Canterbury,Kent,CT2 7AD,www.ststephensjuniorschool.co.uk/,1227464119.0,Co Headteacher,Laura Cutts,Sarah Heaney,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,St Stephen's,Canterbury,(England/Wales) Urban city and town,E10000016,614829.0,159308.0,Canterbury 013,Canterbury 013B,,,,,Good,South-East England and South London,,200000677176.0,,Not applicable,Not applicable,,,E02005022,E01024100,121.0,
|
||||
136417,886,Kent,5443,Tonbridge Grammar School,Academy converter,Academies,Open,Academy Converter,01-01-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1260.0,No Special Classes,19-01-2023,1131.0,29.0,1102.0,1.9,Supported by a single-academy trust,TONBRIDGE GRAMMAR SCHOOL,-,,Not applicable,,10032608.0,,Not applicable,17-10-2019,22-05-2024,Deakin Leas,,,Tonbridge,Kent,TN9 2JR,http://www.tgs.kent.sch.uk,1732365125.0,Mrs,Rebecca,Crean,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Vauxhall,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559055.0,145279.0,Tonbridge and Malling 013,Tonbridge and Malling 013C,,,,,Outstanding,South-East England and South London,,100062594177.0,,Not applicable,Not applicable,,,E02005161,E01024778,17.0,
|
||||
136455,886,Kent,4046,Weald of Kent Grammar School,Academy converter,Academies,Open,Academy Converter,01-02-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,2475.0,No Special Classes,19-01-2023,1966.0,81.0,1885.0,3.3,Supported by a single-academy trust,WEALD OF KENT GRAMMAR SCHOOL ACADEMY TRUST,-,,Not applicable,,10032960.0,,Not applicable,27-04-2022,21-05-2024,Tudeley Lane,,,Tonbridge,Kent,TN9 2JP,http://www.wealdgs.org,1732373500.0,Mr,Richard,Booth,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Vauxhall,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559489.0,145217.0,Tonbridge and Malling 012,Tonbridge and Malling 012E,,,,,Requires improvement,South-East England and South London,,200000962006.0,,Not applicable,Not applicable,,,E02005160,E01024767,50.0,
|
||||
136465,886,Kent,5448,Herne Bay High School,Academy converter,Academies,Open,Academy Converter,01-03-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1494.0,No Special Classes,19-01-2023,1586.0,826.0,760.0,25.2,Supported by a single-academy trust,HERNE BAY HIGH SCHOOL,-,,Not applicable,,10032981.0,,Not applicable,15-06-2022,08-04-2024,Bullockstone Road,,,Herne Bay,Kent,CT6 7NS,http://www.hernebayhigh.org,1227361221.0,Mr,Jon,Boyes,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Greenhill,North Thanet,(England/Wales) Urban city and town,E10000016,616784.0,167105.0,Canterbury 003,Canterbury 003A,,,,,Good,South-East England and South London,,10033162926.0,,Not applicable,Not applicable,,,E02005012,E01024064,333.0,
|
||||
136499,886,Kent,2141,Amherst School,Academy converter,Academies,Open,Academy Converter,01-03-2011,,,Primary,7.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,384.0,No Special Classes,19-01-2023,379.0,205.0,174.0,5.0,Supported by a multi-academy trust,AMHERST SCHOOL (ACADEMY) TRUST,-,,Not applicable,,10033038.0,,Not applicable,11-05-2022,23-05-2024,Witches Lane,Riverhead,,Sevenoaks,Kent,TN13 2AX,http://www.amherst.kent.sch.uk,1732452577.0,Mr,Andrew,Reid,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Brasted, Chevening and Sundridge",Sevenoaks,(England/Wales) Urban city and town,E10000016,550933.0,155835.0,Sevenoaks 011,Sevenoaks 011B,,,,,Good,South-East England and South London,,50002017544.0,,Not applicable,Not applicable,,,E02005097,E01024418,19.0,
|
||||
136501,886,Kent,5428,Sir Roger Manwood's School,Academy converter,Academies,Open,Academy Converter,01-03-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Selective,1070.0,No Special Classes,19-01-2023,979.0,474.0,505.0,9.4,Supported by a single-academy trust,SIR ROGER MANWOOD'S SCHOOL,-,,Not applicable,,10033035.0,,Not applicable,28-09-2022,25-03-2024,Manwood Road,,,Sandwich,Kent,CT13 9JX,http://www.manwoods.co.uk,1304610200.0,Mr,Lee,Hunter,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Sandwich,South Thanet,(England/Wales) Rural town and fringe,E10000016,633436.0,157821.0,Dover 002,Dover 002B,,,,,Good,South-East England and South London,,100062284711.0,,Not applicable,Not applicable,,,E02005042,E01024242,71.0,
|
||||
136570,886,Kent,5449,Queen Elizabeth's Grammar School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Selective,1000.0,No Special Classes,19-01-2023,1020.0,503.0,517.0,7.3,Supported by a multi-academy trust,QUEEN ELIZABETH'S GRAMMAR SCHOOL TRUST FAVERSHAM,-,,Not applicable,,10033266.0,,Not applicable,01-03-2023,21-05-2024,Abbey Place,,,Faversham,Kent,ME13 7BQ,http://www.queenelizabeths.kent.sch.uk,1795533132.0,Mr,David,Anderson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Abbey,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,601923.0,161598.0,Swale 015,Swale 015C,,,,,Good,South-East England and South London,,10034900923.0,,Not applicable,Not applicable,,,E02005129,E01024553,56.0,
|
||||
136571,886,Kent,4172,Hartsdown Academy,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,1175.0,Not applicable,19-01-2023,773.0,408.0,365.0,59.9,Supported by a multi-academy trust,COASTAL ACADEMIES TRUST,Linked to a sponsor,Coastal Academies Trust,Not applicable,,10033264.0,,Not applicable,08-12-2021,07-05-2024,George V Avenue,,,Margate,Kent,CT9 5RE,http://hartsdown.org/,1843227957.0,Mr,Matthew,Tate,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Thanet,Garlinge,North Thanet,(England/Wales) Urban city and town,E10000016,634567.0,169921.0,Thanet 005,Thanet 005A,,,,,Good,South-East England and South London,,100062627471.0,,Not applicable,Not applicable,,,E02005136,E01024672,438.0,
|
||||
136581,886,Kent,4249,Valley Park School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1157.0,Not applicable,19-01-2023,1655.0,779.0,876.0,15.5,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10033421.0,,Not applicable,05-03-2020,12-04-2024,Huntsman Lane,,,Maidstone,Kent,ME14 5DT,http://www.valleypark.viat.org.uk/,1622679421.0,Mr,D,Jones,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Boxley,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,577242.0,155795.0,Maidstone 005,Maidstone 005B,,,,,Good,South-East England and South London,,200003716140.0,,Not applicable,Not applicable,,,E02005072,E01024336,206.0,
|
||||
136582,886,Kent,4058,Invicta Grammar School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1319.0,No Special Classes,19-01-2023,1647.0,43.0,1604.0,5.4,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10033423.0,,Not applicable,21-09-2012,06-06-2024,Huntsman Lane,,,Maidstone,Kent,ME14 5DS,http://www.invicta.viat.org.uk,1622755856.0,Mrs,Van,Beales (Executive Headteacher),Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,High Street,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,577046.0,155881.0,Maidstone 004,Maidstone 004G,,,,,Outstanding,South-East England and South London,,200003717570.0,,Not applicable,Not applicable,,,E02005071,E01033092,68.0,
|
||||
136583,886,Kent,4196,Towers School and Sixth Form Centre,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,1625.0,No Special Classes,19-01-2023,1460.0,676.0,784.0,24.5,Supported by a single-academy trust,TOWERS SCHOOL ACADEMY TRUST,-,,Not applicable,,10033255.0,,Not applicable,23-01-2019,03-06-2024,Faversham Road,Kennington,,Ashford,Kent,TN24 9AL,www.towers.kent.sch.uk,1233634171.0,Mr,Richard,Billings,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Kennington,Ashford,(England/Wales) Urban city and town,E10000016,601709.0,145611.0,Ashford 003,Ashford 003B,,,,,Good,South-East England and South London,,200004392081.0,,Not applicable,Not applicable,,,E02004998,E01023999,312.0,
|
||||
136584,886,Kent,4120,King Ethelbert School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,16,No boarders,Not applicable,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,750.0,No Special Classes,19-01-2023,757.0,379.0,378.0,25.2,Supported by a multi-academy trust,COASTAL ACADEMIES TRUST,Linked to a sponsor,Coastal Academies Trust,Not applicable,,10033425.0,,Not applicable,03-10-2018,28-05-2024,Canterbury Road,,,Birchington,Kent,CT7 9BL,http://www.kingethelbert.com/,1843831999.0,,Tom,Sellen,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Birchington South,North Thanet,(England/Wales) Urban city and town,E10000016,631297.0,169465.0,Thanet 008,Thanet 008C,,,,,Good,South-East England and South London,,100062303868.0,,Not applicable,Not applicable,,,E02005139,E01024638,191.0,
|
||||
136585,886,Kent,5460,Dane Court Grammar School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Selective,1154.0,No Special Classes,19-01-2023,1236.0,589.0,647.0,16.2,Supported by a multi-academy trust,COASTAL ACADEMIES TRUST,Linked to a sponsor,Coastal Academies Trust,Not applicable,,10033426.0,,Not applicable,11-05-2022,08-05-2024,Broadstairs Road,,,Broadstairs,Kent,CT10 2RT,http://danecourt.kent.sch.uk/,1843864941.0,Mr,Martin,Jones,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,St Peters,South Thanet,(England/Wales) Urban city and town,E10000016,638084.0,167977.0,Thanet 011,Thanet 011C,,,,,Good,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005142,E01024689,142.0,
|
||||
136603,886,Kent,5464,Bennett Memorial Diocesan School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Non-selective,1452.0,No Special Classes,19-01-2023,1865.0,954.0,911.0,6.7,Supported by a multi-academy trust,THE TENAX SCHOOLS TRUST,Linked to a sponsor,The Tenax Schools Trust,Not applicable,,10033233.0,,Not applicable,14-12-2023,14-05-2024,Culverden Down,,,Tunbridge Wells,Kent,TN4 9SH,https://www.bennettmemorial.co.uk/,1892521595.0,Dr,Karen,Brookes,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,557344.0,140560.0,Tunbridge Wells 007,Tunbridge Wells 007D,,,,,Outstanding,South-East England and South London,,100062585960.0,,Not applicable,Not applicable,,,E02005168,E01024838,98.0,
|
||||
136727,886,Kent,5422,Oakwood Park Grammar School,Academy converter,Academies,Open,Academy Converter,01-05-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1035.0,No Special Classes,19-01-2023,1096.0,1047.0,49.0,5.6,Supported by a multi-academy trust,OAKWOOD PARK GRAMMAR SCHOOL,-,,Not applicable,,10033587.0,,Not applicable,07-02-2019,05-06-2024,Oakwood Park,,,Maidstone,Kent,ME16 8AH,http://www.opgs.org,1622726683.0,Mr,Kevin,Moody,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Heath,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574384.0,155244.0,Maidstone 008,Maidstone 008C,,,,,Good,South-East England and South London,,200003717871.0,,Not applicable,Not applicable,,,E02005075,E01024367,44.0,
|
||||
136794,886,Kent,2249,Regis Manor Primary School,Academy converter,Academies,Open,Academy Converter,01-06-2011,,,Primary,3.0,11,No boarders,Has Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,570.0,No Special Classes,19-01-2023,580.0,293.0,287.0,24.1,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10033808.0,,Not applicable,04-07-2023,30-04-2024,North Street,,,Sittingbourne,Kent,ME10 2HW,www.regismanor.org.uk,1795472971.0,Mr,Matthew,Perry,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Milton Regis,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,590298.0,165171.0,Swale 007,Swale 007F,,,,,Good,South-East England and South London,United Kingdom,100062375806.0,,Not applicable,Not applicable,,,E02005121,E01024583,130.0,
|
||||
136847,886,Kent,5439,Mascalls Academy,Academy converter,Academies,Open,Academy Converter,01-07-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1450.0,Has Special Classes,19-01-2023,1356.0,731.0,625.0,20.1,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10034109.0,,Not applicable,17-11-2021,13-03-2024,Maidstone Road,Paddock Wood,,Tonbridge,Kent,TN12 6LT,http://www.mascallsacademy.org.uk,1892835366.0,Mrs,Jo,Brooks,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Paddock Wood West,Tunbridge Wells,(England/Wales) Rural town and fringe,E10000016,567107.0,143847.0,Tunbridge Wells 001,Tunbridge Wells 001G,,,,,Good,South-East England and South London,,100062545662.0,,Not applicable,Not applicable,,,E02005162,E01024816,231.0,
|
||||
136923,886,Kent,4000,St Augustine Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2011,Not applicable,,Secondary,11.0,16,No boarders,Not applicable,Does not have a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Non-selective,750.0,Not applicable,19-01-2023,765.0,400.0,365.0,29.9,Supported by a multi-academy trust,WOODARD ACADEMIES TRUST,Linked to a sponsor,Woodard Academies Trust,Not applicable,,10034935.0,,Not applicable,13-07-2023,07-06-2024,Oakwood Road,,,Maidstone,Kent,ME16 8AE,http://www.saa.woodard.co.uk/,1622752490.0,Mr,Steffan,Ball,Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,12.0,12.0,,,South East,Maidstone,Heath,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574440.0,155313.0,Maidstone 008,Maidstone 008C,,,,,Requires improvement,South-East England and South London,,10014313780.0,,Not applicable,Not applicable,,,E02005075,E01024367,229.0,
|
||||
137071,886,Kent,2000,St Johns Church of England Primary School,Voluntary controlled school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2012,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,419.0,218.0,201.0,50.7,Not applicable,,Not applicable,,Not under a federation,,10079922.0,,Not applicable,24-01-2024,21-05-2024,St John's Place,Northgate,,Canterbury,Kent,CT1 1BD,www.stjohns-canterbury.kent.sch.uk/,1227462360.0,Mrs,Jo,Williamson,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Northgate,Canterbury,(England/Wales) Urban city and town,E10000016,615068.0,158363.0,Canterbury 014,Canterbury 014E,,,,,Good,South-East England and South London,,200000678007.0,,Not applicable,Not applicable,,,E02005023,E01024093,212.0,
|
||||
137099,886,Kent,5465,Gravesend Grammar School,Academy converter,Academies,Open,Academy Converter,01-08-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1032.0,No Special Classes,19-01-2023,1424.0,1340.0,84.0,7.4,Supported by a multi-academy trust,THE DECUS EDUCATIONAL TRUST,Linked to a sponsor,Gravesend Grammar School,Not applicable,,10034589.0,,Not applicable,26-06-2015,06-06-2024,Church Walk,,,Gravesend,Kent,DA12 2PR,http://gravesendgrammar.com/,1474331893.0,Mr,Malcolm,Moaby,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Denton,Gravesham,(England/Wales) Urban major conurbation,E10000016,565789.0,173594.0,Gravesham 005,Gravesham 005A,,,,,Outstanding,South-East England and South London,,10012012057.0,,Not applicable,Not applicable,,,E02005059,E01024259,76.0,
|
||||
137104,886,Kent,5450,Hillview School for Girls,Academy converter,Academies,Open,Academy Converter,01-08-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Non-selective,1340.0,No Special Classes,19-01-2023,1496.0,58.0,1438.0,12.6,Supported by a single-academy trust,HILLVIEW SCHOOL FOR GIRLS ACADEMY TRUST,-,,Not applicable,,10034679.0,,Not applicable,20-09-2023,23-05-2024,Brionne Gardens,,,Tonbridge,Kent,TN9 2HE,http://www.hillview.kent.sch.uk,1732352793.0,Mrs,Hilary,Burkett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Vauxhall,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559690.0,145686.0,Tonbridge and Malling 012,Tonbridge and Malling 012E,,,,,Good,South-East England and South London,,200000962877.0,,Not applicable,Not applicable,,,E02005160,E01024767,144.0,
|
||||
137136,886,Kent,2001,Horizon Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2011,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,None,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,202.0,109.0,93.0,41.6,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10034965.0,,Not applicable,15-11-2018,21-05-2024,Hilda May Avenue,,,Swanley,Kent,BR8 7BT,www.horizon-tkat.org/,1322665235.0,Mr,David,Moss,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Swanley White Oak,Sevenoaks,(England/Wales) Urban major conurbation,E10000016,551164.0,169131.0,Sevenoaks 002,Sevenoaks 002D,,,,,Good,South-East England and South London,,100062622098.0,,Not applicable,Not applicable,,,E02005088,E01024480,84.0,
|
||||
137227,886,Kent,5403,Wilmington Grammar School for Boys,Academy converter,Academies,Open,Academy Converter,01-08-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,848.0,No Special Classes,19-01-2023,1079.0,993.0,86.0,8.1,Supported by a multi-academy trust,ENDEAVOUR MAT,-,,Not applicable,,10034780.0,,Not applicable,14-03-2023,07-05-2024,Common Lane,Wilmington,,Dartford,Kent,DA2 7DA,http://www.wgsb.co.uk,1322223090.0,Mr,Stuart,Harrington,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Maypole & Leyton Cross,Dartford,(England/Wales) Urban major conurbation,E10000016,552660.0,172195.0,Dartford 011,Dartford 011D,,,,,Good,South-East England and South London,,200000534710.0,,Not applicable,Not applicable,,,E02005038,E01024189,68.0,
|
||||
137250,886,Kent,5400,Wilmington Grammar School for Girls,Academy converter,Academies,Open,Academy Converter,01-08-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1070.0,No Special Classes,19-01-2023,1068.0,92.0,976.0,4.6,Supported by a multi-academy trust,ENDEAVOUR MAT,-,,Not applicable,,10034848.0,,Not applicable,17-11-2022,05-06-2024,Wilmington Grange,Parsons Lane,Wilmington,Dartford,Kent,DA2 7BB,http://www.gsgw.org.uk/,1322226351.0,Mrs,Michelle,Lawson,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Maypole & Leyton Cross,Dartford,(England/Wales) Urban major conurbation,E10000016,552565.0,172488.0,Dartford 011,Dartford 011C,,,,,Good,South-East England and South London,,200000534735.0,,Not applicable,Not applicable,,,E02005038,E01024188,38.0,
|
||||
137397,886,Kent,2246,Sheldwich Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Primary,2.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,267.0,124.0,143.0,6.0,Supported by a single-academy trust,SHELDWICH PRIMARY SCHOOL,-,,Not applicable,,10035075.0,,Not applicable,09-11-2012,22-05-2024,Lees Court Road,Sheldwich,,Faversham,Kent,ME13 0LU,http://www.sheldwich.kent.sch.uk,1795532779.0,Mrs,Sarah,Garrett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Boughton and Courtenay,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,601215.0,156561.0,Swale 017,Swale 017A,,,,,Outstanding,South-East England and South London,,200002532965.0,,Not applicable,Not applicable,,,E02005131,E01024555,16.0,
|
||||
137458,886,Kent,5466,Brockhill Park Performing Arts College,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1384.0,No Special Classes,19-01-2023,1351.0,680.0,671.0,25.7,Supported by a single-academy trust,BROCKHILL PARK PERFORMING ARTS COLLEGE,-,,Not applicable,,10035076.0,,Not applicable,13-10-2021,05-04-2024,Sandling Road,Saltwood,,Hythe,Kent,CT21 4HL,http://www.brockhill.kent.sch.uk/,1303265521.0,Mr,Charles,Joseph,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Hythe,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,614826.0,135838.0,Folkestone and Hythe 008,Folkestone and Hythe 008D,,,,,Good,South-East England and South London,,50016401.0,,Not applicable,Not applicable,,,E02005109,E01024550,301.0,
|
||||
137467,886,Kent,2233,Lynsted and Norton Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,73.0,41.0,32.0,45.2,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10035094.0,,Not applicable,08-03-2023,23-04-2024,Lynsted Lane,Lynsted,,Sittingbourne,Kent,ME9 0RL,www.lynsted-norton.kent.sch.uk,1795521362.0,Mrs,Catherine,McLaughlin,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Teynham and Lynsted,Sittingbourne and Sheppey,(England/Wales) Rural hamlet and isolated dwellings,E10000016,594445.0,161097.0,Swale 016,Swale 016E,,,,,Requires improvement,South-East England and South London,,200002528949.0,,Not applicable,Not applicable,,,E02005130,E01024624,33.0,
|
||||
137474,886,Kent,5444,Barton Court Grammar School,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Selective,810.0,No Special Classes,19-01-2023,985.0,577.0,408.0,9.2,Supported by a multi-academy trust,BARTON COURT ACADEMY TRUST,Linked to a sponsor,Barton Court Academy Trust,Not applicable,,10035093.0,,Not applicable,12-02-2020,23-04-2024,Longport,,,Canterbury,Kent,CT1 1PH,http://www.bartoncourt.org,1227464600.0,Mr,Jonathan,Hopkins,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,615693.0,157611.0,Canterbury 016,Canterbury 016A,,,,,Good,South-East England and South London,,100062279040.0,,Not applicable,Not applicable,,,E02005025,E01024044,67.0,
|
||||
137481,886,Kent,3112,Selling Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,128.0,75.0,53.0,21.1,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10035102.0,,Not applicable,11-11-2021,06-05-2024,The Street,Selling,,Faversham,Kent,ME13 9RQ,http://www.selling-faversham.kent.sch.uk/,1227752202.0,Mr,Richard,Paez,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Boughton and Courtenay,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,603872.0,156507.0,Swale 017,Swale 017A,,,,,Good,South-East England and South London,,10023196074.0,,Not applicable,Not applicable,,,E02005131,E01024555,27.0,
|
||||
137483,886,Kent,3110,Milstead and Frinsted Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,70.0,No Special Classes,19-01-2023,85.0,48.0,37.0,23.5,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10035126.0,,Not applicable,03-11-2022,31-05-2024,School Lane,Milstead,,Sittingbourne,Kent,ME9 0SJ,http://www.milstead.kent.sch.uk,1795830241.0,Mr,Scott,Guy,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,West Downs,Sittingbourne and Sheppey,(England/Wales) Rural hamlet and isolated dwellings,E10000016,590320.0,158072.0,Swale 013,Swale 013C,,,,,Requires improvement,South-East England and South London,,100062626973.0,,Not applicable,Not applicable,,,E02005127,E01024628,20.0,
|
||||
137484,886,Kent,5408,Homewood School and Sixth Form Centre,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,2156.0,No Special Classes,19-01-2023,2103.0,960.0,1143.0,26.5,Supported by a multi-academy trust,TENTERDEN SCHOOLS TRUST,-,,Not applicable,,10035054.0,,Not applicable,26-04-2023,14-05-2024,Ashford Road,,,Tenterden,Kent,TN30 6LT,http://www.homewood-school.co.uk/,1580764222.0,Mr,Jeremy,Single,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Tenterden North,Ashford,(England/Wales) Rural town and fringe,E10000016,588805.0,134276.0,Ashford 013,Ashford 013E,,,,,Good,South-East England and South London,,100062566868.0,,Not applicable,Not applicable,,,E02005008,E01024024,464.0,
|
||||
137529,886,Kent,2288,Smarden Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2011,,,Primary,2.0,11,No boarders,Has Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,174.0,97.0,77.0,8.6,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10035486.0,,Not applicable,11-05-2023,27-03-2024,Pluckley Road,Smarden,,Ashford,Kent,TN27 8ND,www.smardenprimaryschool.co.uk/,1233770316.0,Mrs,Claudia,Miller,Executive Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Weald North,Ashford,(England/Wales) Rural village,E10000016,588338.0,142472.0,Ashford 011,Ashford 011D,,,,,Good,South-East England and South London,,100062563757.0,,Not applicable,Not applicable,,,E02005006,E01024036,15.0,
|
||||
137581,886,Kent,4001,Ebbsfleet Academy,Academy sponsor led,Academies,Open,New Provision,01-11-2013,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,750.0,No Special Classes,19-01-2023,766.0,421.0,345.0,34.1,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10038343.0,,Not applicable,02-10-2019,14-03-2024,Southfleet Road,,,Swanscombe,Kent,DA10 0BZ,http://www.ebbsfleetacademy.org.uk,1322242252.0,Mrs,Gurjit Kaur,Shergill,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Swanscombe,Dartford,(England/Wales) Urban major conurbation,E10000016,560829.0,173926.0,Dartford 004,Dartford 004B,,,,,Good,South-East England and South London,,200000535991.0,,Not applicable,Not applicable,,,E02005031,E01024176,237.0,
|
||||
137609,886,Kent,5404,Saint George's Church of England School,Academy converter,Academies,Open,Academy Converter,01-11-2011,,,All-through,4.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Non-selective,1410.0,No Special Classes,19-01-2023,1381.0,722.0,659.0,17.4,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10035681.0,,Not applicable,18-10-2023,25-04-2024,Meadow Road,,,Gravesend,Kent,DA11 7LS,http://www.saintgeorgescofe.kent.sch.uk,1474533082.0,Mr,Simon,Murphy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Woodlands,Gravesham,(England/Wales) Urban major conurbation,E10000016,564257.0,172583.0,Gravesham 005,Gravesham 005E,,,,,Good,South-East England and South London,,100062310647.0,,Not applicable,Not applicable,,,E02005059,E01024317,202.0,
|
||||
137615,886,Kent,3372,"Maidstone, St John's Church of England Primary School",Academy converter,Academies,Open,Academy Converter,01-11-2011,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,441.0,211.0,230.0,8.4,Supported by a single-academy trust,ST JOHN'S CHURCH OF ENGLAND PRIMARY SCHOOL MAIDSTONE,-,,Not applicable,,10035701.0,,Not applicable,16-07-2015,16-04-2024,Provender Way,Grove Green,,Maidstone,Kent,ME14 5TZ,www.st-johns-maidstone.kent.sch.uk/,1622735916.0,Mr,D J,Smith,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Boxley,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,578084.0,155874.0,Maidstone 005,Maidstone 005C,,,,,Outstanding,South-East England and South London,,200003688398.0,,Not applicable,Not applicable,,,E02005072,E01024337,37.0,
|
||||
137660,886,Kent,2500,Joydens Wood Infant School,Academy converter,Academies,Open,Academy Converter,01-11-2011,,,Primary,4.0,7,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,238.0,122.0,116.0,5.5,Supported by a multi-academy trust,NEXUS EDUCATION SCHOOLS TRUST,Linked to a sponsor,Nexus Education Schools Trust,Not applicable,,10035690.0,,Not applicable,05-10-2023,29-04-2024,Park Way,,,Bexley,Kent,DA5 2JD,www.joydens-wood-infant.kent.sch.uk/,1322523188.0,Headteacher,Allison Morris/,Gerard Strong,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Joyden's Wood,Dartford,(England/Wales) Urban major conurbation,E10000016,551436.0,172012.0,Dartford 010,Dartford 010D,,,,,Good,South-East England and South London,,200000534792.0,,Not applicable,Not applicable,,,E02005037,E01024153,13.0,
|
||||
137661,886,Kent,2438,Joydens Wood Junior School,Academy converter,Academies,Open,Academy Converter,01-11-2011,,,Primary,7.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,280.0,No Special Classes,19-01-2023,294.0,147.0,147.0,9.5,Supported by a multi-academy trust,NEXUS EDUCATION SCHOOLS TRUST,Linked to a sponsor,Nexus Education Schools Trust,Not applicable,,10035673.0,,Not applicable,08-06-2022,04-04-2024,Birchwood Drive,Wilmington,,Dartford,Kent,DA2 7NE,http://www.joydens-wood-junior.kent.sch.uk,1322522151.0,Mr,Paul,Redford,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Joyden's Wood,Dartford,(England/Wales) Urban major conurbation,E10000016,551348.0,171986.0,Dartford 010,Dartford 010A,,,,,Requires improvement,South-East England and South London,,100060870222.0,,Not applicable,Not applicable,,,E02005037,E01024150,28.0,
|
||||
137663,886,Kent,5219,Wilmington Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2011,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,None,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,210.0,112.0,98.0,7.1,Supported by a multi-academy trust,ENDEAVOUR MAT,-,,Not applicable,,10035695.0,,Not applicable,20-06-2019,22-04-2024,Common Lane,Wilmington,,Dartford,Kent,DA2 7DF,www.wilmingtonprimaryschool.co.uk,1322274080.0,Mrs,Charlotte,Scott,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,"Wilmington, Sutton-at-Hone & Hawley",Dartford,(England/Wales) Urban major conurbation,E10000016,552812.0,172220.0,Dartford 011,Dartford 011C,,,,,Good,South-East England and South London,,200000534694.0,,Not applicable,Not applicable,,,E02005038,E01024188,15.0,
|
||||
137687,886,Kent,4002,The Sittingbourne School,Academy sponsor led,Academies,Open,New Provision,01-01-2012,Not applicable,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1350.0,No Special Classes,19-01-2023,1589.0,777.0,812.0,36.4,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10036067.0,,Not applicable,22-03-2023,21-05-2024,Swanstree Avenue,,,Sittingbourne,Kent,ME10 4NL,http://www.thesittingbourneschool.org.uk,1795472449.0,Mr,Nick,Smith,Trust Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,HI - Hearing Impairment,"SLCN - Speech, language and Communication",,,,,,,,,,,,Resourced provision,82.0,60.0,,,South East,Swale,Roman,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,592123.0,162907.0,Swale 011,Swale 011D,,,,,Good,South-East England and South London,,100062627032.0,,Not applicable,Not applicable,,,E02005125,E01024600,509.0,
|
||||
137728,886,Kent,3025,Chiddingstone Church of England School,Academy converter,Academies,Open,Academy Converter,01-12-2011,,,Primary,5.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,209.0,96.0,113.0,4.3,Supported by a single-academy trust,CHIDDINGSTONE CHURCH OF ENGLAND SCHOOL,-,,Not applicable,,10035696.0,,Not applicable,27-03-2015,15-04-2024,Chiddingstone,,,Edenbridge,Kent,TN8 7AH,www.chiddingstoneschool.co.uk/,1892870339.0,,Kate,Haysom,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Penshurst, Fordcombe and Chiddingstone",Tonbridge and Malling,(England/Wales) Rural village,E10000016,550144.0,145154.0,Sevenoaks 015,Sevenoaks 015C,,,,,Outstanding,South-East England and South London,,10035181506.0,,Not applicable,Not applicable,,,E02005101,E01024455,9.0,
|
||||
137739,886,Kent,5416,Cranbrook School,Academy converter,Academies,Open,Academy Converter,01-12-2011,,,Secondary,11.0,18,Boarding school,Not applicable,Has a sixth form,Mixed,Christian,Does not apply,Not applicable,Selective,910.0,No Special Classes,19-01-2023,884.0,515.0,369.0,2.7,Supported by a single-academy trust,CRANBROOK SCHOOL ACADEMY TRUST,-,,Not applicable,,10035657.0,,Not applicable,23-03-2022,30-04-2024,Waterloo Road,,,Cranbrook,Kent,TN17 3JD,http://www.cranbrookschool.co.uk/,1580711800.0,Mr,David,Clark,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Benenden and Cranbrook,Maidstone and The Weald,(England/Wales) Rural town and fringe,E10000016,577829.0,136133.0,Tunbridge Wells 013,Tunbridge Wells 013C,,,,,Good,South-East England and South London,,100062552225.0,,Not applicable,Not applicable,,,E02005174,E01024790,15.0,
|
||||
137800,886,Kent,4527,Borden Grammar School,Academy converter,Academies,Open,Academy Converter,01-01-2012,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,802.0,No Special Classes,19-01-2023,889.0,861.0,28.0,9.8,Supported by a single-academy trust,BORDEN GRAMMAR SCHOOL TRUST,-,,Not applicable,,10035796.0,,Not applicable,24-11-2021,15-05-2024,Avenue of Remembrance,,,Sittingbourne,Kent,ME10 4DB,http://website.bordengrammar.kent.sch.uk/,1795424192.0,Mr,Ashley,Tomlin,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Homewood,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,590580.0,163359.0,Swale 013,Swale 013A,,,,,Good,South-East England and South London,,100062376270.0,,Not applicable,Not applicable,,,E02005127,E01024606,65.0,
|
||||
137806,886,Kent,2002,Repton Manor Primary School,Foundation school,Local authority maintained schools,Open,New Provision,01-09-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,471.0,244.0,227.0,19.5,Not supported by a trust,,Not applicable,,Supported by a federation,The Lightyear Federation,10072299.0,,Not applicable,29-11-2023,06-03-2024,Repton Avenue,,,Ashford,Kent,TN23 3RX,http://www.reptonmanorprimary.co.uk/,1233666307.0,Mr,Matthew,Rawling,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Repton,Ashford,(England/Wales) Urban city and town,E10000016,599566.0,143671.0,Ashford 016,Ashford 016B,,,,,Good,South-East England and South London,,10012859115.0,,Not applicable,Not applicable,,,E02007047,E01032820,92.0,
|
||||
137833,886,Kent,5401,The Maplesden Noakes School,Academy converter,Academies,Open,Academy Converter,01-02-2012,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1500.0,No Special Classes,19-01-2023,1359.0,675.0,684.0,18.1,Supported by a single-academy trust,THE MAPLESDEN NOAKES SCHOOL,Linked to a sponsor,The Maplesden Noakes School,Not applicable,,10036261.0,,Not applicable,14-11-2018,30-04-2024,Buckland Road,,,Maidstone,Kent,ME16 0TJ,http://www.maplesden.kent.sch.uk/,1622759036.0,Mr,Richard,Owen,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,Bridge,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575149.0,156457.0,Maidstone 006,Maidstone 006B,,,,,Good,South-East England and South London,,200003670026.0,,Not applicable,Not applicable,,,E02005073,E01024340,197.0,
|
||||
137834,886,Kent,5467,"Mayfield Grammar School, Gravesend",Academy converter,Academies,Open,Academy Converter,01-02-2012,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1207.0,No Special Classes,19-01-2023,1349.0,40.0,1309.0,10.5,Supported by a single-academy trust,"MAYFIELD GRAMMAR SCHOOL, GRAVESEND",-,,Not applicable,,10036417.0,,Not applicable,12-06-2013,09-05-2024,Pelham Road,,,Gravesend,Kent,DA11 0JE,http://www.mgsg.kent.sch.uk/,1474352896.0,Headteacher,Elaine,Wilson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Pelham,Gravesham,(England/Wales) Urban major conurbation,E10000016,564094.0,173489.0,Gravesham 002,Gravesham 002C,,,,,Outstanding,South-East England and South London,,100062310034.0,,Not applicable,Not applicable,,,E02005056,E01024290,107.0,
|
||||
137836,886,Kent,2684,Wentworth Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,651.0,321.0,330.0,14.9,Supported by a single-academy trust,WENTWORTH PRIMARY SCHOOL,-,,Not applicable,,10036263.0,,Not applicable,08-06-2023,21-05-2024,Wentworth Drive,,,Dartford,Kent,DA1 3NG,www.wentworthonline.co.uk/,1322225694.0,Mr,Lewis,Pollock,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Heath,Dartford,(England/Wales) Urban major conurbation,E10000016,552144.0,173935.0,Dartford 007,Dartford 007A,,,,,Good,South-East England and South London,,,,Not applicable,Not applicable,,,E02005034,E01024144,97.0,
|
||||
137837,886,Kent,5437,The Folkestone School for Girls,Academy converter,Academies,Open,Academy Converter,01-02-2012,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1053.0,No Special Classes,19-01-2023,1217.0,0.0,1217.0,12.1,Supported by a multi-academy trust,THE FOLKESTONE SCHOOL FOR GIRLS ACADEMY TRUST,-,,Not applicable,,10036180.0,,Not applicable,12-10-2012,17-04-2024,Coolinge Lane,,,Folkestone,Kent,CT20 3RB,http://www.folkestonegirls.kent.sch.uk/,1303251125.0,Mr,Mark,Lester,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Sandgate & West Folkestone,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,620830.0,135551.0,Folkestone and Hythe 006,Folkestone and Hythe 006H,,,,,Outstanding,South-East England and South London,,50026109.0,,Not applicable,Not applicable,,,E02005107,E01024521,112.0,
|
||||
137871,886,Kent,2229,Graveney Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,105.0,No Special Classes,19-01-2023,100.0,50.0,50.0,18.0,Supported by a multi-academy trust,GRAVENEY PRIMARY SCHOOL,-,,Not applicable,,10035872.0,,Not applicable,04-10-2023,17-04-2024,Seasalter Road,Graveney,,Faversham,Kent,ME13 9DU,www.graveneyprimary.com,1795532005.0,Mrs,Alison,Blackwell,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Boughton and Courtenay,Faversham and Mid Kent,(England/Wales) Rural village,E10000016,605092.0,162256.0,Swale 017,Swale 017B,,,,,Good,South-East England and South London,,200002531105.0,,Not applicable,Not applicable,,,E02005131,E01024556,18.0,
|
||||
137881,886,Kent,2003,Oaks Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-04-2012,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,213.0,No Special Classes,19-01-2023,216.0,103.0,113.0,40.4,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10037058.0,,Not applicable,22-09-2021,28-05-2024,Oak Tree Avenue,,,Maidstone,Kent,ME15 9AX,http://www.oaksprimaryacademy.org.uk,1622755960.0,Principal,Tom,Moore,Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Shepway North,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,577321.0,153197.0,Maidstone 013,Maidstone 013C,,,,,Outstanding,South-East England and South London,,200003718069.0,,Not applicable,Not applicable,,,E02005080,E01024391,78.0,
|
||||
137882,886,Kent,2004,Tree Tops Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-04-2012,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,315.0,No Special Classes,19-01-2023,314.0,148.0,166.0,48.8,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10037063.0,,Not applicable,12-06-2019,13-09-2023,Brishing Lane,Park Wood,,Maidstone,Kent,ME15 9EZ,http://www.treetopsprimaryacademy.org/,1622754888.0,Miss,Denise,White,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Park Wood,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,578482.0,152246.0,Maidstone 013,Maidstone 013I,,,,,Good,South-East England and South London,,10014314389.0,,Not applicable,Not applicable,,,E02005080,E01034996,148.0,
|
||||
137961,886,Kent,2264,Hampton Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2012,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,710.0,No Special Classes,19-01-2023,675.0,348.0,327.0,28.1,Supported by a single-academy trust,HAMPTON PRIMARY SCHOOL ACADEMY,-,,Not applicable,,10036924.0,,Not applicable,11-03-2020,07-05-2024,Fitzgerald Avenue,,,Herne Bay,Kent,CT6 8NB,www.hampton.kent.sch.uk,1227372159.0,Ms,Yvonne,Nunn,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,,,,,South East,Canterbury,West Bay,North Thanet,(England/Wales) Urban city and town,E10000016,616209.0,167469.0,Canterbury 004,Canterbury 004D,,,,,Good,South-East England and South London,,200000682720.0,,Not applicable,Not applicable,,,E02005013,E01024119,188.0,
|
||||
138001,886,Kent,3142,Pluckley Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,96.0,51.0,45.0,15.6,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10036989.0,,Not applicable,06-06-2019,15-04-2024,The Street,Pluckley,,Ashford,Kent,TN27 0QS,www.pluckleyprimaryschool.co.uk,1233840422.0,Mrs,Lorraine,Smith,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Upper Weald,Ashford,(England/Wales) Rural village,E10000016,592582.0,145357.0,Ashford 002,Ashford 002E,,,,,Good,South-East England and South London,,100062563578.0,,Not applicable,Not applicable,,,E02004997,E01024033,15.0,
|
||||
138019,886,Kent,4528,The Norton Knatchbull School,Academy converter,Academies,Open,Academy Converter,01-04-2012,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1316.0,No Special Classes,19-01-2023,1252.0,1226.0,26.0,7.9,Supported by a single-academy trust,THE NORTON KNATCHBULL SCHOOL,-,,Not applicable,,10036999.0,,Not applicable,14-12-2023,07-06-2024,Hythe Road,,,Ashford,Kent,TN24 0QJ,http://www.nks.kent.sch.uk,1233620045.0,Mr,Ben,Greene,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Furley,Ashford,(England/Wales) Urban city and town,E10000016,602275.0,142571.0,Ashford 005,Ashford 005C,,,,,Good,South-East England and South London,,100062559840.0,,Not applicable,Not applicable,,,E02005000,E01024023,79.0,
|
||||
138034,886,Kent,2232,Luddenham School,Academy converter,Academies,Open,Academy Converter,01-04-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,201.0,92.0,109.0,25.4,Supported by a single-academy trust,LUDDENHAM SCHOOL,-,,Not applicable,,10037009.0,,Not applicable,27-02-2019,04-06-2024,Luddenham,,,Faversham,Kent,ME13 0TE,www.luddenham.kent.sch.uk,1795532061.0,Mrs,Claire,Vincett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Teynham and Lynsted,Sittingbourne and Sheppey,(England/Wales) Rural hamlet and isolated dwellings,E10000016,599374.0,162425.0,Swale 016,Swale 016C,,,,,Good,South-East England and South London,,200002532858.0,,Not applicable,Not applicable,,,E02005130,E01024622,51.0,
|
||||
138074,886,Kent,2006,St James the Great Academy,Academy sponsor led,Academies,Open,New Provision,01-04-2012,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,197.0,101.0,96.0,42.6,Supported by a multi-academy trust,ACADEMIES ENTERPRISE TRUST,Linked to a sponsor,Academies Enterprise Trust (AET),Not applicable,,10037082.0,,Not applicable,13-09-2023,23-05-2024,Chapman Way,,,East Malling,Kent,ME19 6SD,http://www.stjamesthegreatacademy.org/,1732841912.0,Miss,Tamasin,Springett,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"East Malling, West Malling & Offham",Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,569675.0,157748.0,Tonbridge and Malling 014,Tonbridge and Malling 014B,,,,,Good,South-East England and South London,,100062388024.0,,Not applicable,Not applicable,,,E02006833,E01024742,81.0,
|
||||
138151,886,Kent,2595,Grove Park Primary School,Academy converter,Academies,Open,Academy Converter,01-06-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,412.0,214.0,198.0,21.1,Supported by a multi-academy trust,BOURNE ALLIANCE MULTI ACADEMY TRUST,Linked to a sponsor,TIMU Academy Trust,Not applicable,,10037293.0,,Not applicable,24-05-2023,16-04-2024,Hilton Drive,,,Sittingbourne,Kent,ME10 1PT,www.grovepark-ba-mat.org.uk,1795477417.0,Mrs,Lauren,Flain,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Borden and Grove Park,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,588827.0,164604.0,Swale 009,Swale 009H,,,,,Requires improvement,South-East England and South London,,200002527488.0,,Not applicable,Not applicable,,,E02005123,E01035306,87.0,
|
||||
138167,886,Kent,4113,Astor Secondary School,Academy converter,Academies,Open,Academy Converter,02-06-2012,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,1230.0,No Special Classes,19-01-2023,803.0,348.0,455.0,49.3,Supported by a multi-academy trust,SAMPHIRE STAR EDUCATION TRUST,-,,Not applicable,,10037500.0,,Not applicable,01-11-2023,15-04-2024,Astor Avenue,,,Dover,Kent,CT17 0AS,http://www.astorschool.com,1304201151.0,Mr,Lee,Kane,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dover,Tower Hamlets,Dover,(England/Wales) Urban city and town,E10000016,630219.0,141531.0,Dover 011,Dover 011H,,,,,Requires improvement,South-East England and South London,,10034874352.0,,Not applicable,Not applicable,,,E02005051,E01024248,350.0,
|
||||
138168,886,Kent,2315,White Cliffs Primary and Nursery School,Academy converter,Academies,Open,Academy Converter,01-06-2012,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,315.0,179.0,136.0,59.0,Supported by a multi-academy trust,SAMPHIRE STAR EDUCATION TRUST,-,,Not applicable,,10037463.0,,Not applicable,09-01-2019,22-05-2024,St Radigund's Road,,,Dover,Kent,CT17 0LB,http://www.whitecliffsprimary.com,1304206174.0,Mrs,Helen,Castle,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,St Radigunds,Dover,(England/Wales) Urban city and town,E10000016,630188.0,142202.0,Dover 011,Dover 011F,,,,,Good,South-East England and South London,,200002882635.0,,Not applicable,Not applicable,,,E02005051,E01024240,186.0,
|
||||
138169,886,Kent,2310,Barton Junior School,Academy converter,Academies,Open,Academy Converter,01-06-2012,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,240.0,No Special Classes,19-01-2023,215.0,121.0,94.0,53.5,Supported by a multi-academy trust,SAMPHIRE STAR EDUCATION TRUST,-,,Not applicable,,10037462.0,,Not applicable,05-12-2018,22-05-2024,Barton Road,,,Dover,Kent,CT16 2ND,www.bartonjuniorschool.org/,1304201643.0,Miss,Melanie,O'Dell,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,St Radigunds,Dover,(England/Wales) Urban city and town,E10000016,631229.0,142506.0,Dover 012,Dover 012C,,,,,Good,South-East England and South London,,100062289419.0,,Not applicable,Not applicable,,,E02005052,E01024239,115.0,
|
||||
138170,886,Kent,2316,Shatterlocks Infant and Nursery School,Academy converter,Academies,Open,Academy Converter,01-06-2012,,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,232.0,No Special Classes,19-01-2023,202.0,90.0,112.0,40.5,Supported by a multi-academy trust,SAMPHIRE STAR EDUCATION TRUST,-,,Not applicable,,10037459.0,,Not applicable,16-05-2019,27-02-2024,Heathfield Avenue,,,Dover,Kent,CT16 2PB,http://www.shatterlocks.com,1304204264.0,Miss,Melanie,O'Dell,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Buckland,Dover,(England/Wales) Urban city and town,E10000016,630755.0,142875.0,Dover 011,Dover 011A,,,,,Outstanding,South-East England and South London,,100062289420.0,,Not applicable,Not applicable,,,E02005051,E01024193,77.0,
|
||||
138195,886,Kent,2007,Molehill Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-06-2012,,,Primary,3.0,11,,Has Nursery Classes,Not applicable,Mixed,None,None,Not applicable,Not applicable,315.0,Not applicable,19-01-2023,303.0,159.0,144.0,43.2,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10037485.0,,Not applicable,14-06-2023,03-06-2024,Hereford Road,,,Maidstone,Kent,ME15 7ND,http://www.molehillprimaryacademy.org.uk,1622751729.0,Principal,Laura,Smith,Head of Academy,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,HI - Hearing Impairment,"SLCN - Speech, language and Communication",,,,,,,,,,,,Resourced provision,5.0,10.0,,,South East,Maidstone,Shepway South,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,577701.0,153158.0,Maidstone 013,Maidstone 013E,,,,,Good,South-East England and South London,,200003717486.0,,Not applicable,Not applicable,,,E02005080,E01024398,128.0,
|
||||
138232,886,Kent,2008,Tiger Primary School,Free schools,Free Schools,Open,New Provision,03-09-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,427.0,217.0,210.0,33.7,Supported by a multi-academy trust,FUTURE SCHOOLS TRUST,Linked to a sponsor,Future Schools Trust,Not applicable,,10038742.0,,Not applicable,20-09-2023,22-11-2023,Boughton Lane,,,Maidstone,Kent,ME15 9QL,www.futureschoolstrust.com,1622745166.0,Mr,Daniel,Siggs,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,South,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576813.0,153053.0,Maidstone 012,Maidstone 012D,Ofsted,,,,Good,South-East England and South London,,200003677498.0,,Not applicable,Not applicable,,,E02005079,E01024404,144.0,
|
||||
138405,886,Kent,6138,Earlscliffe (Sussex Summer Schools Ltd),Other independent school,Independent schools,Open,New Provision,17-07-2012,,,Not applicable,14.0,19,Boarding school,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Not applicable,160.0,Not applicable,20-01-2022,129.0,83.0,46.0,0.0,Not applicable,,Not applicable,,Not applicable,,10037987.0,,Not applicable,28-11-2019,08-04-2024,29 Shorncliffe Road,,,Folkestone,Kent,CT20 2NB,www.earlscliffe.co.uk,1303253951.0,Mr,Niall,Johnson,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not approved,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Folkestone Central,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,621725.0,136091.0,Folkestone and Hythe 006,Folkestone and Hythe 006A,ISI,,12.0,,,South-East England and South London,,50033584.0,,Not applicable,Not applicable,,,E02005107,E01024511,0.0,
|
||||
138434,886,Kent,2009,Northdown Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2012,,,Primary,3.0,11,No boarders,Has Nursery Classes,Not applicable,Mixed,None,None,Not applicable,Not applicable,427.0,Not applicable,19-01-2023,325.0,157.0,168.0,73.5,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10038398.0,,Not applicable,24-11-2021,09-05-2024,Tenterden Way,,,Margate,Kent,CT9 3RE,www.northdown-tkat.org,1843226077.0,Mr,Matthew,Harris,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Dane Valley,North Thanet,(England/Wales) Urban city and town,E10000016,637269.0,170097.0,Thanet 006,Thanet 006C,,,,,Good,South-East England and South London,,100062307407.0,,Not applicable,Not applicable,,,E02005137,E01024662,228.0,
|
||||
138436,886,Kent,2010,Newlands Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2012,,,Primary,4.0,11,Not applicable,Not applicable,Not applicable,Mixed,None,None,Not applicable,Not applicable,315.0,Not applicable,19-01-2023,291.0,159.0,132.0,60.8,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10038052.0,,Not applicable,02-11-2022,26-04-2024,Dumpton Lane,,,Ramsgate,Kent,CT11 7AJ,www.newlands-tkat.org,1843593086.0,Mr,David,Bailey,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Sir Moses Montefiore,South Thanet,(England/Wales) Urban city and town,E10000016,638271.0,166231.0,Thanet 012,Thanet 012C,,,,,Good,South-East England and South London,,100062282198.0,,Not applicable,Not applicable,,,E02005143,E01024699,177.0,
|
||||
138438,886,Kent,2011,Salmestone Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2012,,,Primary,3.0,11,,Has Nursery Classes,Not applicable,Mixed,None,None,Not applicable,Non-selective,210.0,Not applicable,19-01-2023,215.0,114.0,101.0,41.4,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10038040.0,,Not applicable,23-01-2019,22-02-2024,College Road,,,Margate,Kent,CT9 4DB,www.salmestone-tkat.org/,1843220949.0,Mr,Thomas,Platten,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Salmestone,North Thanet,(England/Wales) Urban city and town,E10000016,635418.0,169813.0,Thanet 003,Thanet 003C,,,,,Good,South-East England and South London,,100062307602.0,,Not applicable,Not applicable,,,E02005134,E01024695,84.0,
|
||||
138480,886,Kent,4101,The Harvey Grammar School,Academy converter,Academies,Open,Academy Converter,01-08-2012,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,Does not apply,Does not apply,Not applicable,Selective,980.0,No Special Classes,19-01-2023,1026.0,1026.0,0.0,10.8,Supported by a multi-academy trust,THE HARVEY ACADEMY,-,,Not applicable,,10038103.0,,Not applicable,14-12-2022,21-05-2024,Cheriton Road,,,Folkestone,Kent,CT19 5JY,http://www.harveygs.kent.sch.uk/,1303252131.0,Mr,S,Norman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Broadmead,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,621203.0,136613.0,Folkestone and Hythe 015,Folkestone and Hythe 015D,,,,,Outstanding,South-East England and South London,,50032937.0,,Not applicable,Not applicable,,,E02006880,E01024517,81.0,
|
||||
138579,886,Kent,2013,Water Meadows Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2012,,,Primary,4.0,11,,Not applicable,Not applicable,Mixed,None,None,Not applicable,Not applicable,150.0,Not applicable,19-01-2023,148.0,72.0,76.0,43.5,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10038477.0,,Not applicable,20-03-2019,28-05-2024,Shaftesbury Road,Hersden,,Canterbury,Kent,CT3 4HS,www.watermeadows.kent.sch.uk,1227710414.0,Mr,Ben,Martin,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Sturry,North Thanet,(England/Wales) Rural town and fringe,E10000016,620217.0,162160.0,Canterbury 010,Canterbury 010C,,,,,Good,South-East England and South London,,200000683090.0,,Not applicable,Not applicable,,,E02005019,E01024086,64.0,
|
||||
138592,886,Kent,2014,St Laurence In Thanet Church of England Junior Academy,Academy sponsor led,Academies,Open,New Provision,01-12-2012,,,Primary,7.0,11,,Not applicable,Not applicable,Mixed,Church of England,None,Diocese of Canterbury,Not applicable,256.0,Not applicable,19-01-2023,177.0,95.0,82.0,60.5,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10038488.0,,Not applicable,04-07-2018,07-05-2024,Newington Road,,,Ramsgate,Kent,CT11 0QX,www.stlaurencejuniors.co.uk/,1843592257.0,Ms,Sarah,Graham,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Newington,South Thanet,(England/Wales) Urban city and town,E10000016,636976.0,165548.0,Thanet 013,Thanet 013C,,,,,Good,South-East England and South London,,200002234791.0,,Not applicable,Not applicable,,,E02005144,E01024684,107.0,
|
||||
138737,886,Kent,3086,West Malling Church of England Primary School and McGinty Speech and Language Srp,Academy converter,Academies,Open,Academy Converter,01-09-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,Has Special Classes,19-01-2023,207.0,109.0,98.0,24.6,Supported by a multi-academy trust,THE TENAX SCHOOLS TRUST,Linked to a sponsor,The Tenax Schools Trust,Not applicable,,10037972.0,,Not applicable,25-01-2023,06-06-2024,Old Cricket Ground,Norman Road,,West Malling,Kent,ME19 6RL,http://www.west-malling.kent.sch.uk,1732842061.0,Mr,David,Rye,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,14.0,21.0,,,South East,Tonbridge and Malling,"East Malling, West Malling & Offham",Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,567936.0,157853.0,Tonbridge and Malling 014,Tonbridge and Malling 014C,,,,,Good,South-East England and South London,,100062388025.0,,Not applicable,Not applicable,,,E02006833,E01024783,51.0,
|
||||
138738,886,Kent,3128,Sturry Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,406.0,195.0,211.0,26.8,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10038650.0,,Not applicable,28-01-2015,08-04-2024,Park View,Sturry,,Canterbury,Kent,CT2 0NR,http://www.sturry.kent.sch.uk,1227710477.0,Mrs,M,Mannings,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Sturry,Canterbury,(England/Wales) Rural town and fringe,E10000016,617998.0,161082.0,Canterbury 011,Canterbury 011C,,,,,Outstanding,South-East England and South London,,200000679701.0,,Not applicable,Not applicable,,,E02005020,E01024111,109.0,
|
||||
138972,886,Kent,2015,Dame Janet Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-12-2012,,,Primary,4.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,360.0,Not applicable,19-01-2023,371.0,193.0,178.0,58.1,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10039504.0,,Not applicable,03-10-2018,22-02-2024,Newington Road,,,Ramsgate,Kent,CT12 6QY,www.damejanet-tkat.org/,1843591807.0,Mr,Sam,Atkinson,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Northwood,South Thanet,(England/Wales) Urban city and town,E10000016,636796.0,166666.0,Thanet 011,Thanet 011B,,,,,Good,South-East England and South London,,100062284279.0,,Not applicable,Not applicable,,,E02005142,E01024688,215.0,
|
||||
139021,886,Kent,2017,Drapers Mills Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-12-2012,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,330.0,169.0,161.0,66.1,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10039726.0,,Not applicable,01-11-2023,18-03-2024,St Peter's Footpath,,,Margate,Kent,CT9 2SP,http://www.drapersmillsprimary.co.uk/,1843223989.0,Mrs,Kathleen,Davis,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Dane Valley,North Thanet,(England/Wales) Urban city and town,E10000016,636260.0,169934.0,Thanet 004,Thanet 004B,,,,,Good,South-East England and South London,,200003079364.0,,Not applicable,Not applicable,,,E02005135,E01024664,218.0,
|
||||
139052,886,Kent,2018,Temple Grove Academy,Academy sponsor led,Academies,Open,New Provision,01-01-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,210.0,Not applicable,19-01-2023,208.0,96.0,112.0,47.0,Supported by a single-academy trust,TEMPLE GROVE ACADEMY TRUST,Linked to a sponsor,Temple Grove Schools Trust,Not applicable,,10039910.0,,Not applicable,18-09-2019,10-04-2024,Friars Way,,,Tunbridge Wells,Kent,TN2 3UA,http://www.templegroveacademy.com/,1892520562.0,Mrs,Rebekah,Leeves,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Sherwood,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,559898.0,140821.0,Tunbridge Wells 005,Tunbridge Wells 005B,,,,,Good,South-East England and South London,,10000066049.0,,Not applicable,Not applicable,,,E02005166,E01024841,93.0,
|
||||
139075,886,Kent,4004,Meopham School,Academy sponsor led,Academies,Open,New Provision,01-02-2013,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,832.0,Not applicable,19-01-2023,989.0,505.0,484.0,16.0,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10039851.0,,Not applicable,20-04-2023,16-04-2024,Wrotham Road,Meopham,,Gravesend,Kent,DA13 0AH,meophamschool.org.uk,1474814646.0,Mr,Glenn,Prebble,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision and SEN unit,20.0,20.0,20.0,20.0,South East,Gravesham,Meopham North,Gravesham,(England/Wales) Rural village,E10000016,564231.0,165820.0,Gravesham 012,Gravesham 012D,,,,,Good,South-East England and South London,,100062313194.0,,Not applicable,Not applicable,,,E02005066,E01024271,144.0,
|
||||
139096,886,Kent,5209,Allington Primary School,Academy converter,Academies,Open,Academy Converter,01-12-2012,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,465.0,223.0,242.0,4.9,Supported by a multi-academy trust,ORCHARD ACADEMY TRUST,Linked to a sponsor,Allington Primary School Academy Trust (Orchard Academy Trust),Not applicable,,10039700.0,,Not applicable,13-07-2022,16-04-2024,Hildenborough Crescent,London Road,,Maidstone,Kent,ME16 0PG,www.allington.kent.sch.uk/,1622757350.0,Mrs,C,Howson,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Allington,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574093.0,157190.0,Maidstone 003,Maidstone 003D,,,,,Outstanding,South-East England and South London,,200003717229.0,,Not applicable,Not applicable,,,E02005070,E01024323,23.0,
|
||||
139186,886,Kent,2307,Warden House Primary School,Academy converter,Academies,Open,Academy Converter,01-01-2013,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,437.0,228.0,209.0,36.2,Supported by a multi-academy trust,VERITAS MULTI ACADEMY TRUST,Linked to a sponsor,Veritas Multi Academy Trust,Not applicable,,10040005.0,,Not applicable,03-12-2014,21-05-2024,Birdwood Avenue,,,Deal,Kent,CT14 9SF,http://www.warden-house.kent.sch.uk,1304375040.0,Mr,Robert,Hackett,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Middle Deal,Dover,(England/Wales) Urban city and town,E10000016,636229.0,152116.0,Dover 003,Dover 003A,,,,,Outstanding,South-East England and South London,,100062287038.0,,Not applicable,Not applicable,,,E02005043,E01024219,158.0,
|
||||
139254,886,Kent,2019,Chantry Community Primary School,Academy sponsor led,Academies,Open,New Provision,01-06-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,454.0,229.0,225.0,43.7,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10040148.0,,Not applicable,27-01-2022,18-04-2024,Ordnance Road,,,Gravesend,Kent,DA12 2RL,www.chantryprimary.co.uk,1474350011.0,Mrs,Kathryn,Duncan,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Town,Gravesham,(England/Wales) Urban major conurbation,E10000016,565390.0,174044.0,Gravesham 002,Gravesham 002E,,,,,Good,South-East England and South London,,100062311901.0,,Not applicable,Not applicable,,,E02005056,E01024295,176.0,
|
||||
139255,886,Kent,2020,"Christ Church Church of England Junior School, Ramsgate",Academy sponsor led,Academies,Open,New Provision,01-12-2013,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Not applicable,240.0,Not applicable,19-01-2023,200.0,104.0,96.0,43.5,Supported by a single-academy trust,"CHRIST CHURCH CHURCH OF ENGLAND JUNIOR SCHOOL, RAMSGATE",Linked to a sponsor,The Diocese of Canterbury Academies Company Limited,Not applicable,,10040135.0,,Not applicable,10-11-2021,14-05-2024,London Road,,,Ramsgate,Kent,CT11 0ZZ,www.christchurchjuniors.com/,1843593350.0,Mr,Neil,Tucker,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Cliffsend and Pegwell,South Thanet,(England/Wales) Urban city and town,E10000016,637368.0,164573.0,Thanet 017,Thanet 017B,,,,,Good,South-East England and South London,,100062628044.0,,Not applicable,Not applicable,,,E02005148,E01024651,87.0,
|
||||
139309,886,Kent,3148,"Christ Church Cep Academy, Folkestone",Academy converter,Academies,Open,Academy Converter,01-03-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,422.0,193.0,229.0,46.7,Supported by a single-academy trust,"CHRIST CHURCH CHURCH OF ENGLAND PRIMARY ACADEMY, FOLKESTONE",-,,Not applicable,,10040025.0,,Not applicable,01-12-2022,21-05-2024,Brockman Road,,,Folkestone,Kent,CT20 1DJ,www.christchurchfolkestone.com/,1303253645.0,Mr,Robin,Flack,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Folkestone Central,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,622492.0,136267.0,Folkestone and Hythe 014,Folkestone and Hythe 014B,,,,,Good,South-East England and South London,,50041734.0,,Not applicable,Not applicable,,,E02006879,E01024507,197.0,
|
||||
139310,886,Kent,3349,Folkestone St. Mary's Church of England Primary Academy,Academy converter,Academies,Open,Academy Converter,01-02-2013,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,449.0,228.0,221.0,36.3,Supported by a single-academy trust,FOLKESTONE ST MARY'S CHURCH OF ENGLAND PRIMARY ACADEMY,-,,Not applicable,,10040279.0,,Not applicable,20-10-2021,08-05-2024,Warren Road,,,Folkestone,Kent,CT19 6QH,www.stmarysfolkestone.com,1303251390.0,Mrs,Amanda,Wolfram,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Folkestone and Hythe,Folkestone Harbour,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,623841.0,136685.0,Folkestone and Hythe 003,Folkestone and Hythe 003D,,,,,Good,South-East England and South London,,50046311.0,,Not applicable,Not applicable,,,E02005104,E01024503,144.0,
|
||||
139315,886,Kent,3348,St Eanswythe's Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,212.0,94.0,118.0,25.0,Supported by a single-academy trust,ST EANSWYTHE'S CHURCH OF ENGLAND PRIMARY SCHOOL,-,,Not applicable,,10040285.0,,Not applicable,13-03-2019,31-05-2024,Church Street,,,Folkestone,Kent,CT20 1SE,http://www.st-eanswythes.kent.sch.uk/,1303255516.0,Headteacher,Claire,Jacobs,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Folkestone Central,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,622986.0,135909.0,Folkestone and Hythe 014,Folkestone and Hythe 014D,,,,,Outstanding,South-East England and South London,,50036799.0,,Not applicable,Not applicable,,,E02006879,E01033215,53.0,
|
||||
139396,886,Kent,2021,Kemsley Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-04-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,232.0,112.0,120.0,31.4,Supported by a multi-academy trust,REACH2 ACADEMY TRUST,Linked to a sponsor,REAch2 Academy Trust,Not applicable,,10040770.0,,Not applicable,14-02-2019,16-01-2024,Coldharbour Lane,Kemsley,,Sittingbourne,Kent,ME10 2RP,www.kemsley.kent.sch.uk,1795428689.0,,Iris,Homer,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Kemsley,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,590740.0,166282.0,Swale 007,Swale 007J,,,,,Good,South-East England and South London,,10035063593.0,,Not applicable,Not applicable,,,E02005121,E01035303,69.0,
|
||||
139397,886,Kent,2022,Milton Court Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-04-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,238.0,Not applicable,19-01-2023,245.0,121.0,124.0,52.7,Supported by a multi-academy trust,REACH2 ACADEMY TRUST,Linked to a sponsor,REAch2 Academy Trust,Not applicable,,10040771.0,,Not applicable,18-09-2019,13-05-2024,Brewery Road,Milton Regis,,Sittingbourne,Kent,ME10 2EE,www.milton-court.kent.sch.uk,1795472972.0,Miss,Sarah,Gadsdon,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Milton Regis,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,590434.0,164874.0,Swale 010,Swale 010B,,,,,Good,South-East England and South London,,100062375804.0,,Not applicable,Not applicable,,,E02005124,E01024584,125.0,
|
||||
139436,886,Kent,2023,Temple Ewell Church of England Primary School,Academy sponsor led,Academies,Open,New Provision,01-02-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Not applicable,140.0,Not applicable,19-01-2023,145.0,74.0,71.0,18.6,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10041585.0,,Not applicable,04-07-2023,26-03-2024,3-4 Brookside,Temple Ewell,,Dover,Kent,CT16 3DT,www.temple-ewell.kent.sch.uk/,1304822665.0,Mrs,Angela,Matthews,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Dover Downs & River,Dover,(England/Wales) Urban city and town,E10000016,628536.0,144298.0,Dover 010,Dover 010G,,,,,Good,South-East England and South London,,100062289542.0,,Not applicable,Not applicable,,,E02005050,E01033210,27.0,
|
||||
139542,886,Kent,5409,Wrotham School,Academy converter,Academies,Open,Academy Converter,01-04-2013,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,716.0,No Special Classes,19-01-2023,1029.0,553.0,476.0,16.3,Supported by a multi-academy trust,CHARACTER EDUCATION TRUST,Linked to a sponsor,Wrotham School,Not applicable,,10035479.0,,Not applicable,22-05-2019,06-06-2024,Borough Green Road,Wrotham,,Sevenoaks,Kent,TN15 7RD,http://www.wrothamschool.com/,1732905860.0,Mr,Michael,Cater,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Pilgrims with Ightham,Tonbridge and Malling,(England/Wales) Rural village,E10000016,561109.0,158269.0,Tonbridge and Malling 006,Tonbridge and Malling 006F,,,,,Good,South-East England and South London,,100062550537.0,,Not applicable,Not applicable,,,E02005154,E01024786,139.0,
|
||||
139554,886,Kent,4006,Trinity School,Free schools,Free Schools,Open,New Provision,01-09-2013,,,Secondary,11.0,19,Not applicable,No Nursery Classes,Has a sixth form,Mixed,Christian,Christian,,Non-selective,1140.0,Not applicable,19-01-2023,1130.0,619.0,511.0,14.8,Supported by a single-academy trust,TRINITY SCHOOL SEVENOAKS LTD,-,,Not applicable,,10041643.0,,Not applicable,02-10-2018,02-05-2024,Seal Hollow Rd,,,Sevenoaks,Kent,TN13 3SL,http://www.trinitysevenoaks.org.uk/,1732469111.0,Dr,Matthew,Pawson,Headmaster,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Seal and Weald,Sevenoaks,(England/Wales) Urban city and town,E10000016,554061.0,156627.0,Sevenoaks 012,Sevenoaks 012A,Ofsted,,,,Good,South-East England and South London,,10013774563.0,,Not applicable,Not applicable,,,E02005098,E01024458,136.0,
|
||||
139615,886,Kent,2511,Hartley Primary Academy,Academy converter,Academies,Open,Academy Converter,01-05-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,443.0,230.0,213.0,9.3,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10041353.0,,Not applicable,11-10-2023,16-04-2024,Round Ash Way,Hartley,,Longfield,Kent,DA3 8BT,www.hartleyprimaryacademy.org.uk/,1474702742.0,Mr,Stuart,Mitchell,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Sevenoaks,Hartley and Hodsoll Street,Dartford,(England/Wales) Urban city and town,E10000016,560221.0,167409.0,Sevenoaks 004,Sevenoaks 004C,,,,,Outstanding,South-East England and South London,,100062688333.0,,Not applicable,Not applicable,,,E02005090,E01024443,40.0,
|
||||
139664,886,Kent,4007,Wye School,Free schools,Free Schools,Open,New Provision,03-09-2013,,,Secondary,11.0,18,Not applicable,No Nursery Classes,Has a sixth form,Mixed,None,None,,Non-selective,600.0,Not applicable,19-01-2023,573.0,295.0,278.0,17.5,Supported by a multi-academy trust,UNITED LEARNING TRUST,Linked to a sponsor,United Learning Trust,Not applicable,,10041669.0,,Not applicable,11-12-2018,15-04-2024,Olantigh Road,,,Wye,Kent,TN25 5EJ,http://www.wyeschool.org.uk/,1233811110.0,Mr,Luke,Magee,Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Wye with Hinxhill,Ashford,(England/Wales) Rural town and fringe,E10000016,605615.0,146915.0,Ashford 001,Ashford 001E,Ofsted,,,,Good,South-East England and South London,,10012843405.0,,Not applicable,Not applicable,,,E02004996,E01024041,88.0,
|
||||
139685,886,Kent,2024,Copperfield Academy,Academy sponsor led,Academies,Open,New Provision,01-11-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,480.0,Not applicable,19-01-2023,443.0,231.0,212.0,34.1,Supported by a multi-academy trust,REACH2 ACADEMY TRUST,Linked to a sponsor,REAch2 Academy Trust,Not applicable,,10041614.0,,Not applicable,06-05-2021,13-09-2023,Dover Road East,Northfleet,,Gravesend,Kent,DA11 0RB,www.copperfieldacademy.org,1474352488.0,,Ben,Clark,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Coldharbour & Perry Street,Gravesham,(England/Wales) Urban major conurbation,E10000016,563486.0,173483.0,Gravesham 004,Gravesham 004D,,,,,Good,South-East England and South London,,100062310549.0,,Not applicable,Not applicable,,,E02005058,E01024283,145.0,
|
||||
139696,886,Kent,2025,The Wells Free School,Free schools,Free Schools,Open,New Provision,01-09-2013,,,Primary,4.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,None,None,,Not applicable,210.0,Not applicable,19-01-2023,208.0,115.0,93.0,13.0,Supported by a single-academy trust,THE WELLS FREE SCHOOL,-,,Not applicable,,10041721.0,,Not applicable,18-06-2019,07-05-2024,King Charles Square,,,Tunbridge Wells,Kent,TN4 8FA,www.thewellsfreeschool.co.uk/,1892739075.0,Mrs,Katharine,Le Page,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Culverden,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558235.0,139866.0,Tunbridge Wells 008,Tunbridge Wells 008E,Ofsted,,,,Good,South-East England and South London,,10024139359.0,,Not applicable,Not applicable,,,E02005169,E01035012,27.0,
|
||||
139697,886,Kent,4009,Hadlow Rural Community School,Free schools,Free Schools,Open,New Provision,01-09-2013,,,Secondary,11.0,16,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,None,None,,Non-selective,375.0,Not applicable,19-01-2023,371.0,224.0,147.0,24.0,Supported by a single-academy trust,HADLOW RURAL COMMUNITY SCHOOL LIMITED,-,,Not applicable,,10041646.0,,Not applicable,26-02-2019,20-05-2024,Tonbridge Road,,,Hadlow,Kent,TN11 0AU,www.hrcschool.org,1732498120.0,Mr,Paul,Boxall,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Bourne,Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,562706.0,149711.0,Tonbridge and Malling 008,Tonbridge and Malling 008C,Ofsted,,,,Good,South-East England and South London,,10092971671.0,,Not applicable,Not applicable,,,E02005156,E01024745,89.0,
|
||||
139810,886,Kent,2026,Petham Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,115.0,Not applicable,19-01-2023,103.0,50.0,53.0,16.5,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10042015.0,,Not applicable,05-07-2019,31-05-2024,Petham,,,Canterbury,Kent,CT4 5RD,http://www.petham.kent.sch.uk/,1227700260.0,Mr,Scott,Guy,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Chartham & Stone Street,Canterbury,(England/Wales) Rural village,E10000016,613071.0,151303.0,Canterbury 017,Canterbury 017C,,,,,Good,South-East England and South London,,200000675436.0,,Not applicable,Not applicable,,,E02005026,E01024054,17.0,
|
||||
139822,886,Kent,2027,Archbishop Courtenay Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Church of England,Diocese of Canterbury,Not applicable,315.0,Not applicable,19-01-2023,307.0,162.0,145.0,36.2,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10042411.0,,Not applicable,07-06-2023,15-04-2024,Eccleston Road,Tovil,,Maidstone,Kent,ME15 6QN,www.archbishopcourtenay.org.uk/,1622754666.0,Mrs,Sue,Heather,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,South,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575645.0,154791.0,Maidstone 009,Maidstone 009D,,,,,Good,South-East England and South London,,10014313238.0,,Not applicable,Not applicable,,,E02005076,E01024401,111.0,
|
||||
140012,886,Kent,2028,Cliftonville Primary School,Academy sponsor led,Academies,Open,New Provision,01-12-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,862.0,Not applicable,19-01-2023,828.0,407.0,421.0,49.1,Supported by a multi-academy trust,COASTAL ACADEMIES TRUST,Linked to a sponsor,Coastal Academies Trust,Not applicable,,10042921.0,,Not applicable,18-01-2023,22-05-2024,Northumberland Avenue,Cliftonville,,Margate,Kent,CT9 3LY,www.cliftonvilleprimary.co.uk/,1843227575.0,Ms,Claire,Whichcord,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Cliftonville East,South Thanet,(England/Wales) Urban city and town,E10000016,637095.0,170622.0,Thanet 002,Thanet 002C,,,,,Outstanding,South-East England and South London,,100062307559.0,,Not applicable,Not applicable,,,E02005133,E01024655,390.0,
|
||||
140167,886,Kent,2029,Tymberwood Academy,Academy sponsor led,Academies,Open,New Provision,01-02-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,384.0,186.0,198.0,48.7,Supported by a multi-academy trust,REACH2 ACADEMY TRUST,Linked to a sponsor,REAch2 Academy Trust,Not applicable,,10043284.0,,Not applicable,03-03-2022,03-04-2024,Cerne Road,,,Gravesend,Kent,DA12 4BN,www.tymberwoodacademy.org/,1474361193.0,Mr,Frazer,Westmorland,Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,10.0,8.0,,,South East,Gravesham,Riverview Park,Gravesham,(England/Wales) Urban major conurbation,E10000016,566437.0,172049.0,Gravesham 007,Gravesham 007B,,,,,Good,South-East England and South London,,100062312652.0,,Not applicable,Not applicable,,,E02005061,E01024310,175.0,
|
||||
140168,886,Kent,2030,Valley Invicta Primary School At Aylesford,Academy sponsor led,Academies,Open,New Provision,01-12-2013,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,315.0,Not applicable,19-01-2023,381.0,189.0,192.0,18.4,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10043311.0,,Not applicable,11-05-2023,25-04-2024,Teapot Lane,,,Aylesford,Kent,ME20 7JU,www.aylesford.viat.org.uk/,1622718192.0,Mr,Billy,Harrington,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford South & Ditton,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,571993.0,158332.0,Tonbridge and Malling 005,Tonbridge and Malling 005B,,,,,Outstanding,South-East England and South London,,100062389712.0,,Not applicable,Not applicable,,,E02005153,E01024718,70.0,
|
||||
140322,886,Kent,2686,Furley Park Primary Academy,Academy converter,Academies,Open,Academy Converter,01-11-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,575.0,294.0,281.0,16.7,Supported by a multi-academy trust,ACE LEARNING,-,,Not applicable,,10043951.0,,Not applicable,06-07-2022,18-04-2024,Reed Crescent,Park Farm,Kingsnorth,Ashford,Kent,TN23 3PA,www.furleypark.org.uk/,1233501732.0,Mrs,Emma,Collip,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Park Farm South,Ashford,(England/Wales) Urban city and town,E10000016,601223.0,138948.0,Ashford 009,Ashford 009I,,,,,Requires improvement,South-East England and South London,,200002407842.0,,Not applicable,Not applicable,,,E02005004,E01032819,96.0,
|
||||
140323,886,Kent,2286,Hamstreet Primary Academy,Academy converter,Academies,Open,Academy Converter,01-11-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,315.0,No Special Classes,19-01-2023,269.0,132.0,137.0,15.2,Supported by a multi-academy trust,ACE LEARNING,-,,Not applicable,,10043950.0,,Not applicable,18-05-2023,29-05-2024,Hamstreet,,,Ashford,Kent,TN26 2EA,www.ham-street.org.uk/,1233732577.0,Mrs,Helen,Glancy,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Weald South,Ashford,(England/Wales) Rural town and fringe,E10000016,599958.0,133831.0,Ashford 014,Ashford 014D,,,,,Good,South-East England and South London,,10012848068.0,,Not applicable,Not applicable,,,E02005009,E01024037,41.0,
|
||||
140393,886,Kent,2034,Thistle Hill Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,359.0,183.0,176.0,41.2,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10053864.0,,Not applicable,27-04-2022,16-04-2024,Aspen Drive,Minster-on-Sea,,Sheerness,Kent,ME12 3UD,http://www.thistlehill.kent.sch.uk,1795899119.0,Ms,Rebecca,Handebeaux,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,10.0,10.0,,,South East,Swale,Sheppey Central,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,594815.0,172145.0,Swale 004,Swale 004H,,,,,Requires improvement,South-East England and South London,,10023203143.0,,Not applicable,Not applicable,,,E02005118,E01035298,148.0,
|
||||
140430,886,Kent,2036,Valley Invicta Primary School At Leybourne Chase,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,None,,Not applicable,220.0,Not applicable,19-01-2023,227.0,119.0,108.0,11.0,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10053401.0,,Not applicable,22-02-2024,20-05-2024,Derby Drive,Leybourne Chase,,West Malling,Kent,ME19 5FF,www.leybournechase.viat.org.uk,1732840908.0,Mrs,Gemma,Robinson (Head of School),Executive Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,16.0,15.0,,,South East,Tonbridge and Malling,"Birling, Leybourne & Ryarsh",Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,567807.0,159097.0,Tonbridge and Malling 014,Tonbridge and Malling 014G,,,,,Good,South-East England and South London,,100062387483.0,,Not applicable,Not applicable,,,E02006833,E01035010,25.0,
|
||||
140431,886,Kent,2037,Valley Invicta Primary School at Holborough Lakes,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,227.0,Has Special Classes,19-01-2023,228.0,105.0,123.0,13.2,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10052657.0,,Not applicable,31-01-2024,20-05-2024,Holborough Lakes,Pollyfield Close,,Snodland,Kent,ME6 5GR,www.holboroughlakes.viat.org.uk,1634242839.0,Mrs,Lisa,Vickers,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,17.0,15.0,,,South East,Tonbridge and Malling,Snodland West & Holborough Lakes,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,570281.0,162312.0,Tonbridge and Malling 002,Tonbridge and Malling 002I,,,,,Good,South-East England and South London,,10013924379.0,,Not applicable,Not applicable,,,E02005150,E01035006,30.0,
|
||||
140432,886,Kent,2038,Valley Invicta Primary School At Kings Hill,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,222.0,122.0,100.0,13.1,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10053404.0,,Not applicable,14-03-2024,22-05-2024,Warwick Way (Off Tower View),Kings Hill,,West Malling,Kent,ME19 4AL,www.kingshill.viat.org.uk,1732841695.0,Mrs,Steph,Guthrie,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,15.0,15.0,,,South East,Tonbridge and Malling,Kings Hill,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,567363.0,155590.0,Tonbridge and Malling 007,Tonbridge and Malling 007E,,,,,Good,South-East England and South London,,10092970953.0,,Not applicable,Not applicable,,,E02005155,E01032825,29.0,
|
||||
140433,886,Kent,2039,Martello Primary,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,172.0,97.0,75.0,64.0,Supported by a multi-academy trust,TURNER SCHOOLS,Linked to a sponsor,Turner Schools,Not applicable,,10053863.0,,Not applicable,09-03-2022,24-05-2024,Warren Way,,,Folkestone,Kent,CT19 6DT,www.turnermartello.org,1303847540.0,Miss,Natalie,Barrow,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,17.0,15.0,,,South East,Folkestone and Hythe,Folkestone Harbour,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,623927.0,137063.0,Folkestone and Hythe 003,Folkestone and Hythe 003D,,,,,Good,South-East England and South London,,50114714.0,,Not applicable,Not applicable,,,E02005104,E01024503,110.0,
|
||||
140520,886,Kent,3719,"St Joseph's Catholic Primary School, Aylesham",Academy converter,Academies,Open,Academy Converter,01-01-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,133.0,No Special Classes,19-01-2023,131.0,72.0,59.0,36.6,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10044505.0,,Not applicable,02-11-2021,25-01-2024,Ackholt Road,Aylesham,,Canterbury,Kent,CT3 3AS,www.stjosephs-aylesham.co.uk,1304840370.0,Mrs,Hester,Seager-Fleming,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,"Aylesham, Eythorne & Shepherdswell",Dover,(England/Wales) Rural town and fringe,E10000016,624091.0,152415.0,Dover 006,Dover 006F,,,,,Good,South-East England and South London,,10034883661.0,,Not applicable,Not applicable,,,E02005046,E01035315,48.0,
|
||||
140521,886,Kent,2435,South Avenue Primary School,Academy converter,Academies,Open,Academy Converter,01-01-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,407.0,205.0,202.0,32.2,Supported by a multi-academy trust,FULSTON MANOR ACADEMIES TRUST,Linked to a sponsor,Fulston Manor Academies Trust,Not applicable,,10044503.0,,Not applicable,12-10-2022,30-05-2024,South Avenue,,,Sittingbourne,Kent,ME10 4SU,www.southavenue.kent.sch.uk,1795477750.0,Miss,Tracy,Cadwallader,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Roman,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591229.0,163193.0,Swale 010,Swale 010D,,,,,Good,South-East England and South London,,200002532192.0,,Not applicable,Not applicable,,,E02005124,E01024599,131.0,
|
||||
140537,886,Kent,5432,St Simon Stock Catholic School,Academy converter,Academies,Open,Academy Converter,01-01-2014,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,1018.0,No Special Classes,19-01-2023,1106.0,584.0,522.0,14.9,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10044507.0,,Not applicable,13-10-2021,23-04-2024,Oakwood Park,,,Maidstone,Kent,ME16 0JP,http://www.ssscs.co.uk/,1622754551.0,Mrs,Andrea,Denny,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Heath,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,574251.0,155595.0,Maidstone 008,Maidstone 008C,,,,,Good,South-East England and South London,,200003717928.0,,Not applicable,Not applicable,,,E02005075,E01024367,134.0,
|
||||
140592,886,Kent,2679,The Brent Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,630.0,No Special Classes,19-01-2023,652.0,322.0,330.0,25.0,Supported by a multi-academy trust,CYGNUS ACADEMIES TRUST,Linked to a sponsor,Cygnus Academies Trust,Not applicable,,10044811.0,,Not applicable,22-02-2023,15-04-2024,London Road,Stone,,Dartford,Kent,DA2 6BA,www.brent.kent.sch.uk,1322223943.0,Mrs,Sarah,Rye,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Stone House,Dartford,(England/Wales) Urban major conurbation,E10000016,556186.0,173935.0,Dartford 006,Dartford 006A,,,,,Outstanding,South-East England and South London,,200000534113.0,,Not applicable,Not applicable,,,E02005033,E01024169,162.0,
|
||||
140593,886,Kent,2685,The Gateway Primary Academy,Academy converter,Academies,Open,Academy Converter,01-02-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,210.0,110.0,100.0,12.4,Supported by a single-academy trust,THE GATEWAY PRIMARY ACADEMY,-,,Not applicable,,10044810.0,,Not applicable,29-06-2022,14-04-2024,Milestone Road,,,Dartford,Kent,DA2 6DW,www.gateway-pri.kent.sch.uk/,1322220090.0,Mr,Jamiel,Cassem,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Stone House,Dartford,(England/Wales) Urban major conurbation,E10000016,555850.0,174010.0,Dartford 005,Dartford 005D,,,,,Good,South-East England and South London,,200000544345.0,,Not applicable,Not applicable,,,E02005032,E01024163,26.0,
|
||||
140595,886,Kent,5418,The Skinners' School,Academy converter,Academies,Open,Academy Converter,01-02-2014,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,945.0,No Special Classes,19-01-2023,1118.0,1118.0,0.0,4.2,Supported by a multi-academy trust,SKINNERS' ACADEMIES TRUST,Linked to a sponsor,The Skinners' Company,Not applicable,,10044808.0,,Not applicable,17-11-2021,25-04-2024,St John's Road,,,Tunbridge Wells,Kent,TN4 9PG,http://www.skinners-school.co.uk,1892520732.0,Mr,Edward,Wesson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,VI - Visual Impairment,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,Resourced provision,2.0,2.0,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558264.0,140612.0,Tunbridge Wells 007,Tunbridge Wells 007D,,,,,Good,South-East England and South London,,10008661460.0,,Not applicable,Not applicable,,,E02005168,E01024838,34.0,
|
||||
140640,886,Kent,5435,St Gregory's Catholic School,Academy converter,Academies,Open,Academy Converter,01-03-2014,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,1082.0,No Special Classes,19-01-2023,1339.0,796.0,543.0,18.3,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10045187.0,,Not applicable,,07-05-2024,Reynolds Lane,,,Tunbridge Wells,Kent,TN4 9XL,http://www.sgschool.org.uk,1892527444.0,Mr,Michael,Wilson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,,,,,,,,,,,,,Resourced provision,,,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558104.0,141564.0,Tunbridge Wells 002,Tunbridge Wells 002A,,,,,,South-East England and South London,,100062586238.0,,Not applicable,Not applicable,,,E02005163,E01024837,201.0,
|
||||
140641,886,Kent,3890,"St Joseph's Catholic Primary School, Broadstairs",Academy converter,Academies,Open,Academy Converter,01-03-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,Not applicable,19-01-2023,199.0,100.0,99.0,30.7,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10045193.0,,Not applicable,09-06-2022,21-05-2024,"St Joseph's Catholic Primary School, Broadstairs",,St Peter's Park Road,Broadstairs,Kent,CT10 2BA,www.st-josephs-broadstairs.kent.sch.uk,1843861738.0,Mrs,Vicki,O'Halloran,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,Not Applicable,,,,,,,,,,,,,Resourced provision,186.0,210.0,,,South East,Thanet,St Peters,South Thanet,(England/Wales) Urban city and town,E10000016,638835.0,168425.0,Thanet 009,Thanet 009E,,,,,Requires improvement,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005140,E01024693,61.0,
|
||||
140800,886,Kent,3900,Whitehill Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,659.0,347.0,312.0,29.7,Supported by a multi-academy trust,THE DECUS EDUCATIONAL TRUST,Linked to a sponsor,Gravesend Grammar School,Not applicable,,10045602.0,,Not applicable,28-02-2024,22-05-2024,Sun Lane,,,Gravesend,Kent,DA12 5HN,www.whitehillprimary.com,1474352973.0,Mrs,Angela,Carpenter,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Gravesham,Whitehill & Windmill Hill,Gravesham,(England/Wales) Urban major conurbation,E10000016,565183.0,172378.0,Gravesham 005,Gravesham 005D,,,,,Requires improvement,South-East England and South London,,10012028072.0,,Not applicable,Not applicable,,,E02005059,E01024313,185.0,
|
||||
140873,886,Kent,3889,"St Gregory's Catholic Primary School, Margate",Academy converter,Academies,Open,Academy Converter,01-05-2014,,,Primary,3.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,315.0,No Special Classes,19-01-2023,291.0,145.0,146.0,35.1,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10045950.0,,Not applicable,19-09-2019,14-05-2024,Nash Road,,,Margate,Kent,CT9 4BU,www.st-gregorys.kent.sch.uk,1843221896.0,Mr,David,Walker,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Salmestone,North Thanet,(England/Wales) Rural town and fringe,E10000016,635277.0,169530.0,Thanet 004,Thanet 004E,,,,,Good,South-East England and South London,,100062307601.0,,Not applicable,Not applicable,,,E02005135,E01024696,102.0,
|
||||
140874,886,Kent,5446,"St Anselm's Catholic School, Canterbury",Academy converter,Academies,Open,Academy Converter,01-05-2014,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,1071.0,No Special Classes,19-01-2023,1099.0,555.0,544.0,18.0,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10045938.0,,Not applicable,13-09-2023,05-06-2024,Old Dover Road,,,Canterbury,Kent,CT1 3EN,http://www.stanselmscanterbury.org.uk/,1227826200.0,Mr,J,Rowarth,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,PD - Physical Disability,,,,,,,,,,,,Resourced provision,16.0,15.0,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,616376.0,156068.0,Canterbury 016,Canterbury 016C,,,,,Good,South-East England and South London,,200000678848.0,,Not applicable,Not applicable,,,E02005025,E01024046,163.0,
|
||||
140899,886,Kent,2223,Bobbing Village School,Academy converter,Academies,Open,Academy Converter,01-06-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,208.0,114.0,94.0,8.2,Supported by a multi-academy trust,BOURNE ALLIANCE MULTI ACADEMY TRUST,Linked to a sponsor,TIMU Academy Trust,Not applicable,,10046193.0,,Not applicable,22-02-2023,19-03-2024,Sheppey Way,Bobbing,,Sittingbourne,Kent,ME9 8PL,www.bourne-alliance-mat.org.uk,1795423939.0,Mr,Tim,Harwood,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,"Bobbing, Iwade and Lower Halstow",Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,588784.0,165079.0,Swale 009,Swale 009F,,,,,Outstanding,South-East England and South London,,200002532781.0,,Not applicable,Not applicable,,,E02005123,E01035304,17.0,
|
||||
140900,886,Kent,2230,Iwade School,Academy converter,Academies,Open,Academy Converter,01-06-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,621.0,326.0,295.0,12.6,Supported by a multi-academy trust,BOURNE ALLIANCE MULTI ACADEMY TRUST,Linked to a sponsor,TIMU Academy Trust,Not applicable,,10046192.0,,Not applicable,22-09-2022,19-10-2023,School Lane,Iwade,,Sittingbourne,Kent,ME9 8RS,www.iwade-ba-mat.org.uk,1795472578.0,Mrs,Katrine,Stewart,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,"Bobbing, Iwade and Lower Halstow",Sittingbourne and Sheppey,(England/Wales) Rural town and fringe,E10000016,589970.0,167845.0,Swale 007,Swale 007G,,,,,Good,South-East England and South London,,100062397902.0,,Not applicable,Not applicable,,,E02005121,E01032655,78.0,
|
||||
140980,886,Kent,2041,The Holy Family Catholic Primary School,Academy sponsor led,Academies,Open,New Provision,01-06-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Roman Catholic,Archdiocese of Southwark,Not applicable,210.0,Not applicable,19-01-2023,209.0,112.0,97.0,51.7,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10046167.0,,Not applicable,12-10-2023,23-04-2024,Bicknor Road,Park Wood,,Maidstone,Kent,ME15 9PS,www.holyfamily.kent.sch.uk,1622756778.0,Mrs,Megan,Underhill,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Park Wood,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,578525.0,151872.0,Maidstone 013,Maidstone 013B,,,,,Good,South-East England and South London,,200003680167.0,,Not applicable,Not applicable,,,E02005080,E01024390,108.0,
|
||||
140987,886,Kent,4012,The Leigh UTC,University technical college,Free Schools,Open,New Provision,01-09-2014,,,Secondary,11.0,18,Not applicable,No Nursery Classes,Has a sixth form,Mixed,None,Does not apply,,,960.0,Not applicable,19-01-2023,730.0,558.0,172.0,28.8,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10047247.0,,Not applicable,26-05-2022,15-04-2024,Brunel Way,,,Dartford,Kent,DA1 5TF,http://theleighutc.org.uk/,1322626600.0,Mr,Kevin,Watson,Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Bridge,Dartford,(England/Wales) Rural town and fringe,E10000016,554583.0,175781.0,Dartford 001,Dartford 001F,,,,,Good,South-East England and South London,,10023439451.0,,Not applicable,Not applicable,,,E02005028,E01035271,178.0,
|
||||
141025,886,Kent,2043,Jubilee Primary School,Free schools,Free Schools,Open,New Provision,01-09-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Christian,,,420.0,Not applicable,19-01-2023,301.0,160.0,141.0,25.2,Supported by a single-academy trust,JUBILEE PRIMARY SCHOOL,-,,Not applicable,,10047086.0,,Not applicable,18-10-2023,23-04-2024,Gatland Lane,,,Maidstone,Kent,ME16 8PF,www.jubileeprimaryschool.org.uk,1622808873.0,Dr,Marilyn,Nadesan,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Fant,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,573703.0,154618.0,Maidstone 009,Maidstone 009A,,,,,Outstanding,South-East England and South London,,200003717426.0,,Not applicable,Not applicable,,,E02005076,E01024360,76.0,
|
||||
141065,886,Kent,3720,St Mary's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-07-2014,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,185.0,94.0,91.0,25.9,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10046463.0,,Not applicable,17-11-2022,26-04-2024,St Richard's Road,,,Deal,Kent,CT14 9LF,www.stmarysdeal.co.uk/,1304375046.0,Mrs,Maria,Pullen,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Mill Hill,Dover,(England/Wales) Urban city and town,E10000016,635925.0,151281.0,Dover 005,Dover 005C,,,,,Good,South-East England and South London,,200001851337.0,,Not applicable,Not applicable,,,E02005045,E01024223,48.0,
|
||||
141067,886,Kent,3743,"St Simon of England Roman Catholic Primary School, Ashford",Academy converter,Academies,Open,Academy Converter,01-07-2014,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,209.0,112.0,97.0,18.7,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10046324.0,,Not applicable,01-12-2022,21-05-2024,Noakes Meadow,,,Ashford,Kent,TN23 4RB,www.st-simon.kent.sch.uk,1233623199.0,Mr,Peter,McCabe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Beaver,Ashford,(England/Wales) Urban city and town,E10000016,600090.0,141538.0,Ashford 007,Ashford 007B,,,,,Requires improvement,South-East England and South London,,100062559274.0,,Not applicable,Not applicable,,,E02005002,E01023975,39.0,
|
||||
141085,886,Kent,2045,Skinners' Kent Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,None,None,,,210.0,Not applicable,19-01-2023,210.0,117.0,93.0,17.1,Supported by a multi-academy trust,SKINNERS' ACADEMIES TRUST,Linked to a sponsor,The Skinners' Company,Not applicable,,10053936.0,,Not applicable,07-02-2024,21-05-2024,The Avenue,Knights Wood,,Tunbridge Wells,Kent,TN2 3GS,www.skps.org.uk,1892553060.0,,Gemma,Wyatt,Executive Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Sherwood,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,560430.0,141932.0,Tunbridge Wells 003,Tunbridge Wells 003B,,,,,Good,South-East England and South London,,10008671585.0,,Not applicable,Not applicable,,,E02005164,E01024839,36.0,
|
||||
141156,886,Kent,3744,St Margaret Clitherow Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-08-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,420.0,No Special Classes,19-01-2023,397.0,191.0,206.0,7.3,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10046759.0,,Not applicable,02-11-2022,16-04-2024,Trench Road,,,Tonbridge,Kent,TN11 9NG,www.stmargaretclitherowschool.org.uk,1732358000.0,Mrs,Fiona,Oubridge,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tonbridge and Malling,Hildenborough,Tonbridge and Malling,(England/Wales) Rural village,E10000016,558678.0,148870.0,Tonbridge and Malling 010,Tonbridge and Malling 010D,,,,,Good,South-East England and South London,,10002908678.0,,Not applicable,Not applicable,,,E02005158,E01024754,29.0,
|
||||
141157,886,Kent,3751,"St Thomas' Catholic Primary School, Sevenoaks",Academy converter,Academies,Open,Academy Converter,01-08-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,240.0,No Special Classes,19-01-2023,218.0,108.0,110.0,8.4,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10046758.0,,Not applicable,,17-05-2024,South Park,,,Sevenoaks,Kent,TN13 1EH,www.saintthomas.co.uk,1732453921.0,Mrs,Geraldine,Leahy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Sevenoaks,Sevenoaks Town and St John's,Sevenoaks,(England/Wales) Urban city and town,E10000016,552746.0,154671.0,Sevenoaks 012,Sevenoaks 012F,,,,,,South-East England and South London,,10035182527.0,,Not applicable,Not applicable,,,E02005098,E01024471,17.0,
|
||||
141216,886,Kent,2048,Reculver Church of England Primary School,Academy sponsor led,Academies,Open,New Provision,01-07-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Church of England,Diocese of Canterbury,Not applicable,525.0,Not applicable,19-01-2023,493.0,247.0,246.0,20.7,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10047006.0,,Not applicable,04-07-2018,02-05-2024,Hillborough,,,Herne Bay,Kent,CT6 6TA,www.reculver.kent.sch.uk,1227375907.0,Mrs,Stella,Collins,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,VI - Visual Impairment,SLD - Severe Learning Difficulty,,,,,,,,,,,,Resourced provision,17.0,15.0,,,South East,Canterbury,Reculver,North Thanet,(England/Wales) Urban city and town,E10000016,621137.0,167984.0,Canterbury 002,Canterbury 002A,,,,,Outstanding,South-East England and South London,,10033163216.0,,Not applicable,Not applicable,,,E02005011,E01024094,102.0,
|
||||
141217,886,Kent,4013,St Edmund's Catholic School,Academy sponsor led,Academies,Open,,01-07-2016,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Roman Catholic,None,Archdiocese of Southwark,Non-selective,600.0,Not applicable,19-01-2023,567.0,296.0,271.0,36.5,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047005.0,,Not applicable,13-07-2022,21-05-2024,Old Charlton Road,,,Dover,Kent,CT16 2QB,www.st-edmunds.com,1304201551.0,Mrs,Grainne,Parsons,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Buckland,Dover,(England/Wales) Urban city and town,E10000016,631515.0,142620.0,Dover 011,Dover 011C,,,,,Good,South-East England and South London,,100062289422.0,,Not applicable,Not applicable,,,E02005051,E01024195,207.0,
|
||||
141220,886,Kent,2051,St Mary of Charity CofE (Aided) Primary School,Academy sponsor led,Academies,Open,New Provision,01-08-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Not applicable,210.0,Not applicable,19-01-2023,208.0,105.0,103.0,36.5,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10047011.0,,Not applicable,11-07-2018,28-03-2024,Orchard Place,,,Faversham,Kent,ME13 8AP,,1795532496.0,Mrs,Louise,Rowley-Jones,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Abbey,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,601949.0,161379.0,Swale 015,Swale 015A,,,,,Outstanding,South-East England and South London,,200002530657.0,,Not applicable,Not applicable,,,E02005129,E01024551,76.0,
|
||||
141308,886,Kent,3119,Adisham Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,97.0,49.0,48.0,19.6,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10047192.0,,Not applicable,29-11-2023,17-05-2024,The Street,Adisham,,Canterbury,Kent,CT3 3JW,www.adisham.kent.sch.uk/,1304840246.0,Miss,Sophie,Metcalf,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Little Stour & Adisham,Canterbury,(England/Wales) Rural village,E10000016,622676.0,153952.0,Canterbury 018,Canterbury 018A,,,,,Outstanding,South-East England and South London,,100062619270.0,,Not applicable,Not applicable,,,E02005027,E01024042,19.0,
|
||||
141329,886,Kent,2052,Kennington Church of England Academy,Academy sponsor led,Academies,Open,New Provision,01-11-2014,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Not applicable,360.0,Not applicable,19-01-2023,356.0,179.0,177.0,29.5,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10047449.0,,Not applicable,08-03-2023,29-01-2024,Upper Vicarage Road,Kennington,,Ashford,Kent,TN24 9AG,http://www.kenningtonacademy.co.uk,1233623744.0,Mrs,Karen,Godsell,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Kennington,Ashford,(England/Wales) Urban city and town,E10000016,602027.0,145151.0,Ashford 003,Ashford 003B,,,,,Good,South-East England and South London,,100062561303.0,,Not applicable,Not applicable,,,E02004998,E01023999,105.0,
|
||||
141386,886,Kent,2054,St Edward's Catholic Primary School,Academy sponsor led,Academies,Open,,01-07-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,None,Archdiocese of Southwark,Not applicable,210.0,Not applicable,19-01-2023,197.0,97.0,100.0,50.3,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047523.0,,Not applicable,22-05-2019,05-06-2024,New Road,,,Sheerness,Kent,ME12 1BW,www.st-edwards-sheerness.co.uk,1795662708.0,Mrs,Sara,Wakefield,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheerness,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591704.0,174591.0,Swale 002,Swale 002B,,,,,Good,South-East England and South London,,200002533178.0,,Not applicable,Not applicable,,,E02005116,E01024614,99.0,
|
||||
141471,886,Kent,3714,St Peter's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,212.0,107.0,105.0,11.8,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047621.0,,Not applicable,07-02-2024,20-05-2024,West Ridge,,,Sittingbourne,Kent,ME10 1UJ,www.st-peters-sittingbourne.co.uk,1795423479.0,Ms,Catherine,Vedamuttu,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Homewood,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,589880.0,162952.0,Swale 012,Swale 012D,,,,,Good,South-East England and South London,,200002527647.0,,Not applicable,Not applicable,,,E02005126,E01024630,25.0,
|
||||
141472,886,Kent,3745,More Park Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,207.0,95.0,112.0,8.7,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047622.0,,Not applicable,23-02-2023,16-04-2024,Lucks Hill,,,West Malling,Kent,ME19 6HN,www.moreparkprimary.co.uk/,1732843047.0,Mrs,Deborah,Seal,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,"East Malling, West Malling & Offham",Tonbridge and Malling,(England/Wales) Rural hamlet and isolated dwellings,E10000016,568701.0,157897.0,Tonbridge and Malling 014,Tonbridge and Malling 014C,,,,,Good,South-East England and South London,,200000960428.0,,Not applicable,Not applicable,,,E02006833,E01024783,18.0,
|
||||
141497,886,Kent,3740,St Richard's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,191.0,80.0,111.0,30.9,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047638.0,,Not applicable,06-10-2022,29-05-2024,Castle Avenue,,,Dover,Kent,CT16 1EZ,http://www.st-richards.kent.sch.uk,1304201118.0,Mr,Colin,Taylor,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Town & Castle,Dover,(England/Wales) Urban city and town,E10000016,631763.0,142271.0,Dover 012,Dover 012E,,,,,Good,South-East England and South London,,100062288989.0,,Not applicable,Not applicable,,,E02005052,E01033209,59.0,
|
||||
141532,886,Kent,5217,"Our Lady of Hartley Catholic Primary School, Hartley, Longfield",Academy converter,Academies,Open,Academy Converter,01-11-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,218.0,No Special Classes,19-01-2023,212.0,95.0,117.0,4.2,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047893.0,,Not applicable,,14-11-2023,Stack Lane,Hartley,,Longfield,Kent,DA3 8BL,http://www.ourladyhartley.kent.sch.uk/,1474706385.0,Mr,James,Baker,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Hartley and Hodsoll Street,Dartford,(England/Wales) Urban city and town,E10000016,560720.0,167649.0,Sevenoaks 004,Sevenoaks 004A,,,,,,South-East England and South London,,10035182405.0,,Not applicable,Not applicable,,,E02005090,E01024441,9.0,
|
||||
141534,886,Kent,2069,Dartford Primary Academy,Academy converter,Academies,Open,Academy Converter,01-11-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,654.0,No Special Classes,19-01-2023,673.0,333.0,340.0,17.2,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10047892.0,,Not applicable,13-09-2023,09-04-2024,York Road,,,Dartford,Kent,DA1 1SQ,www.dartfordprimary.org.uk/,1322224453.0,Miss,Rebecca,Roberts,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Brent,Dartford,(England/Wales) Urban major conurbation,E10000016,554979.0,173734.0,Dartford 008,Dartford 008C,,,,,Good,South-East England and South London,,200000533723.0,,Not applicable,Not applicable,,,E02005035,E01024137,115.0,
|
||||
141548,886,Kent,2055,Lansdowne Primary School,Academy sponsor led,Academies,Open,New Provision,01-11-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,397.0,201.0,196.0,33.5,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10047877.0,,Not applicable,14-12-2022,29-05-2024,Gladstone Drive,,,Sittingbourne,Kent,ME10 3BH,www.lansdowne.kent.sch.uk/,1795423752.0,Mrs,Claire,Jobe,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Murston,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,592376.0,163394.0,Swale 011,Swale 011B,,,,,Good,South-East England and South London,,200002528849.0,,Not applicable,Not applicable,,,E02005125,E01024592,133.0,
|
||||
141578,886,Kent,3019,Shorne Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-12-2014,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,209.0,114.0,95.0,4.8,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10048077.0,,Not applicable,08-03-2023,23-04-2024,Cob Drive,Shorne,,Gravesend,Kent,DA12 3DU,www.shorne.kent.sch.uk,1474822312.0,Miss,Tara,Hewett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Higham & Shorne,Gravesham,(England/Wales) Rural village,E10000016,569273.0,171239.0,Gravesham 010,Gravesham 010E,,,,,Good,South-East England and South London,,100062312571.0,,Not applicable,Not applicable,,,E02005064,E01024301,10.0,
|
||||
141579,886,Kent,5210,St Botolph's Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-12-2014,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,422.0,197.0,225.0,25.6,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10048078.0,,Not applicable,29-03-2023,10-05-2024,Dover Road,Northfleet,,Gravesend,Kent,DA11 9PL,www.st-botolphs.kent.sch.uk,1474365737.0,Mrs,Alice,Martin,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Rosherville,Gravesham,(England/Wales) Urban major conurbation,E10000016,562966.0,173733.0,Gravesham 001,Gravesham 001A,,,,,Good,South-East England and South London,,100062311179.0,,Not applicable,Not applicable,,,E02005055,E01024276,108.0,
|
||||
141580,886,Kent,5222,"St Joseph's Catholic Primary School, Northfleet",Academy converter,Academies,Open,Academy Converter,01-12-2014,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,208.0,106.0,102.0,15.4,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10047684.0,,Not applicable,11-01-2023,13-09-2023,Springhead Road,Northfleet,,Gravesend,Kent,DA11 9QZ,www.st-josephs-northfleet.kent.sch.uk/,1474533515.0,Mr,Andrew,Baldock,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Northfleet & Springhead,Gravesham,(England/Wales) Urban major conurbation,E10000016,562589.0,173691.0,Gravesham 001,Gravesham 001B,,,,,Outstanding,South-East England and South London,,100062311131.0,,Not applicable,Not applicable,,,E02005055,E01024277,32.0,
|
||||
141628,886,Kent,4633,Ursuline College,Academy converter,Academies,Open,Academy Converter,01-01-2015,,,Secondary,11.0,18,Not applicable,Not applicable,Has a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,763.0,Not applicable,19-01-2023,978.0,473.0,505.0,26.4,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10048333.0,,Not applicable,09-11-2022,03-06-2024,225 Canterbury Road,,,Westgate-on-Sea,Kent,CT8 8LX,http://www.ursuline.kent.sch.uk/,1843834431.0,Miss,Danielle,Lancefield,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Westgate-on-Sea,North Thanet,(England/Wales) Urban city and town,E10000016,631675.0,169531.0,Thanet 007,Thanet 007B,,,,,Good,South-East England and South London,,200003078464.0,,Not applicable,Not applicable,,,E02005138,E01024713,218.0,
|
||||
141629,886,Kent,5216,Stella Maris Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-01-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,211.0,106.0,105.0,19.0,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10048334.0,,Not applicable,21-06-2023,01-05-2024,Parkfield Road,,,Folkestone,Kent,CT19 5BY,www.stellamaris.kent.sch.uk/,1303252127.0,Mr,Andrew,Langley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Broadmead,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,622234.0,136791.0,Folkestone and Hythe 006,Folkestone and Hythe 006E,,,,,Good,South-East England and South London,,50040637.0,,Not applicable,Not applicable,,,E02005107,E01024515,40.0,
|
||||
141650,886,Kent,2180,South Borough Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2015,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,480.0,No Special Classes,19-01-2023,509.0,240.0,269.0,23.2,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10048683.0,,Not applicable,26-04-2023,18-04-2024,Stagshaw Close,Postley Road,,Maidstone,Kent,ME15 6TL,http://www.southboroughprimary.org.uk,1622752161.0,Mr,Mathew,Currie,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,High Street,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576421.0,154655.0,Maidstone 009,Maidstone 009C,,,,,Good,South-East England and South London,,200003683342.0,,Not applicable,Not applicable,,,E02005076,E01024374,116.0,
|
||||
141659,886,Kent,2058,Charlton Church of England Primary School,Academy sponsor led,Academies,Open,New Provision,01-03-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,None,Diocese of Canterbury,Not applicable,210.0,Not applicable,19-01-2023,199.0,88.0,111.0,35.7,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10048211.0,,Not applicable,22-11-2023,23-04-2024,Barton Road,,,Dover,Kent,CT16 2LX,www.charlton.kent.sch.uk/,1304201275.0,Mrs,Sally-Anne,Pettersen,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Buckland,Dover,(England/Wales) Urban city and town,E10000016,631452.0,142470.0,Dover 011,Dover 011B,,,,,Good,South-East England and South London,,100062289316.0,,Not applicable,Not applicable,,,E02005051,E01024194,71.0,
|
||||
141660,886,Kent,2059,Lydd Primary School,Academy sponsor led,Academies,Open,New Provision,01-03-2015,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,280.0,Not applicable,19-01-2023,274.0,150.0,124.0,50.6,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10048212.0,,Not applicable,13-09-2023,07-05-2024,20 Skinner Road,Lydd,,Romney Marsh,Kent,TN29 9HW,www.lyddprimary.org.uk,1797320362.0,Mrs,Nicki,Man,Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Walland & Denge Marsh,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,604171.0,120623.0,Folkestone and Hythe 013,Folkestone and Hythe 013C,,,,,Good,South-East England and South London,,50114553.0,,Not applicable,Not applicable,,,E02005114,E01024534,129.0,
|
||||
141754,886,Kent,2625,Godinton Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,419.0,229.0,190.0,13.6,Supported by a single-academy trust,GODINTON ACADEMY TRUST,-,,Not applicable,,10048785.0,,Not applicable,07-02-2024,03-06-2024,Lockholt Close,,,Ashford,Kent,TN23 3JR,http://www.godinton.kent.sch.uk,1233621616.0,,Jillian,Talbot,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Godinton,Ashford,(England/Wales) Urban city and town,E10000016,599006.0,143191.0,Ashford 016,Ashford 016D,,,,,Good,South-East England and South London,,100062558565.0,,Not applicable,Not applicable,,,E02007047,E01023993,57.0,
|
||||
141766,886,Kent,2596,Chilton Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,418.0,211.0,207.0,18.9,Supported by a multi-academy trust,VIKING ACADEMY TRUST,-,,Not applicable,,10048967.0,,Not applicable,10-01-2019,17-09-2023,Chilton Lane,,,Ramsgate,Kent,CT11 0LQ,http://www.chiltonprimary.co.uk/,1843597695.0,Mr,Alex,McAuley,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Cliffsend and Pegwell,South Thanet,(England/Wales) Urban city and town,E10000016,636338.0,164593.0,Thanet 017,Thanet 017A,,,,,Outstanding,South-East England and South London,,100062281920.0,,Not applicable,Not applicable,,,E02005148,E01024650,79.0,
|
||||
141871,886,Kent,2060,Beaver Green Primary School,Academy sponsor led,Academies,Open,New Provision,01-04-2015,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,480.0,248.0,232.0,44.6,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10049033.0,,Not applicable,14-03-2023,15-05-2024,Cuckoo Lane,,,Ashford,Kent,TN23 5DA,www.beaver-green.kent.sch.uk,1233621989.0,Ms,Tina,Oakley,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Beaver,Ashford,(England/Wales) Urban city and town,E10000016,599449.0,141135.0,Ashford 007,Ashford 007C,,,,,Good,South-East England and South London,,200004396685.0,,Not applicable,Not applicable,,,E02005002,E01023977,193.0,
|
||||
141881,886,Kent,2061,Finberry Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,2.0,11,,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,350.0,Has Special Classes,19-01-2023,334.0,175.0,159.0,22.3,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10053381.0,,Not applicable,24-01-2024,20-05-2024,Avocet Way,Finberry,,Ashford,Kent,TN25 7GS,http://www.finberryprimaryschool.org.uk/,1233622686.0,Headteacher,Siobhan,Risley,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,"SEMH - Social, Emotional and Mental Health",,,,,,,,,,,,,Resourced provision,5.0,8.0,,,South East,Ashford,"Mersham, Sevington South with Finberry",Ashford,(England/Wales) Urban city and town,E10000016,602230.0,139442.0,Ashford 010,Ashford 010E,,,,,Good,South-East England and South London,United Kingdom,10012868578.0,,Not applicable,Not applicable,,,E02005005,E01034987,71.0,
|
||||
142052,886,Kent,2063,Istead Rise Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,243.0,125.0,118.0,16.5,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10049666.0,,Not applicable,28-02-2024,22-05-2024,Downs Road,Northfleet,,Gravesend,Kent,DA13 9HG,,1474833177.0,Mr,Steven,Payne,Executive Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,"Istead Rise, Cobham & Luddesdown",Gravesham,(England/Wales) Rural town and fringe,E10000016,563354.0,169714.0,Gravesham 012,Gravesham 012A,,,,,Good,South-East England and South London,,10012011594.0,,Not applicable,Not applicable,,,E02005066,E01024268,40.0,
|
||||
142117,886,Kent,2064,Ramsgate Arts Primary School,Free schools,Free Schools,Open,New Provision,01-09-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,None,,,420.0,Not applicable,19-01-2023,355.0,161.0,194.0,29.9,Supported by a multi-academy trust,VIKING ACADEMY TRUST,-,,Not applicable,,10053814.0,,Not applicable,28-09-2023,13-12-2023,140-144 Newington Road,,,Ramsgate,Kent,CT12 6PP,http://www.ramsgateartsprimaryschool.co.uk/,1843582847.0,Mr,Nicholas,Budge,Executive Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Northwood,South Thanet,(England/Wales) Urban city and town,E10000016,636761.0,166126.0,Thanet 013,Thanet 013D,,,,,Good,South-East England and South London,,100061133646.0,,Not applicable,Not applicable,,,E02005144,E01024685,106.0,
|
||||
142156,886,Kent,3708,"St John's Catholic Primary School, Gravesend",Academy converter,Academies,Open,Academy Converter,01-07-2015,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,840.0,No Special Classes,19-01-2023,886.0,441.0,445.0,16.5,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10053098.0,,Not applicable,18-10-2023,23-04-2024,Rochester Road,,,Gravesend,Kent,DA12 2SY,http://www.stjohnsprimary.kent.sch.uk,1474534546.0,Co Headteacher,Caroline Barron,Paula Cooneyhan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Gravesham,Denton,Gravesham,(England/Wales) Urban major conurbation,E10000016,565990.0,173541.0,Gravesham 003,Gravesham 003C,,,,,Good,South-East England and South London,,100062311904.0,,Not applicable,Not applicable,,,E02005057,E01024293,142.0,
|
||||
142162,886,Kent,3715,"St Mary's Catholic Primary School, Whitstable",Academy converter,Academies,Open,Academy Converter,01-07-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,420.0,No Special Classes,19-01-2023,374.0,186.0,188.0,12.3,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10053095.0,,Not applicable,08-11-2023,21-05-2024,Northwood Road,,,Whitstable,Kent,CT5 2EY,http://www.st-marys-whitstable.kent.sch.uk,1227272692.0,Mrs,Michele,Blunt,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Canterbury,Tankerton,Canterbury,(England/Wales) Urban city and town,E10000016,611618.0,166842.0,Canterbury 007,Canterbury 007E,,,,,Good,South-East England and South London,,100062300983.0,,Not applicable,Not applicable,,,E02005016,E01024116,46.0,
|
||||
142188,886,Kent,2073,Langley Park Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2016,,,Primary,3.0,11,,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,451.0,232.0,219.0,15.5,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10053393.0,,Not applicable,19-06-2019,15-04-2024,Edmett Way,,,Maidstone,Kent,ME17 3FX,www.langleyparkprimaryacademy.org.uk,1622250880.0,Miss,Sally,Brading,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,10.0,10.0,,,South East,Maidstone,Park Wood,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,579116.0,151664.0,Maidstone 015,Maidstone 015H,,,,,Good,South-East England and South London,,10093304704.0,,Not applicable,Not applicable,,,E02005082,E01034999,70.0,
|
||||
142346,886,Kent,2110,Culverstone Green Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,212.0,121.0,91.0,10.8,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10054199.0,,Not applicable,18-10-2018,18-04-2024,Wrotham Road,Meopham,,Gravesend,Kent,DA13 0RF,http://www.cgps.kent.sch.uk,1732822568.0,,James,Bernard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Meopham South & Vigo,Gravesham,(England/Wales) Rural hamlet and isolated dwellings,E10000016,563542.0,163114.0,Gravesham 013,Gravesham 013A,,,,,Good,South-East England and South London,,100062312941.0,,Not applicable,Not applicable,,,E02005067,E01024273,23.0,
|
||||
142347,886,Kent,2650,Dymchurch Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,311.0,No Special Classes,19-01-2023,159.0,73.0,86.0,36.5,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10054200.0,,Not applicable,27-04-2022,15-05-2024,New Hall Close,Dymchurch,,Romney Marsh,Kent,TN29 0LE,http://www.dymchurch.kent.sch.uk,1303872377.0,Mr,Iain,Rudgyard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Romney Marsh,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,610150.0,129670.0,Folkestone and Hythe 011,Folkestone and Hythe 011A,,,,,Requires improvement,South-East England and South London,,50009728.0,,Not applicable,Not applicable,,,E02005112,E01024486,58.0,
|
||||
142363,886,Kent,2462,Riverview Infant School,Academy converter,Academies,Open,Academy Converter,01-10-2015,,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,330.0,167.0,163.0,19.1,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10054175.0,,Not applicable,08-12-2021,09-04-2024,Cimba Wood,,,Gravesend,Kent,DA12 4SD,www.riverview-infant.com,1474566484.0,Mrs,Kerrie,Ward,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Riverview Park,Gravesham,(England/Wales) Urban major conurbation,E10000016,566337.0,171561.0,Gravesham 008,Gravesham 008B,,,,,Good,South-East England and South London,,10012012176.0,,Not applicable,Not applicable,,,E02005062,E01024298,63.0,
|
||||
142372,886,Kent,5228,St Georges CofE (Aided) Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,393.0,189.0,204.0,20.6,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10054170.0,,Not applicable,07-02-2024,20-05-2024,Chequers Road,,,Sheerness,Kent,ME12 3QU,http://www.st-georges-sheppey.kent.sch.uk/,1795877667.0,Mr,Howard,Fisher,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheppey Central,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,596107.0,172691.0,Swale 005,Swale 005D,,,,,Good,South-East England and South London,,200002530151.0,,Not applicable,Not applicable,,,E02005119,E01024620,81.0,
|
||||
142429,886,Kent,3140,Kingsnorth Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2015,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,420.0,202.0,218.0,11.2,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10054668.0,,Not applicable,09-10-2018,22-04-2024,Church Hill,Kingsnorth,,Ashford,Kent,TN23 3EF,www.kingsnorth.kent.sch.uk,1233622673.0,Mr,Iain,Witts,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Kingsnorth Village & Bridgefield,Ashford,(England/Wales) Urban city and town,E10000016,600571.0,139333.0,Ashford 010,Ashford 010G,,,,,Good,South-East England and South London,,100062558563.0,,Not applicable,Not applicable,,,E02005005,E01034989,47.0,
|
||||
142517,886,Kent,2076,Cherry Orchard Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2017,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,,,,Not applicable,450.0,Not applicable,19-01-2023,446.0,223.0,223.0,13.6,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10064709.0,,,10-11-2021,11-01-2024,Cherry Orchard,Cherry Orchard Road,Ebbsfleet Valley,Ebbsfleet,Kent,DA10 1AD,www.cherryorchardprimaryacademy.org.uk,1322242011.0,Mrs,Julie,Forsythe,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,14.0,15.0,,,South East,Dartford,Ebbsfleet,Dartford,(England/Wales) Urban major conurbation,E10000016,560297.0,173251.0,Dartford 002,Dartford 002H,,,,,Outstanding,South-East England and South London,United Kingdom,10023446223.0,,Not applicable,Not applicable,,,E02005029,E01035280,60.0,
|
||||
142591,886,Kent,3915,Manor Community Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2016,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,674.0,No Special Classes,19-01-2023,670.0,334.0,336.0,31.2,Supported by a multi-academy trust,CYGNUS ACADEMIES TRUST,Linked to a sponsor,Cygnus Academies Trust,Not applicable,,10055399.0,,Not applicable,31-10-2018,29-05-2024,Keary Road,,,Swanscombe,Kent,DA10 0BU,www.manor.kent.sch.uk/,1322383314.0,Mrs,Natalie,Hill,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Swanscombe,Dartford,(England/Wales) Urban major conurbation,E10000016,560606.0,173953.0,Dartford 004,Dartford 004B,,,,,Good,South-East England and South London,,10023438316.0,,Not applicable,Not applicable,,,E02005031,E01024176,199.0,
|
||||
142613,886,Kent,2077,Westgate Primary School,Academy sponsor led,Academies,Open,New Provision,01-04-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,210.0,101.0,109.0,21.0,Supported by a multi-academy trust,CYGNUS ACADEMIES TRUST,Linked to a sponsor,Cygnus Academies Trust,Not applicable,,10055488.0,,,06-03-2019,02-06-2024,Summerhill Road,,,Dartford,Kent,DA1 2LP,https://www.westgateprimary.org/,1322223382.0,Mrs,Laura,Crosley,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Town,Dartford,(England/Wales) Urban major conurbation,E10000016,553773.0,173889.0,Dartford 003,Dartford 003C,,,,,Good,South-East England and South London,,100062616136.0,,Not applicable,Not applicable,,,E02005030,E01024182,44.0,
|
||||
142689,886,Kent,3306,Brenchley and Matfield Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-05-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,196.0,93.0,103.0,9.7,Supported by a multi-academy trust,THE TENAX SCHOOLS TRUST,Linked to a sponsor,The Tenax Schools Trust,Not applicable,,10056161.0,,Not applicable,15-11-2018,13-09-2023,Market Heath,Brenchley,,Tonbridge,Kent,TN12 7NY,www.bmprimary.org.uk,1892722929.0,Miss,Jane,Mallon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Brenchley and Horsmonden,Tunbridge Wells,(England/Wales) Rural village,E10000016,567404.0,141906.0,Tunbridge Wells 004,Tunbridge Wells 004A,,,,,Good,South-East England and South London,,100062546036.0,,Not applicable,Not applicable,,,E02005165,E01024793,19.0,
|
||||
142814,886,Kent,2078,St Nicholas Church of England Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-06-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,,Diocese of Canterbury,Not applicable,378.0,Not applicable,19-01-2023,396.0,212.0,184.0,34.8,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10056538.0,,,23-05-2019,08-05-2024,Fairfield Road,,,New Romney,Kent,TN28 8BP,www.st-nicholas-newromney.kent.sch.uk,1797361906.0,Mr,Christopher,Dale,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,2.0,14.0,,,South East,Folkestone and Hythe,New Romney,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,606585.0,125146.0,Folkestone and Hythe 012,Folkestone and Hythe 012C,,,,,Good,South-East England and South London,,50002925.0,,Not applicable,Not applicable,,,E02005113,E01024539,138.0,
|
||||
142834,886,Kent,2079,Woodlands Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2016,,,Primary,4.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,,Not applicable,630.0,Not applicable,19-01-2023,643.0,355.0,288.0,20.7,Not applicable,,Not applicable,,Not under a federation,,10058897.0,,,12-06-2019,11-04-2024,Higham School Lane,Hunt Road,,Tonbridge,Kent,TN10 4BB,www.woodlands.kent.sch.uk,1732355577.0,Mrs,Vicki,Lonie,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tonbridge and Malling,Higham,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,560317.0,148629.0,Tonbridge and Malling 011,Tonbridge and Malling 011C,,,,,Good,South-East England and South London,,10013922163.0,,Not applicable,Not applicable,,,E02005159,E01024749,133.0,
|
||||
142924,886,Kent,2080,Barming Primary School,Academy sponsor led,Academies,Open,New Provision,01-07-2016,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,467.0,244.0,223.0,25.1,Supported by a multi-academy trust,ORCHARD ACADEMY TRUST,Linked to a sponsor,Allington Primary School Academy Trust (Orchard Academy Trust),Not applicable,,10056809.0,,,09-05-2019,30-04-2024,Belmont Close,Barming,,Maidstone,Kent,ME16 9DY,https://www.barming.kent.sch.uk/,1622726472.0,Mr,Christopher,Laker,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Barming and Teston,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,572627.0,154790.0,Maidstone 014,Maidstone 014A,,,,,Good,South-East England and South London,,200003720125.0,,Not applicable,Not applicable,,,E02005081,E01024325,107.0,
|
||||
143073,886,Kent,2309,Priory Fields School,Academy converter,Academies,Open,Academy Converter,01-08-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,365.0,178.0,187.0,57.3,Supported by a multi-academy trust,WHINLESS DOWN ACADEMY TRUST,-,,Not applicable,,10057450.0,,Not applicable,20-11-2018,08-05-2024,Astor Avenue,,,Dover,Kent,CT17 0FS,http://www.prioryfields.kent.sch.uk,1304211543.0,Miss,Casey,Hall,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Tower Hamlets,Dover,(England/Wales) Urban city and town,E10000016,630848.0,141634.0,Dover 011,Dover 011H,,,,,Good,South-East England and South London,,10034874352.0,,Not applicable,Not applicable,,,E02005051,E01024248,209.0,
|
||||
143075,886,Kent,2313,St Martin's School,Academy converter,Academies,Open,Academy Converter,01-08-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,194.0,102.0,92.0,17.0,Supported by a multi-academy trust,WHINLESS DOWN ACADEMY TRUST,-,,Not applicable,,10057448.0,,Not applicable,08-02-2024,20-05-2024,Markland Road,,,Dover,Kent,CT17 9LY,www.stmartins.kent.sch.uk/,1304206620.0,Mrs,Helen,Thompson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Maxton & Elms Vale,Dover,(England/Wales) Urban city and town,E10000016,630181.0,141199.0,Dover 014,Dover 014C,,,,,Good,South-East England and South London,,100062290538.0,,Not applicable,Not applicable,,,E02005054,E01024213,33.0,
|
||||
143218,886,Kent,3914,Oakfield Primary Academy,Academy converter,Academies,Open,Academy Converter,01-09-2016,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Has Special Classes,19-01-2023,719.0,383.0,336.0,27.5,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10057852.0,,Not applicable,06-10-2021,03-06-2024,Oakfield Lane,,,Dartford,Kent,DA1 2SW,http://www.oakfield-dartford.co.uk,1322220831.0,Mrs,Rajinder,Kaur-Gill,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,14.0,12.0,,,South East,Dartford,"Wilmington, Sutton-at-Hone & Hawley",Dartford,(England/Wales) Urban major conurbation,E10000016,553836.0,172851.0,Dartford 009,Dartford 009D,,,,,Good,South-East England and South London,,200000534405.0,,Not applicable,Not applicable,,,E02005036,E01024168,192.0,
|
||||
143219,886,Kent,2657,Temple Hill Primary Academy,Academy converter,Academies,Open,Academy Converter,01-09-2016,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,914.0,Has Special Classes,19-01-2023,891.0,458.0,433.0,38.5,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10057853.0,,Not applicable,26-06-2019,03-06-2024,St Edmund's Road,Temple Hill,,Dartford,Kent,DA1 5ND,http://www.temple-hill.kent.sch.uk,1322224600.0,Mr,Leon,Dawson,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,5.0,8.0,3.0,12.0,South East,Dartford,Temple Hill,Dartford,(England/Wales) Urban major conurbation,E10000016,555040.0,175006.0,Dartford 001,Dartford 001I,,,,,Good,South-East England and South London,,200000530828.0,,Not applicable,Not applicable,,,E02005028,E01035274,329.0,
|
||||
143220,886,Kent,2523,Upton Junior School,Academy converter,Academies,Open,Academy Converter,01-09-2016,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,512.0,No Special Classes,19-01-2023,513.0,263.0,250.0,22.2,Supported by a multi-academy trust,VIKING ACADEMY TRUST,-,,Not applicable,,10057856.0,,Not applicable,,05-06-2024,"Upton Junior School, Edge End Road",,,Broadstairs,Kent,CT10 2AH,http://www.uptonjunior.com/,1843861393.0,Miss,Darci,Arthur,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Viking,South Thanet,(England/Wales) Urban city and town,E10000016,638779.0,167831.0,Thanet 010,Thanet 010C,,,,,,South-East England and South London,,100062627193.0,,Not applicable,Not applicable,,,E02005141,E01024706,114.0,
|
||||
143517,886,Kent,2081,Brenzett Church of England Primary School,Academy sponsor led,Academies,Open,New Provision,01-10-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,,Diocese of Canterbury,Not applicable,140.0,Not applicable,19-01-2023,83.0,40.0,43.0,38.6,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10061368.0,,,03-07-2019,18-04-2024,Straight Lane,,Brenzett,Romney Marsh,Kent,TN29 9UA,www.brenzett.kent.sch.uk,1797344335.0,Mrs,Rowan,Wright,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Walland & Denge Marsh,Folkestone and Hythe,(England/Wales) Rural hamlet and isolated dwellings,E10000016,600427.0,127014.0,Folkestone and Hythe 011,Folkestone and Hythe 011D,,,,,Good,South-East England and South London,,50100964.0,,Not applicable,Not applicable,,,E02005112,E01024548,32.0,
|
||||
143605,886,Kent,5220,Halfway Houses Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,591.0,302.0,289.0,28.4,Supported by a multi-academy trust,THE ISLAND LEARNING TRUST,Linked to a sponsor,The Island Learning Trust,Not applicable,,10061768.0,,Not applicable,13-11-2018,18-04-2024,Danley Road,Minster-on-Sea,,Sheerness,Kent,ME12 3AP,www.halfwayhouses.kent.sch.uk,1795662875.0,Mrs,Lindsay,Fordyce,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Queenborough and Halfway,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,593286.0,173082.0,Swale 004,Swale 004B,,,,,Good,South-East England and South London,,10093084941.0,,Not applicable,Not applicable,,,E02005118,E01024598,168.0,
|
||||
143606,886,Kent,2235,Minster in Sheppey Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,600.0,No Special Classes,19-01-2023,579.0,285.0,294.0,25.9,Supported by a multi-academy trust,THE ISLAND LEARNING TRUST,Linked to a sponsor,The Island Learning Trust,Not applicable,,10061767.0,,Not applicable,10-03-2022,25-04-2024,Brecon Chase,Minster,,Sheerness,Kent,ME12 2HX,http://www.minster-sheppey.kent.sch.uk,1795872138.0,Mrs,Michelle Jeffery co-head,Lynne Lewis co-head,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Minster Cliffs,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,595361.0,173030.0,Swale 003,Swale 003D,,,,,Good,South-East England and South London,,200002530145.0,,Not applicable,Not applicable,,,E02005117,E01024589,150.0,
|
||||
143787,886,Kent,2290,Tenterden Infant School,Academy converter,Academies,Open,Academy Converter,01-12-2016,,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,211.0,No Special Classes,19-01-2023,151.0,78.0,73.0,21.2,Supported by a multi-academy trust,TENTERDEN SCHOOLS TRUST,-,,Not applicable,,10061964.0,,Not applicable,05-02-2019,13-05-2024,Recreation Ground Road,,,Tenterden,Kent,TN30 6RA,www.tenterdenprimaryfederation.kent.sch.uk/,1580762086.0,Mrs,Tina,McIntosh,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Tenterden South,Ashford,(England/Wales) Rural town and fringe,E10000016,588615.0,133196.0,Ashford 013,Ashford 013F,,,,,Good,South-East England and South London,,100062567470.0,,Not applicable,Not applicable,,,E02005008,E01024025,32.0,
|
||||
143788,886,Kent,3143,St Michael's Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-12-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,160.0,93.0,67.0,25.6,Supported by a multi-academy trust,TENTERDEN SCHOOLS TRUST,-,,Not applicable,,10061960.0,,Not applicable,12-12-2018,13-05-2024,Ashford Road,St Michael's,,Tenterden,Kent,TN30 6PU,www.stmcep.school,1580763210.0,Mrs,"Sara Williamson,",Mrs Jo Paskhin,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Tenterden St Michael's,Ashford,(England/Wales) Rural town and fringe,E10000016,588469.0,135486.0,Ashford 013,Ashford 013C,,,,,Good,South-East England and South London,,100062567391.0,,Not applicable,Not applicable,,,E02005008,E01024011,41.0,
|
||||
143789,886,Kent,3144,Tenterden Church of England Junior School,Academy converter,Academies,Open,Academy Converter,01-12-2016,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Diocese of Canterbury,Not applicable,240.0,No Special Classes,19-01-2023,194.0,91.0,103.0,28.9,Supported by a multi-academy trust,TENTERDEN SCHOOLS TRUST,-,,Not applicable,,10061969.0,,Not applicable,11-12-2018,13-05-2024,Recreation Ground Road,,,Tenterden,Kent,TN30 6RA,www.tenterdenprimaryfederation.kent.sch.uk/,1580763717.0,Mrs,Tina,McIntosh,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Tenterden South,Ashford,(England/Wales) Rural town and fringe,E10000016,588577.0,133233.0,Ashford 013,Ashford 013F,,,,,Good,South-East England and South London,,200004397348.0,,Not applicable,Not applicable,,,E02005008,E01024025,56.0,
|
||||
143954,886,Kent,4015,The Lenham School,Academy sponsor led,Academies,Open,New Provision,01-03-2017,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Does not apply,,Not applicable,Non-selective,1012.0,Not applicable,19-01-2023,779.0,392.0,387.0,23.0,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10062504.0,,,06-11-2019,07-05-2024,Ham Lane,Lenham,,Maidstone,Kent,ME17 2LL,www.thelenham.viat.org.uk,1622858267.0,Mr,Robbie,Ferguson,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Harrietsham and Lenham,Faversham and Mid Kent,(England/Wales) Rural town and fringe,E10000016,589367.0,152380.0,Maidstone 011,Maidstone 011B,,,,,Good,South-East England and South London,,200003719333.0,,Not applicable,Not applicable,,,E02005078,E01024362,172.0,
|
||||
143987,886,Kent,3324,"Leybourne, St Peter and St Paul Church of England Primary Academy",Academy converter,Academies,Open,Academy Converter,01-03-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,216.0,No Special Classes,19-01-2023,216.0,103.0,113.0,6.5,Supported by a multi-academy trust,THE TENAX SCHOOLS TRUST,Linked to a sponsor,The Tenax Schools Trust,Not applicable,,10062539.0,,Not applicable,03-11-2021,09-05-2024,Rectory Lane North,Leybourne,,West Malling,Kent,ME19 5HD,http://www.leybourne.school,1732842008.0,,Tina,Holditch,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tonbridge and Malling,"Birling, Leybourne & Ryarsh",Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,569081.0,158746.0,Tonbridge and Malling 003,Tonbridge and Malling 003G,,,,,Good,South-East England and South London,,10002908134.0,,Not applicable,Not applicable,,,E02005151,E01024782,14.0,
|
||||
144005,886,Kent,2658,Westcourt Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2017,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,240.0,No Special Classes,19-01-2023,358.0,177.0,181.0,43.2,Supported by a multi-academy trust,THE PRIMARY FIRST TRUST,Linked to a sponsor,The Primary First Trust,Not applicable,,10062555.0,,Not applicable,27-11-2019,07-03-2024,Silver Road,,,Gravesend,Kent,DA12 4JG,www.westcourt.kent.sch.uk/,1474566411.0,Miss,Mags,Sexton,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Westcourt,Gravesham,(England/Wales) Urban major conurbation,E10000016,566426.0,172839.0,Gravesham 007,Gravesham 007C,,,,,Good,South-East England and South London,,100062312611.0,,Not applicable,Not applicable,,,E02005061,E01024311,145.0,
|
||||
144015,886,Kent,4016,The Charles Dickens School,Academy sponsor led,Academies,Open,New Provision,01-03-2017,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,,Not applicable,Not applicable,1104.0,Not applicable,19-01-2023,1111.0,540.0,571.0,36.0,Supported by a multi-academy trust,BARTON COURT ACADEMY TRUST,Linked to a sponsor,Barton Court Academy Trust,Not applicable,,10062895.0,,,29-03-2023,23-04-2024,Broadstairs Road,,,Broadstairs,Kent,CT10 2RL,www.cds.kent.sch.uk,1843862988.0,Mr,Warren,Smith,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,VI - Visual Impairment,,,,,,,,,,,,,Resourced provision,3.0,5.0,,,South East,Thanet,St Peters,South Thanet,(England/Wales) Urban city and town,E10000016,638334.0,167981.0,Thanet 011,Thanet 011D,,,,,Good,South-East England and South London,,200003079311.0,,Not applicable,Not applicable,,,E02005142,E01024690,400.0,
|
||||
144098,886,Kent,3021,Stone St Mary's CofE Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,630.0,No Special Classes,19-01-2023,645.0,327.0,318.0,18.8,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10063054.0,,Not applicable,05-02-2020,14-09-2023,Hayes Road,Horns Cross,,Greenhithe,Kent,DA9 9EF,http://www.stone.kent.sch.uk,1322382292.0,Mrs,Jane,Rolfe,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Stone Castle,Dartford,(England/Wales) Urban major conurbation,E10000016,557252.0,173910.0,Dartford 006,Dartford 006C,,,,,Good,South-East England and South London,,200000536262.0,,Not applicable,Not applicable,,,E02005033,E01024171,121.0,
|
||||
144099,886,Kent,5215,Horton Kirby Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2017,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,240.0,No Special Classes,19-01-2023,236.0,114.0,122.0,20.8,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10063053.0,,Not applicable,17-05-2023,23-04-2024,Horton Road,Horton Kirby,,Dartford,Kent,DA4 9BN,www.hortonkirby.kent.sch.uk,1322863278.0,Mr,Glenn,Pollard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Farningham, Horton Kirby and South Darenth",Sevenoaks,(England/Wales) Rural town and fringe,E10000016,556340.0,168608.0,Sevenoaks 005,Sevenoaks 005B,,,,,Good,South-East England and South London,,100062317327.0,,Not applicable,Not applicable,,,E02005091,E01024432,49.0,
|
||||
144100,886,Kent,5411,Dartford Grammar School for Girls,Academy converter,Academies,Open,Academy Converter,01-06-2017,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1119.0,No Special Classes,19-01-2023,1248.0,41.0,1207.0,18.0,Supported by a multi-academy trust,THE ARETÉ TRUST,-,,Not applicable,,10063935.0,,Not applicable,20-10-2021,04-06-2024,Shepherds Lane,,,Dartford,Kent,DA1 2NT,http://www.dartfordgrammargirls.org.uk,1322223123.0,Mrs,Sharon,Pritchard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,West Hill,Dartford,(England/Wales) Urban major conurbation,E10000016,553265.0,173905.0,Dartford 003,Dartford 003F,,,,,Outstanding,South-East England and South London,,200000532843.0,,Not applicable,Not applicable,,,E02005030,E01024185,160.0,
|
||||
144354,886,Kent,4091,The Whitstable School,Academy converter,Academies,Open,Academy Converter,01-09-2018,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Does not apply,,Not applicable,Non-selective,1220.0,Not applicable,19-01-2023,1080.0,514.0,566.0,19.9,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10068468.0,,,13-03-2024,22-05-2024,Bellevue Road,,,Whitstable,Kent,CT5 1PX,www.thewhitstableschool.org.uk,1227931300.0,Mr,Alex,Holmes,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Gorrell,Canterbury,(England/Wales) Urban city and town,E10000016,611643.0,165739.0,Canterbury 009,Canterbury 009A,,,,,Good,South-East England and South London,,100062300161.0,,Not applicable,Not applicable,,,E02005018,E01024062,197.0,
|
||||
144420,886,Kent,3716,St Teresa's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-05-2017,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,218.0,No Special Classes,19-01-2023,213.0,100.0,113.0,14.6,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10063828.0,,Not applicable,16-01-2020,20-05-2024,Quantock Drive,,,Ashford,Kent,TN24 8QN,www.st-teresas.kent.sch.uk/,1233622797.0,Mrs,H,Bennett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Furley,Ashford,(England/Wales) Urban city and town,E10000016,600520.0,143538.0,Ashford 015,Ashford 015D,,,,,Good,South-East England and South London,,100062560866.0,,Not applicable,Not applicable,,,E02007046,E01024022,31.0,
|
||||
144531,886,Kent,2172,Valley Invicta Primary School At East Borough,Academy converter,Academies,Open,Academy Converter,01-11-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,455.0,No Special Classes,19-01-2023,471.0,250.0,221.0,20.4,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10065310.0,,Not applicable,14-10-2021,04-06-2024,Vinters Road,,,Maidstone,Kent,ME14 5DX,www.eastborough.viat.org.uk,1622754633.0,Mrs,C,Bacon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,9.0,10.0,,,South East,Maidstone,East,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576832.0,155960.0,Maidstone 005,Maidstone 005E,,,,,Good,South-East England and South London,,200003689415.0,,Not applicable,Not applicable,,,E02005072,E01024351,96.0,
|
||||
144615,886,Kent,2085,Royal Rise Primary School,Academy sponsor led,Academies,Open,New Provision,01-07-2017,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,203.0,94.0,109.0,34.5,Supported by a multi-academy trust,CYGNUS ACADEMIES TRUST,Linked to a sponsor,Cygnus Academies Trust,Not applicable,,10064021.0,,,15-09-2021,29-04-2024,Royal Rise,,,Tonbridge,Kent,TN9 2DQ,,1732354143.0,Mrs,Sarah,Griggs,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Vauxhall,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559446.0,145748.0,Tonbridge and Malling 012,Tonbridge and Malling 012H,,,,,Good,South-East England and South London,,200000966929.0,,Not applicable,Not applicable,,,E02005160,E01035009,70.0,
|
||||
144634,886,Kent,2086,Bishop Chavasse Primary School,Free schools,Free Schools,Open,,01-09-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Church of England,,Non-selective,420.0,Not applicable,19-01-2023,311.0,162.0,149.0,22.8,Supported by a multi-academy trust,THE TENAX SCHOOLS TRUST,Linked to a sponsor,The Tenax Schools Trust,Not applicable,,10064781.0,,,06-07-2022,03-04-2024,2a Baker Lane,,,Tonbridge,Kent,TN11 0FB,www.bishopchavasseschool.org.uk,1732676040.0,Mrs,Becks,Hood,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Tonbridge and Malling,Vauxhall,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,560184.0,145430.0,Tonbridge and Malling 012,Tonbridge and Malling 012E,,,,,Good,South-East England and South London,United Kingdom,10092972630.0,,Not applicable,Not applicable,,,E02005160,E01024767,71.0,
|
||||
144668,886,Kent,2676,West Hill Primary Academy,Academy converter,Academies,Open,Academy Converter,01-09-2017,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,507.0,No Special Classes,19-01-2023,534.0,271.0,263.0,12.5,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10064807.0,,Not applicable,01-10-2021,03-06-2024,Dartford Road,,,Dartford,Kent,DA1 3DZ,www.west-hill.kent.sch.uk/,1322226019.0,Ms,Katy,Ward,Executive Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,West Hill,Dartford,(England/Wales) Urban major conurbation,E10000016,553136.0,174316.0,Dartford 003,Dartford 003D,,,,,Good,South-East England and South London,,100062309268.0,,Not applicable,Not applicable,,,E02005030,E01024183,67.0,
|
||||
144716,886,Kent,4019,School of Science and Technology Maidstone,Free schools,Free Schools,Open,New Provision,01-09-2020,,,Secondary,11.0,19,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,,,1200.0,Not applicable,19-01-2023,576.0,385.0,191.0,9.9,Supported by a multi-academy trust,VALLEY INVICTA ACADEMIES TRUST,Linked to a sponsor,Valley Invicta Academies Trust,Not applicable,,10086463.0,,,25-01-2023,20-12-2023,New Cut Road,,,Maidstone,Kent,ME14 5GQ,,1622938444.0,Mr,Ryan,Royston (Head of School),,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Boxley,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,577636.0,155754.0,Maidstone 005,Maidstone 005B,,,,,Outstanding,South-East England and South London,United Kingdom,10094441687.0,,Not applicable,Not applicable,,,E02005072,E01024336,57.0,
|
||||
144835,886,Kent,3343,Charing Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-07-2017,,,Primary,2.0,11,Not applicable,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,140.0,No Special Classes,19-01-2023,165.0,87.0,78.0,35.1,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10064466.0,,Not applicable,20-10-2021,19-02-2024,School Road,Charing,,Ashford,Kent,TN27 0JN,www.charingschool.org.uk/,1233712277.0,Mr,Thomas,Bird,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Charing,Ashford,(England/Wales) Rural town and fringe,E10000016,595188.0,149495.0,Ashford 002,Ashford 002B,,,,,Good,South-East England and South London,,100062563576.0,,Not applicable,Not applicable,,,E02004997,E01023986,52.0,
|
||||
144836,886,Kent,3754,St Augustine's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-07-2017,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,285.0,No Special Classes,19-01-2023,287.0,154.0,133.0,17.1,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10064464.0,,Not applicable,15-09-2021,03-06-2024,Wilman Road,,,Tunbridge Wells,Kent,TN4 9AL,https://www.st-augustines.kent.sch.uk/,1892529796.0,Mr,Jon,Crozier,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,St John's,Tunbridge Wells,(England/Wales) Urban city and town,E10000016,558501.0,141283.0,Tunbridge Wells 002,Tunbridge Wells 002A,,,,,Good,South-East England and South London,,100062586226.0,,Not applicable,Not applicable,,,E02005163,E01024837,49.0,
|
||||
144867,886,Kent,3329,Borden Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-08-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,126.0,No Special Classes,19-01-2023,123.0,64.0,59.0,14.6,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10064657.0,,Not applicable,29-06-2022,28-05-2024,School Lane,Borden,,Sittingbourne,Kent,ME9 8JS,www.borden.kent.sch.uk,1795472593.0,Miss,Georgina,Ingram,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Borden and Grove Park,Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,587791.0,163390.0,Swale 009,Swale 009A,,,,,Requires improvement,South-East England and South London,,100062626970.0,,Not applicable,Not applicable,,,E02005123,E01024554,18.0,
|
||||
144868,886,Kent,3330,Bredgar Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-08-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,106.0,No Special Classes,19-01-2023,108.0,52.0,56.0,10.2,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10064679.0,,Not applicable,12-01-2022,28-05-2024,Bexon Lane,Bredgar,,Sittingbourne,Kent,ME9 8HB,www.bredgar.kent.sch.uk/,1622884359.0,Miss,Joanna,Heath,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,West Downs,Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,588053.0,160269.0,Swale 013,Swale 013C,,,,,Good,South-East England and South London,,100062626605.0,,Not applicable,Not applicable,,,E02005127,E01024628,11.0,
|
||||
144869,886,Kent,2463,Minterne Junior School,Academy converter,Academies,Open,Academy Converter,01-08-2017,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,Has Special Classes,19-01-2023,374.0,182.0,192.0,16.0,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10064678.0,,Not applicable,06-10-2021,23-04-2024,Minterne Avenue,,,Sittingbourne,Kent,ME10 1SB,http://www.minterne.org,1795472323.0,Ms,Kirsty,Warner,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,,,,,South East,Swale,Homewood,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,589818.0,162822.0,Swale 012,Swale 012D,,,,,Good,South-East England and South London,,200002527646.0,,Not applicable,Not applicable,,,E02005126,E01024630,60.0,
|
||||
144870,886,Kent,2513,The Oaks Infant School,Academy converter,Academies,Open,Academy Converter,01-08-2017,,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,Has Special Classes,19-01-2023,324.0,165.0,159.0,16.8,Supported by a multi-academy trust,OUR COMMUNITY MULTI ACADEMY TRUST,-,,Not applicable,,10064677.0,,Not applicable,24-11-2021,23-04-2024,Gore Court Road,,,Sittingbourne,Kent,ME10 1GL,http://www.theoaksinfantschool.co.uk,1795423619.0,Mrs,Jenny,Wynn,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,4.0,12.0,,,South East,Swale,Woodstock,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,589925.0,162789.0,Swale 012,Swale 012D,,,,,Good,South-East England and South London,,200002532277.0,,Not applicable,Not applicable,,,E02005126,E01024630,47.0,
|
||||
144910,886,Kent,5204,Sutton-At-Hone Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,420.0,No Special Classes,19-01-2023,400.0,198.0,202.0,18.0,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10065030.0,,Not applicable,04-03-2020,26-03-2024,Church Road,Sutton-At-Hone,,Dartford,Kent,DA4 9EX,www.sutton-at-hone.kent.sch.uk/,1322862147.0,Mrs,Karen,Trowell,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,"Wilmington, Sutton-at-Hone & Hawley",Dartford,(England/Wales) Rural town and fringe,E10000016,555501.0,170379.0,Dartford 012,Dartford 012D,,,,,Good,South-East England and South London,,200000535148.0,,Not applicable,Not applicable,,,E02005039,E01024174,72.0,
|
||||
145012,886,Kent,2087,Morehall Primary School and Nursery,Academy sponsor led,Academies,Open,Fresh Start,01-01-2017,,,Primary,2.0,11,,Has Nursery Classes,Not applicable,Mixed,None,None,,,210.0,Not applicable,19-01-2023,228.0,113.0,115.0,26.0,Supported by a multi-academy trust,TURNER SCHOOLS,Linked to a sponsor,Turner Schools,Not applicable,,10064517.0,,,02-10-2019,15-04-2024,Morehall Primary School and Nursery,Chart Road,,Folkestone,Kent,CT19 4PN,www.turnermorehall.org,1303275128.0,Mrs,Am'e,Moris,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,VI - Visual Impairment,,,,,,,,,,,,,Resourced provision,2.0,3.0,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,620753.0,136837.0,Folkestone and Hythe 006,Folkestone and Hythe 006C,,,,,Good,South-East England and South London,,50027301.0,,Not applicable,Not applicable,,,E02005107,E01024513,57.0,
|
||||
145013,886,Kent,2090,Richmond Primary School,Academy sponsor led,Academies,Open,Fresh Start,01-01-2017,,,Primary,2.0,11,,Has Nursery Classes,Not applicable,Mixed,None,None,,,420.0,Not applicable,19-01-2023,325.0,156.0,169.0,64.2,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10064518.0,,,09-11-2022,07-06-2024,Unity Street,,,Sheerness,Kent,ME12 2ET,,1795662891.0,,Lesley,Conway,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheerness,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,592928.0,174502.0,Swale 001,Swale 001D,,,,,Good,South-East England and South London,,100062378139.0,,Not applicable,Not applicable,,,E02005115,E01024612,188.0,
|
||||
145014,886,Kent,2092,Knockhall Primary School,Academy sponsor led,Academies,Open,Fresh Start,01-01-2017,,,Primary,3.0,11,Not applicable,Has Nursery Classes,Not applicable,Mixed,None,None,,Not applicable,682.0,Not applicable,19-01-2023,385.0,215.0,170.0,33.7,Supported by a multi-academy trust,THE WOODLAND ACADEMY TRUST,Linked to a sponsor,The Woodland Academy Trust,Not applicable,,10064519.0,,,21-06-2023,10-05-2024,Eynsford Road,,,Greenhithe,Kent,DA9 9RF,www.knockhallprimaryschool.co.uk,1322382053.0,Miss,Kathryn,Yiannadji,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Greenhithe & Knockhall,Dartford,(England/Wales) Urban major conurbation,E10000016,559140.0,174692.0,Dartford 004,Dartford 004F,,,,,Requires improvement,South-East England and South London,,200000537409.0,,Not applicable,Not applicable,,,E02005031,E01035283,126.0,
|
||||
145081,886,Kent,3059,"St Mark's Church of England Primary School, Eccles",Academy converter,Academies,Open,Academy Converter,01-11-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,140.0,No Special Classes,19-01-2023,128.0,61.0,67.0,15.6,Supported by a multi-academy trust,THE PILGRIM MULTI ACADEMY TRUST,-,,Not applicable,,10065389.0,,Not applicable,22-03-2022,17-04-2024,Eccles Row,Eccles,,Aylesford,Kent,ME20 7HS,www.st-marks-aylesford.kent.sch.uk,1622717337.0,Mr,Jonathan,Bassett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford North & North Downs,Chatham and Aylesford,(England/Wales) Rural town and fringe,E10000016,572898.0,160831.0,Tonbridge and Malling 001,Tonbridge and Malling 001F,,,,,Good,South-East England and South London,,200000961684.0,,Not applicable,Not applicable,,,E02005149,E01024728,20.0,
|
||||
145115,886,Kent,2093,Chilmington Green Primary School,Free schools,Free Schools,Open,,03-09-2018,,,Primary,2.0,11,,Has Nursery Classes,Not applicable,Mixed,None,None,,,460.0,Not applicable,19-01-2023,205.0,110.0,95.0,21.5,Supported by a multi-academy trust,THE STOUR ACADEMY TRUST,Linked to a sponsor,The Stour Academy Trust,Not applicable,,10068096.0,,,07-12-2022,29-05-2024,Mock Lane,,,Ashford,Kent,TN23 3DS,www.chilmingtongreen.kent.sch.uk,1233228241.0,Miss,Tamsin,Mobbs,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,Not Applicable,,,,,,,,,,,,,Resourced provision,,15.0,,,South East,Ashford,Weald Central,Ashford,(England/Wales) Rural hamlet and isolated dwellings,E10000016,600157.0,141878.0,Ashford 012,Ashford 012F,,,,,Good,South-East England and South London,United Kingdom,10012877144.0,,Not applicable,Not applicable,,,E02005007,E01032814,42.0,
|
||||
145117,886,Kent,2096,Riverview Junior School,Academy sponsor led,Academies,Open,New Provision,01-10-2017,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,480.0,Not applicable,19-01-2023,504.0,257.0,247.0,24.6,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10065188.0,,,09-02-2022,22-05-2024,Cimba Wood,,,Gravesend,Kent,DA12 4SD,https://www.riverview-junior.co.uk/,1474352620.0,Mr,Aaron,Jones,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,SpLD - Specific Learning Difficulty,"SLCN - Speech, language and Communication",ASD - Autistic Spectrum Disorder,"SEMH - Social, Emotional and Mental Health",MLD - Moderate Learning Difficulty,,,,,,,,,Resourced provision,,,,,South East,Gravesham,Riverview Park,Gravesham,(England/Wales) Urban major conurbation,E10000016,566276.0,171553.0,Gravesham 008,Gravesham 008B,,,,,Good,South-East England and South London,,10012012179.0,,Not applicable,Not applicable,,,E02005062,E01024298,124.0,
|
||||
145355,886,Kent,2531,Vale View Community School,Academy converter,Academies,Open,Academy Converter,01-01-2018,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,229.0,112.0,117.0,50.7,Supported by a multi-academy trust,WHINLESS DOWN ACADEMY TRUST,-,,Not applicable,,10066413.0,,Not applicable,27-04-2022,27-03-2024,Vale View Road,Elms Vale,,Dover,Kent,CT17 9NP,www.vale-view.kent.sch.uk,1304202821.0,Mrs,Lisa,Sprigmore,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Town & Castle,Dover,(England/Wales) Urban city and town,E10000016,630652.0,141278.0,Dover 013,Dover 013C,,,,,Good,South-East England and South London,,100062290539.0,,Not applicable,Not applicable,,,E02005053,E01024216,116.0,
|
||||
145420,886,Kent,4020,Folkestone Academy,Academy sponsor led,Academies,Open,Fresh Start,01-12-2017,,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,None,None,,Non-selective,2170.0,Not applicable,19-01-2023,1080.0,538.0,542.0,45.8,Supported by a multi-academy trust,TURNER SCHOOLS,Linked to a sponsor,Turner Schools,Not applicable,,10066337.0,,,21-04-2022,14-05-2024,Academy Lane,,,Folkestone,Kent,CT19 5FP,www.folkestoneacademy.com,1303842400.0,Mr,Steven,Shaw,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Broadmead,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,621910.0,137354.0,Folkestone and Hythe 006,Folkestone and Hythe 006F,,,,,Good,South-East England and South London,United Kingdom,50120541.0,,Not applicable,Not applicable,,,E02005107,E01024516,426.0,
|
||||
145815,886,Kent,2666,Wrotham Road Primary School,Academy converter,Academies,Open,Academy Converter,01-06-2018,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,446.0,235.0,211.0,29.5,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10067738.0,,Not applicable,06-10-2022,18-04-2024,Wrotham Road,,,Gravesend,Kent,DA11 0QF,http://www.wrotham-road.kent.sch.uk,1474534540.0,Ms,Nicole,Galinis,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Pelham,Gravesham,(England/Wales) Urban major conurbation,E10000016,564628.0,173560.0,Gravesham 002,Gravesham 002D,,,,,Good,South-East England and South London,,100062310321.0,,Not applicable,Not applicable,,,E02005056,E01024291,121.0,
|
||||
145923,886,Kent,4021,Turner Free School,Free schools,Free Schools,Open,,01-09-2018,,,Secondary,11.0,18,,Not applicable,Has a sixth form,Mixed,None,None,,Non-selective,1260.0,No Special Classes,19-01-2023,826.0,430.0,396.0,35.6,Supported by a multi-academy trust,TURNER SCHOOLS,Linked to a sponsor,Turner Schools,Not applicable,,10068105.0,,,07-12-2022,21-05-2024,Tile Kiln Lane,Cheriton,,Folkestone,,CT19 4PB,www.turnerfreeschool.org,1303842400.0,Ms,Jennifer,van Deelen,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,X999999,620593.0,137066.0,Folkestone and Hythe 006,Folkestone and Hythe 006C,,,,,Good,South-East England and South London,,50114755.0,,Not applicable,Not applicable,,,E02005107,E01024513,294.0,
|
||||
145951,886,Kent,2098,Pilgrims' Way Primary School,Academy sponsor led,Academies,Open,Fresh Start,01-05-2018,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,,,420.0,Not applicable,19-01-2023,348.0,184.0,164.0,52.9,Supported by a multi-academy trust,VERITAS MULTI ACADEMY TRUST,Linked to a sponsor,Veritas Multi Academy Trust,Not applicable,,10067579.0,,,22-09-2022,14-05-2024,Pilgrims Way,,,Canterbury,,CT1 1XU,https://www.pilgrims-way.kent.sch.uk,1227760084.0,Mrs,Emma,Campbell,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,616215.0,157035.0,Canterbury 016,Canterbury 016B,,,,,Good,South-East England and South London,United Kingdom,200000683218.0,,Not applicable,Not applicable,,,E02005025,E01024045,171.0,
|
||||
146081,886,Kent,2099,Edenbridge Primary School,Academy sponsor led,Academies,Open,New Provision,01-07-2018,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,450.0,No Special Classes,19-01-2023,407.0,206.0,201.0,38.2,Supported by a multi-academy trust,THE PIONEER ACADEMY,Linked to a sponsor,The Pioneer Academy,Not applicable,,10068058.0,,,12-10-2022,23-04-2024,High Street,,,Edenbridge,Kent,TN8 5AB,https://edenbridge.kent.sch.uk/kent/primary/edenbridge,1732863787.0,,Mary,Gates,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Edenbridge North and East,Tonbridge and Malling,(England/Wales) Rural town and fringe,E10000016,544347.0,146395.0,Sevenoaks 014,Sevenoaks 014A,,,,,Good,South-East England and South London,United Kingdom,100061001421.0,,Not applicable,Not applicable,,,E02005100,E01024425,153.0,
|
||||
146114,886,Kent,2677,Coxheath Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2018,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,434.0,212.0,222.0,21.7,Supported by a multi-academy trust,COPPICE PRIMARY PARTNERSHIP,Linked to a sponsor,The Coppice Primary Partnership,Not applicable,,10068466.0,,Not applicable,08-02-2023,14-09-2023,Stockett Lane,Coxheath,,Maidstone,Kent,ME17 4PS,www.coxheath.kent.sch.uk/,1622745553.0,Mr,Giacomo,Mazza,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Coxheath and Hunton,Maidstone and The Weald,(England/Wales) Rural town and fringe,E10000016,574377.0,151268.0,Maidstone 016,Maidstone 016A,,,,,Good,South-East England and South London,,200003715377.0,,Not applicable,Not applicable,,,E02005083,E01024342,94.0,
|
||||
146143,886,Kent,2044,Loose Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2018,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,,Not applicable,630.0,Not applicable,19-01-2023,656.0,343.0,313.0,8.1,Supported by a multi-academy trust,COPPICE PRIMARY PARTNERSHIP,Linked to a sponsor,The Coppice Primary Partnership,Not applicable,,10068467.0,,Not applicable,28-06-2023,07-05-2024,Loose Road,,,Maidstone,Kent,ME15 9UW,www.loose-primary.kent.sch.uk/,1622743549.0,Mr,Trevor,North,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Loose,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,576115.0,152490.0,Maidstone 016,Maidstone 016D,,,,,Good,South-East England and South London,,200003676025.0,,Not applicable,Not applicable,,,E02005083,E01024376,53.0,
|
||||
146376,886,Kent,2107,Rosherville Church of England Academy,Academy sponsor led,Academies,Open,Fresh Start,01-09-2018,,,Primary,4.0,11,,No Nursery Classes,Not applicable,Mixed,Church of England,None,,,140.0,Not applicable,19-01-2023,141.0,76.0,65.0,41.8,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10081064.0,,,28-09-2022,26-04-2024,London Road,,,Northfleet,Kent,DA11 9JQ,www.rosherville.co.uk,1474365266.0,Mr,Marc,Dockrell,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Northfleet & Springhead,Gravesham,(England/Wales) Urban major conurbation,E10000016,562934.0,174025.0,Gravesham 001,Gravesham 001B,,,,,Good,South-East England and South London,United Kingdom,100062064703.0,,Not applicable,Not applicable,,,E02005055,E01024277,59.0,
|
||||
146400,886,Kent,3313,Fordcombe Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2018,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,70.0,38.0,32.0,27.1,Supported by a multi-academy trust,THE TENAX SCHOOLS TRUST,Linked to a sponsor,The Tenax Schools Trust,Not applicable,,10081146.0,,Not applicable,18-10-2022,21-05-2024,The Green,Fordcombe,,Tunbridge Wells,Kent,TN3 0RY,http://www.fordcombe.kent.sch.uk,1892740224.0,Mr,Chris,Blackburn,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Penshurst, Fordcombe and Chiddingstone",Tonbridge and Malling,(England/Wales) Rural village,E10000016,552622.0,140271.0,Sevenoaks 015,Sevenoaks 015D,,,,,Good,South-East England and South London,,10035185120.0,,Not applicable,Not applicable,,,E02005101,E01024456,19.0,
|
||||
146574,886,Kent,2062,Greenlands Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2019,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,187.0,94.0,93.0,42.8,Supported by a multi-academy trust,CYGNUS ACADEMIES TRUST,Linked to a sponsor,Cygnus Academies Trust,Not applicable,,10082225.0,,Not applicable,18-05-2023,06-06-2024,Green Street Green Road,Darenth,,Dartford,Kent,DA2 8DH,www.greenlandsprimary.org.uk,1474703178.0,Mrs,Alison,Cook,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Darenth,Dartford,(England/Wales) Rural village,E10000016,557498.0,170938.0,Dartford 012,Dartford 012C,,,,,Requires improvement,South-East England and South London,,200000533883.0,,Not applicable,Not applicable,,,E02005039,E01024135,80.0,
|
||||
146624,886,Kent,4023,Goodwin Academy,Academy sponsor led,Academies,Open,Fresh Start,01-09-2018,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,None,,Non-selective,1150.0,Not applicable,19-01-2023,900.0,435.0,465.0,32.5,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10081522.0,,,19-10-2022,03-06-2024,Hamilton Road,,,Deal,,CT14 9BD,https://www.goodwinacademy.org.uk/,3333602210.0,Mr,Phil,Jones,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,Resourced provision,14.0,13.0,,,South East,Dover,Middle Deal,Dover,(England/Wales) Urban city and town,E10000016,637076.0,151679.0,Dover 007,Dover 007A,,,,,Requires improvement,South-East England and South London,United Kingdom,100062286680.0,,Not applicable,Not applicable,,,E02005047,E01024218,276.0,
|
||||
146950,886,Kent,5224,All Soul's Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,315.0,No Special Classes,19-01-2023,250.0,124.0,126.0,40.0,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10083010.0,,Not applicable,13-09-2023,23-04-2024,Stanley Road,,,Folkestone,Kent,CT19 4LG,www.allsouls.kent.sch.uk/,1303275967.0,Mrs,Lisa,Ransley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Cheriton,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,620109.0,136784.0,Folkestone and Hythe 005,Folkestone and Hythe 005A,,,,,Good,South-East England and South London,,50028932.0,,Not applicable,Not applicable,,,E02005106,E01024491,100.0,
|
||||
147053,886,Kent,3353,Deal Parochial Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,199.0,95.0,104.0,36.7,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10083015.0,,Not applicable,28-06-2023,28-03-2024,Gladstone Road,Walmer,,Deal,Kent,CT14 7ER,http://www.deal-parochial.kent.sch.uk,1304374464.0,Ms,Justine,Brown,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Walmer,Dover,(England/Wales) Urban city and town,E10000016,637295.0,151463.0,Dover 004,Dover 004D,,,,,Good,South-East England and South London,,200002882433.0,,Not applicable,Not applicable,,,E02005044,E01024252,73.0,
|
||||
147054,886,Kent,3911,Hornbeam Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,240.0,Not applicable,19-01-2023,238.0,118.0,120.0,29.8,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10083013.0,,Not applicable,11-10-2023,01-05-2024,Mongeham Road,,,Deal,Kent,CT14 9PQ,www.hornbeam.kent.sch.uk,1304374033.0,Mrs,Rose,Cope,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Mill Hill,Dover,(England/Wales) Urban city and town,E10000016,635697.0,152063.0,Dover 005,Dover 005E,,,,,Good,South-East England and South London,,100062287063.0,,Not applicable,Not applicable,,,E02005045,E01024226,71.0,
|
||||
147055,886,Kent,3172,Northbourne Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,140.0,No Special Classes,19-01-2023,132.0,69.0,63.0,6.8,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10083011.0,,Not applicable,18-07-2023,07-05-2024,Coldharbour Lane,Northbourne,,Deal,Kent,CT14 0LP,www.northbourne-cep.kent.sch.uk,1304611376.0,Mr,Matthew,Reynolds,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Eastry Rural,Dover,(England/Wales) Rural village,E10000016,632267.0,151903.0,Dover 005,Dover 005A,,,,,Good,South-East England and South London,,10034875643.0,,Not applicable,Not applicable,,,E02005045,E01024201,9.0,
|
||||
147056,886,Kent,2659,Sandown School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,380.0,191.0,189.0,32.1,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10083016.0,,Not applicable,20-09-2023,20-05-2024,Golf Road,,,Deal,Kent,CT14 6PY,http://www.sandown.kent.sch.uk/,1304374951.0,Ms,Kate,Luxford,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,North Deal,Dover,(England/Wales) Urban city and town,E10000016,637419.0,153252.0,Dover 004,Dover 004B,,,,,Good,South-East England and South London,,100062285576.0,,Not applicable,Not applicable,,,E02005044,E01024230,122.0,
|
||||
147057,886,Kent,3358,Sholden Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,105.0,No Special Classes,19-01-2023,97.0,46.0,51.0,21.6,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10083012.0,,Not applicable,14-06-2023,20-05-2024,London Road,Sholden,,Deal,Kent,CT14 0AB,www.sholdenprimary.org.uk,1304374852.0,Mrs,Dawn,Theaker,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Eastry Rural,Dover,(England/Wales) Urban city and town,E10000016,635694.0,152316.0,Dover 003,Dover 003A,,,,,Good,South-East England and South London,,100062285027.0,,Not applicable,Not applicable,,,E02005043,E01024219,21.0,
|
||||
147058,886,Kent,3163,The Downs Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,420.0,No Special Classes,19-01-2023,317.0,156.0,161.0,36.9,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10083014.0,,Not applicable,13-09-2023,07-05-2024,Owen Square,Walmer,,Deal,Kent,CT14 7TL,http://www.downs.kent.sch.uk/,1304372486.0,Ms,Natalie,Luxford,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Walmer,Dover,(England/Wales) Urban city and town,E10000016,636946.0,150846.0,Dover 007,Dover 007D,,,,,Good,South-East England and South London,,100062286224.0,,Not applicable,Not applicable,,,E02005047,E01024250,117.0,
|
||||
147059,886,Kent,4024,Stone Lodge School,Free schools,Free Schools,Open,New Provision,02-09-2019,,,Secondary,11.0,19,,No Nursery Classes,Has a sixth form,Mixed,None,None,,Non-selective,930.0,Not applicable,19-01-2023,677.0,392.0,285.0,26.9,Supported by a multi-academy trust,ENDEAVOUR MAT,-,,Not applicable,,10083768.0,,,18-10-2023,23-04-2024,Stone Lodge Road,,,Dartford,Kent,DA2 6FY,https://www.stonelodgeschool.co.uk/,1322250340.0,Mr,Gavin,Barnett,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dartford,Stone Castle,Dartford,(England/Wales) Urban major conurbation,E10000016,556289.0,174355.0,Dartford 006,Dartford 006B,,,,,Good,South-East England and South London,United Kingdom,10094155641.0,,Not applicable,Not applicable,,,E02005033,E01024170,177.0,
|
||||
147083,886,Kent,2112,River Mill Primary School,Free schools,Free Schools,Open,New Provision,02-09-2019,,,Primary,3.0,11,,Has Nursery Classes,Does not have a sixth form,Mixed,,,,Non-selective,420.0,Not applicable,19-01-2023,240.0,128.0,112.0,9.2,Supported by a multi-academy trust,CONNECT SCHOOLS ACADEMY TRUST,Linked to a sponsor,Connect Schools Academy Trust,Not applicable,,10083765.0,,,06-12-2023,20-05-2024,Central Road,,,Dartford,,DA1 5XR,www.rivermillprimaryschool.co.uk,1322466975.0,Ms,Suzanne,Leader,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Town,Dartford,(England/Wales) Urban major conurbation,E10000016,554517.0,175013.0,Dartford 001,Dartford 001J,,,,,Good,South-East England and South London,United Kingdom,10023444394.0,,Not applicable,Not applicable,,,E02005028,E01035275,22.0,
|
||||
147104,886,Kent,2114,Cage Green Primary School,Academy sponsor led,Academies,Open,New Provision,01-07-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,,421.0,Has Special Classes,19-01-2023,225.0,126.0,99.0,44.9,Supported by a multi-academy trust,CONNECT SCHOOLS ACADEMY TRUST,Linked to a sponsor,Connect Schools Academy Trust,Not applicable,,10083593.0,,Not applicable,09-11-2023,08-05-2024,Cage Green Road,,,Tonbridge,Kent,TN10 4PT,www.cage-green.kent.sch.uk,1732354325.0,,Joanna,Styles,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,19.0,22.0,,,South East,Tonbridge and Malling,Trench,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559644.0,148493.0,Tonbridge and Malling 011,Tonbridge and Malling 011A,,,,,Good,South-East England and South London,,100062543771.0,,Not applicable,Not applicable,,,E02005159,E01024729,101.0,
|
||||
147205,886,Kent,6156,VTC Independent School,Other independent school,Independent schools,Open,New Provision,04-05-2020,,,Not applicable,13.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Selective,32.0,Has Special Classes,20-01-2022,19.0,18.0,1.0,0.0,Not applicable,,Not applicable,,Not applicable,,,,,30-11-2023,04-06-2024,"Unit 2,",Centre 2000,St Michaels Road,Sittingbourne,Kent,ME10 3DZ,https://vtcindependentschool.co.uk/,1795899240.0,Mrs,Anna,Daly,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not approved,,,,,,,,,,,,,,,,,,,South East,Swale,Roman,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591338.0,163638.0,Swale 010,Swale 010D,Ofsted,18.0,1.0,Vocational Training Centre Ltd,Good,South-East England and South London,United Kingdom,200002540944.0,,Not applicable,Not applicable,,,E02005124,E01024599,0.0,
|
||||
147280,886,Kent,2135,Horsmonden Primary Academy,Academy converter,Academies,Open,Academy Converter,01-09-2019,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,180.0,94.0,86.0,10.0,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10084109.0,,Not applicable,19-10-2023,09-05-2024,Back Lane,Horsmonden,,Tonbridge,Kent,TN12 8NJ,https://horsmondenprimaryacademy.org.uk/,1892722529.0,Mrs,Hayley,Sharp,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Brenchley and Horsmonden,Tunbridge Wells,(England/Wales) Rural town and fringe,E10000016,570239.0,140737.0,Tunbridge Wells 011,Tunbridge Wells 011A,,,,,Good,South-East England and South London,,10008667663.0,,Not applicable,Not applicable,,,E02005172,E01024792,18.0,
|
||||
147409,886,Kent,2127,Paddock Wood Primary Academy,Academy converter,Academies,Open,Academy Converter,01-09-2019,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,611.0,325.0,286.0,18.0,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10084108.0,,Not applicable,09-11-2023,30-04-2024,Old Kent Road,Paddock Wood,,Tonbridge,Kent,TN12 6JE,www.paddockwoodprimaryacademy.org.uk,1892833654.0,Mr,Simon,Page (Interim),Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tunbridge Wells,Paddock Wood East,Tunbridge Wells,(England/Wales) Rural town and fringe,E10000016,566923.0,144859.0,Tunbridge Wells 001,Tunbridge Wells 001D,,,,,Good,South-East England and South London,,100062545661.0,,Not applicable,Not applicable,,,E02005162,E01024813,110.0,
|
||||
147454,886,Kent,2117,Dartford Bridge Community Primary School,Academy sponsor led,Academies,Open,New Provision,01-10-2019,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,,446.0,No Special Classes,19-01-2023,471.0,242.0,229.0,8.6,Supported by a multi-academy trust,CYGNUS ACADEMIES TRUST,Linked to a sponsor,Cygnus Academies Trust,Not applicable,,10084107.0,,,28-02-2024,22-05-2024,Community Campus,Birdwood Avenue,,Dartford,Kent,DA1 5GB,dartfordbridgecps.com,1322470678.0,Mrs,Sarah,Smith,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Bridge,Dartford,(England/Wales) Rural town and fringe,E10000016,554858.0,176047.0,Dartford 001,Dartford 001F,,,,,Good,South-East England and South London,United Kingdom,10023437975.0,,Not applicable,Not applicable,,,E02005028,E01035271,36.0,
|
||||
147563,886,Kent,2287,Rolvenden Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2019,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,98.0,No Special Classes,19-01-2023,83.0,44.0,39.0,20.5,Supported by a multi-academy trust,TENTERDEN SCHOOLS TRUST,-,,Not applicable,,10084732.0,,Not applicable,,22-05-2024,Hastings Road,Rolvenden,,Cranbrook,Kent,TN17 4LS,http://www.rolvenden.kent.sch.uk,1580241444.0,,Ben,Vincer,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Ashford,Rolvenden & Tenterden West,Ashford,(England/Wales) Rural village,E10000016,584417.0,131221.0,Ashford 013,Ashford 013B,,,,,,South-East England and South London,,100062552639.0,,Not applicable,Not applicable,,,E02005008,E01024010,17.0,
|
||||
147591,886,Kent,2118,St Katherine's School & Nursery,Academy sponsor led,Academies,Open,New Provision,01-11-2019,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,511.0,257.0,254.0,28.0,Supported by a multi-academy trust,COPPICE PRIMARY PARTNERSHIP,Linked to a sponsor,The Coppice Primary Partnership,Not applicable,,10084731.0,,,31-01-2024,20-05-2024,St. Katherines Lane,,,Snodland,Kent,ME6 5EJ,www.stkatherineskent.co.uk,1634240061.0,Mr,Ray,Lang,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Snodland East & Ham Hill,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,569847.0,161292.0,Tonbridge and Malling 002,Tonbridge and Malling 002D,,,,,Good,South-East England and South London,United Kingdom,100062628713.0,,Not applicable,Not applicable,,,E02005150,E01024772,143.0,
|
||||
147729,886,Kent,2126,Sunny Bank Primary School,Academy sponsor led,Academies,Open,New Provision,01-02-2020,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,315.0,Not applicable,19-01-2023,194.0,104.0,90.0,56.0,Supported by a multi-academy trust,THE ISLAND LEARNING TRUST,Linked to a sponsor,The Island Learning Trust,Not applicable,,10085416.0,,,,16-05-2024,Sunny Bank,Murston,,Sittingbourne,Kent,ME10 3QN,www.sunnybank.kent.sch.uk,1795473891.0,Mr,Jack,Allen,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Murston,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591932.0,164074.0,Swale 011,Swale 011C,,,,,,South-East England and South London,United Kingdom,200002528712.0,,Not applicable,Not applicable,,,E02005125,E01024593,94.0,
|
||||
147749,886,Kent,2237,Queenborough School and Nursery,Academy converter,Academies,Open,Academy Converter,01-03-2020,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,470.0,233.0,237.0,37.2,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10085584.0,,Not applicable,04-07-2023,03-05-2024,Edward Road,,,Queenborough,Kent,ME11 5DF,http://www.queenborough.kent.sch.uk,1795662574.0,Mr,Jason,Howard,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Queenborough and Halfway,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,591694.0,172082.0,Swale 005,Swale 005A,,,,,Outstanding,South-East England and South London,,100062626308.0,,Not applicable,Not applicable,,,E02005119,E01024594,162.0,
|
||||
147750,886,Kent,2534,Bysing Wood Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2020,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,236.0,128.0,108.0,46.5,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10085585.0,,Not applicable,,19-03-2024,Lower Road,,,Faversham,Kent,ME13 7NU,http://www.bysing-wood.kent.sch.uk,1795534644.0,Mr,Andrew,Harrison,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,St Ann's,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,600233.0,161634.0,Swale 014,Swale 014C,,,,,,South-East England and South London,,200002530516.0,,Not applicable,Not applicable,,,E02005128,E01024604,100.0,
|
||||
147751,886,Kent,2569,Briary Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2020,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,295.0,148.0,147.0,35.9,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10085586.0,,Not applicable,21-02-2024,20-05-2024,Greenhill Road,,,Herne Bay,Kent,CT6 7RS,www.briary.kent.sch.uk/,1227373095.0,Mrs,Kate,Espley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Greenhill,North Thanet,(England/Wales) Urban city and town,E10000016,616083.0,166658.0,Canterbury 004,Canterbury 004B,,,,,Good,South-East England and South London,,200000682218.0,,Not applicable,Not applicable,,,E02005013,E01024065,106.0,
|
||||
147752,886,Kent,2629,Holywell Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2020,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,196.0,104.0,92.0,14.4,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10085587.0,,Not applicable,,03-06-2024,Forge Lane,Upchurch,,Sittingbourne,Kent,ME9 7AE,http://www.holywell.kent.sch.uk/,1634388416.0,Mrs,Nicky,Murrell,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,"Hartlip, Newington and Upchurch",Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,584508.0,167486.0,Swale 008,Swale 008D,,,,,,South-East England and South London,,200002533102.0,,Not applicable,Not applicable,,,E02005122,E01024573,25.0,
|
||||
147850,886,Kent,2129,Springhead Park Primary School,Free schools,Free Schools,Open,New Provision,01-09-2020,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,,Non-selective,472.0,Not applicable,19-01-2023,368.0,176.0,192.0,20.1,Supported by a multi-academy trust,THE PRIMARY FIRST TRUST,Linked to a sponsor,The Primary First Trust,Not applicable,,10086458.0,,,25-05-2023,07-05-2024,Springhead Parkway,Springhead Park,,Northfleet,Kent,DA11 8BY,www.springheadparkprimary.com,1474555155.0,Mr,Wayne,Clayton,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Northfleet & Springhead,Gravesham,(England/Wales) Urban major conurbation,E10000016,562032.0,173001.0,Gravesham 006,Gravesham 006F,,,,,Good,South-East England and South London,United Kingdom,10012022831.0,,Not applicable,Not applicable,,,E02005060,E01035284,67.0,
|
||||
147866,886,Kent,2131,Bearsted Primary Academy,Free schools,Free Schools,Open,New Provision,01-09-2020,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,,Non-selective,446.0,,19-01-2023,263.0,122.0,141.0,6.5,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10086483.0,,,25-01-2023,20-05-2024,Popesfield Way,Weavering,,Maidstone,Kent,ME14 5GA,https://bearstedprimaryacademy.org.uk/,1622250040.0,Mrs,Jane,Tipple,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Maidstone,Boxley,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,578618.0,156729.0,Maidstone 005,Maidstone 005B,,,,,Outstanding,South-East England and South London,United Kingdom,10095448157.0,,Not applicable,Not applicable,,,E02005072,E01024336,17.0,
|
||||
147867,886,Kent,2140,Ebbsfleet Green Primary School,Free schools,Free Schools,Open,New Provision,01-09-2020,,,Primary,3.0,11,,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,,Non-selective,461.0,Not applicable,19-01-2023,262.0,148.0,114.0,8.4,Supported by a multi-academy trust,MARITIME ACADEMY TRUST,Linked to a sponsor,Maritime Academy Trust,Not applicable,,10086480.0,,,08-03-2023,14-09-2023,Ackers Drive Weldon,,Ebbsfleet Valley,Swanscombe,Kent,DA10 1AL,http://www.ebbsfleetgreenprimary.org.uk,1987591627.0,Mrs,Joanne,Wilkinson-Tabi,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,9.0,15.0,,,South East,Dartford,Ebbsfleet,Dartford,(England/Wales) Urban major conurbation,E10000016,560812.0,173242.0,Dartford 002,Dartford 002I,,,,,Good,South-East England and South London,United Kingdom,10094156607.0,,Not applicable,Not applicable,,,E02005029,E01035281,22.0,
|
||||
147897,886,Kent,4027,The Holmesdale School,Academy sponsor led,Academies,Open,New Provision,01-09-2022,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,959.0,No Special Classes,19-01-2023,509.0,252.0,257.0,36.3,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10086757.0,,,,14-05-2024,Malling Road,,,Snodland,Kent,ME6 5HS,https://www.holmesdale.kent.sch.uk,1634240416.0,Mr,Lee,Downey,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,5.0,5.0,,,South East,Tonbridge and Malling,Snodland East & Ham Hill,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,569974.0,161137.0,Tonbridge and Malling 002,Tonbridge and Malling 002D,,,,,,South-East England and South London,United Kingdom,10002905073.0,,Not applicable,Not applicable,,,E02005150,E01024772,168.0,
|
||||
148068,886,Kent,2143,Folkestone Primary,Academy sponsor led,Academies,Open,Split school,01-09-2020,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,,None,,,450.0,Not applicable,19-01-2023,424.0,211.0,213.0,24.1,Supported by a multi-academy trust,TURNER SCHOOLS,Linked to a sponsor,Turner Schools,Not applicable,,10086281.0,,,28-06-2023,14-05-2024,Academy Lane,,,Folkestone,,CT19 5FP,https://www.turnerfolkestoneprimary.com/home,1303842400.0,,Louise,Feaver,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Broadmead,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,621910.0,137354.0,Folkestone and Hythe 006,Folkestone and Hythe 006F,,,,,Good,South-East England and South London,United Kingdom,50120541.0,,Not applicable,Not applicable,,,E02005107,E01024516,102.0,
|
||||
148116,886,Kent,2183,Marden Primary Academy,Academy converter,Academies,Open,Academy Converter,01-09-2020,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,275.0,No Special Classes,19-01-2023,281.0,154.0,127.0,29.9,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10086832.0,,,01-03-2023,14-04-2024,Goudhurst Road,Marden,,Tonbridge,Kent,TN12 9JX,www.mardenprimaryacademy.org.uk,1622831393.0,Mrs,Hannah,Penning,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Maidstone,Marden and Yalding,Maidstone and The Weald,(England/Wales) Rural town and fringe,E10000016,574009.0,144505.0,Maidstone 018,Maidstone 018C,Not applicable,,,,Good,South-East England and South London,,200003718702.0,,Not applicable,Not applicable,,,E02005085,E01024380,84.0,
|
||||
148118,886,Kent,3106,Eastchurch Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2020,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,368.0,187.0,181.0,47.0,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10086841.0,,,12-07-2023,03-05-2024,Warden Road,Eastchurch,,Sheerness,Kent,ME12 4EJ,http://www.eastchurch.kent.sch.uk,1795880279.0,Mrs,Teresa,Oliver (Acting Head),Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheppey East,Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,598963.0,171467.0,Swale 006,Swale 006F,Not applicable,,,,Requires improvement,South-East England and South London,,100062626436.0,,Not applicable,Not applicable,,,E02005120,E01035301,162.0,
|
||||
148144,886,Kent,1132,North West Kent Alternative Provision Service,Academy alternative provision sponsor led,Academies,Open,New Provision,01-09-2020,,,Not applicable,11.0,16,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,,Not applicable,19-01-2023,7.0,6.0,1.0,14.3,Supported by a multi-academy trust,ALTERNATIVE LEARNING TRUST,Linked to a sponsor,Alternative Learning Trust,Not applicable,,10086859.0,,,14-06-2023,05-06-2024,Richmond Drive,,,Gravesend,,DA12 4DJ,,1474332897.0,Ms,Abigail,Woodhouse,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Westcourt,Gravesham,(England/Wales) Urban major conurbation,E10000016,566417.0,172683.0,Gravesham 007,Gravesham 007C,,,,,Good,South-East England and South London,United Kingdom,10012024036.0,,Not applicable,Not applicable,,,E02005061,E01024311,1.0,
|
||||
148217,886,Kent,5202,Holy Trinity Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2020,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,470.0,No Special Classes,19-01-2023,457.0,239.0,218.0,27.3,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10087078.0,,,04-10-2023,30-04-2024,Trinity Road,,,Gravesend,Kent,DA12 1LU,www.holytrinity-gravesend.kent.sch.uk/,1474534746.0,Mrs,Pamela,Gough,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Town,Gravesham,(England/Wales) Urban major conurbation,E10000016,565155.0,173712.0,Gravesham 003,Gravesham 003A,Not applicable,,,,Good,South-East England and South London,,100062311696.0,,Not applicable,Not applicable,,,E02005057,E01024258,118.0,
|
||||
148308,886,Kent,3173,Kingsdown and Ringwould Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-01-2021,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,210.0,No Special Classes,19-01-2023,213.0,114.0,99.0,14.1,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10087305.0,,,25-05-2023,20-02-2024,Glen Road,Kingsdown,,Deal,Kent,CT14 8DD,http://www.kingsdown-ringwould.kent.sch.uk,1304373734.0,Mrs,Joanne,Hygate,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Dover,"Guston, Kingsdown & St Margaret's-at-Cliffe",Dover,(England/Wales) Rural town and fringe,E10000016,637415.0,148426.0,Dover 009,Dover 009A,Not applicable,,,,Outstanding,South-East England and South London,,100062286573.0,,Not applicable,Not applicable,,,E02005049,E01024232,30.0,
|
||||
148370,886,Kent,2327,Worth Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2021,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,70.0,No Special Classes,19-01-2023,63.0,37.0,26.0,20.6,Supported by a multi-academy trust,DEAL EDUCATION ALLIANCE FOR LEARNING TRUST,-,,Not applicable,,10087569.0,,,07-02-2024,20-05-2024,The Street,,,Deal,Kent,CT14 0DF,http://www.worthprimary.co.uk,1304612148.0,Mrs,Katy,Chance,Executive Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Eastry Rural,South Thanet,(England/Wales) Rural village,E10000016,633761.0,156191.0,Dover 002,Dover 002B,Not applicable,,,,Good,South-East England and South London,,100062620829.0,,Not applicable,Not applicable,,,E02005042,E01024242,13.0,
|
||||
148500,886,Kent,2259,Chartham Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2021,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,382.0,199.0,183.0,26.4,Supported by a multi-academy trust,INSPIRA ACADEMY TRUST,-,,Not applicable,,10088095.0,,,07-02-2024,20-05-2024,Shalmsford Street,Chartham,,Canterbury,Kent,CT4 7QN,http://www.charthamprimary.org.uk,1227738225.0,Mr,Jamie,Noble,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Chartham & Stone Street,Canterbury,(England/Wales) Rural hamlet and isolated dwellings,E10000016,610126.0,154535.0,Canterbury 017,Canterbury 017A,Not applicable,,,,Good,South-East England and South London,,200000682327.0,,Not applicable,Not applicable,,,E02005026,E01024052,101.0,
|
||||
148501,886,Kent,2611,St Stephen's Infant School,Academy converter,Academies,Open,Academy Converter,01-04-2021,,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,269.0,146.0,123.0,28.3,Supported by a multi-academy trust,INSPIRA ACADEMY TRUST,-,,Not applicable,,10088094.0,,,24-01-2024,06-06-2024,Hales Drive,St Stephen's,,Canterbury,Kent,CT2 7AB,www.st-stephens-infant.kent.sch.uk,1227769204.0,Mrs,Alice,Edgington,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,St Stephen's,Canterbury,(England/Wales) Urban city and town,E10000016,614817.0,159227.0,Canterbury 013,Canterbury 013B,Not applicable,,,,Good,South-East England and South London,,200002882763.0,,Not applicable,Not applicable,,,E02005022,E01024100,76.0,
|
||||
148502,886,Kent,2626,Sandwich Infant School,Academy converter,Academies,Open,Academy Converter,01-04-2021,,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,168.0,No Special Classes,19-01-2023,148.0,73.0,75.0,23.1,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10088093.0,,,30-01-2024,20-05-2024,School Road,,,Sandwich,Kent,CT13 9HT,www.sandwich-infant.kent.sch.uk/,1304612228.0,Miss,Leanne,Bennett,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dover,Sandwich,South Thanet,(England/Wales) Rural town and fringe,E10000016,632813.0,158407.0,Dover 002,Dover 002D,Not applicable,,,,Good,South-East England and South London,,100062284920.0,,Not applicable,Not applicable,,,E02005042,E01024244,34.0,
|
||||
148519,886,Kent,5229,Fleetdown Primary Academy,Academy converter,Academies,Open,Academy Converter,01-04-2021,,,Primary,4.0,11,,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,654.0,No Special Classes,19-01-2023,648.0,345.0,303.0,13.3,Supported by a multi-academy trust,THE GOLDEN THREAD ALLIANCE,-,,Not applicable,,10088076.0,,,,03-06-2024,Lunedale Road,Darenth,,Dartford,Kent,DA2 6JX,http://www.fleetdown.kent.sch.uk,1322226891.0,Mrs,Alice,Harrington,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,HI - Hearing Impairment,,,,,,,,,,,,,Resourced provision,6.0,12.0,,,South East,Dartford,Brent,Dartford,(England/Wales) Urban major conurbation,E10000016,556256.0,173026.0,Dartford 008,Dartford 008E,Not applicable,,,,,South-East England and South London,,200000545083.0,,Not applicable,Not applicable,,,E02005035,E01024139,86.0,
|
||||
148632,886,Kent,6164,MEPA ACADEMY,Other independent school,Independent schools,Open,New Provision,09-09-2021,,,Not applicable,11.0,16,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,,50.0,Not applicable,20-01-2022,17.0,3.0,14.0,0.0,Not applicable,,Not applicable,,Not applicable,,,,,29-09-2022,07-05-2024,27 & 29 EARL STREET,MAIDSTONE,KENT,,,ME14 1PF,https://www.mepaacademy.com/,1622756644.0,Mrs,Mandy,Ellen,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not approved,,,,,,,,,,,,,,,,,,,South East,Maidstone,High Street,Maidstone and The Weald,(England/Wales) Urban city and town,E10000016,575951.0,155842.0,Maidstone 004,Maidstone 004H,Ofsted,,6.0,Mandy Ellen Cook,Good,South-East England and South London,United Kingdom,10091815091.0,,Not applicable,Not applicable,,,E02005071,E01034991,0.0,
|
||||
148711,886,Kent,2296,Mundella Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2021,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,173.0,87.0,86.0,56.1,Supported by a multi-academy trust,VERITAS MULTI ACADEMY TRUST,Linked to a sponsor,Veritas Multi Academy Trust,Not applicable,,10088877.0,,,,08-04-2024,Black Bull Road,,,Folkestone,Kent,CT19 5QX,http://www.mundella.kent.sch.uk,1303252265.0,Mrs,Lisa Paez and Lauren Wharmby,.,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,East Folkestone,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,622754.0,136911.0,Folkestone and Hythe 004,Folkestone and Hythe 004C,Not applicable,,,,,South-East England and South London,,50040639.0,,Not applicable,Not applicable,,,E02005105,E01024501,97.0,
|
||||
148712,886,Kent,4246,The North School,Academy converter,Academies,Open,Academy Converter,01-01-2022,,,Secondary,11.0,19,No boarders,No Nursery Classes,Has a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Non-selective,1265.0,Not applicable,19-01-2023,1267.0,700.0,567.0,31.1,Supported by a multi-academy trust,SWALE ACADEMIES TRUST,Linked to a sponsor,Swale Academies Trust,Not applicable,,10088876.0,,,,28-05-2024,Essella Road,,,Ashford,Kent,TN24 8AL,https://www.thenorthschool.org.uk/,1233614600.0,Mrs,Clair,Ellerby,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision,25.0,25.0,,,South East,Ashford,Furley,Ashford,(England/Wales) Urban city and town,E10000016,601952.0,142258.0,Ashford 005,Ashford 005C,Not applicable,,,,,South-East England and South London,,100062560863.0,,Not applicable,Not applicable,,,E02005000,E01024023,350.0,
|
||||
148876,886,Kent,2133,Halstead Community Primary School,Academy converter,Academies,Open,Academy Converter,01-03-2022,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,175.0,No Special Classes,19-01-2023,54.0,33.0,21.0,25.9,Supported by a multi-academy trust,THE PIONEER ACADEMY,Linked to a sponsor,The Pioneer Academy,Not applicable,,10089754.0,,,,03-06-2024,Otford Lane,Halstead,,Sevenoaks,Kent,TN14 7EA,www.halstead.kent.sch.uk,1959532224.0,Mrs,Sue,Saheed,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,"Halstead, Knockholt and Badgers Mount",Sevenoaks,(England/Wales) Rural village,E10000016,548908.0,161035.0,Sevenoaks 008,Sevenoaks 008D,Not applicable,,,,,South-East England and South London,,50002001021.0,,Not applicable,Not applicable,,,E02005094,E01024440,14.0,
|
||||
148918,886,Kent,2119,Shears Green Infant School,Academy converter,Academies,Open,Academy Converter,01-07-2022,,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,342.0,163.0,179.0,21.3,Supported by a multi-academy trust,HORNCHURCH ACADEMY TRUST,-,,Not applicable,,10089902.0,,,,04-05-2024,Packham Road,Northfleet,,Gravesend,Kent,DA11 7JF,http://www.shearsgreeninfantschool.co.uk,1474566700.0,Ms,Hayley,Kotze,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Gravesham,Coldharbour & Perry Street,Gravesham,(England/Wales) Urban major conurbation,E10000016,563816.0,172261.0,Gravesham 009,Gravesham 009A,Not applicable,,,,,South-East England and South London,,100062310733.0,,Not applicable,Not applicable,,,E02005063,E01024264,73.0,
|
||||
148991,886,Kent,2272,East Stour Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2022,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,472.0,No Special Classes,19-01-2023,438.0,233.0,205.0,36.2,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10090164.0,,,,07-06-2024,Earlsworth Road,South Willesborough,,Ashford,Kent,TN24 0DW,www.east-stour.kent.sch.uk/,1233630820.0,Mrs,E,Law,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Aylesford & East Stour,Ashford,(England/Wales) Urban city and town,E10000016,601863.0,140767.0,Ashford 009,Ashford 009G,Not applicable,,,,,South-East England and South London,,100062560510.0,,Not applicable,Not applicable,,,E02005004,E01032817,147.0,
|
||||
148992,886,Kent,2672,Palm Bay Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2022,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,428.0,No Special Classes,19-01-2023,424.0,218.0,206.0,26.9,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10090163.0,,,,07-06-2024,Palm Bay Avenue,Cliftonville,,Margate,Kent,CT9 3PP,http://www.palmbay.uk,1843290050.0,Miss,Lizzie,Williams,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Cliftonville East,South Thanet,(England/Wales) Urban city and town,E10000016,637983.0,171167.0,Thanet 002,Thanet 002D,Not applicable,,,,,South-East England and South London,,100062307578.0,,Not applicable,Not applicable,,,E02005133,E01024656,114.0,
|
||||
149039,886,Kent,4028,Barton Manor School,Free schools,Free Schools,Open,New Provision,01-09-2022,,,Secondary,11.0,19,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1050.0,Not applicable,19-01-2023,149.0,71.0,78.0,36.9,Supported by a multi-academy trust,BARTON COURT ACADEMY TRUST,Linked to a sponsor,Barton Court Academy Trust,Not applicable,,10090861.0,,,,17-05-2024,Spring Lane,,,Canterbury,Kent,CT1 1SU,https://www.bartonmanor.org/,1227532140.0,Mr,Richard,Morgan,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Canterbury,Barton,Canterbury,(England/Wales) Urban city and town,E10000016,616290.0,157296.0,Canterbury 016,Canterbury 016B,,,,,,South-East England and South London,United Kingdom,10094587078.0,,Not applicable,Not applicable,,,E02005025,E01024045,55.0,
|
||||
149123,886,Kent,3020,Sedley's Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-06-2022,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,105.0,No Special Classes,19-01-2023,99.0,57.0,42.0,9.1,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10090460.0,,,12-07-2023,07-06-2024,Church Street,Southfleet,,Gravesend,Kent,DA13 9NR,www.sedleys.kent.sch.uk/,1474833221.0,Mrs,T,Handley,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,"Longfield, New Barn & Southfleet",Dartford,(England/Wales) Rural village,E10000016,561345.0,171128.0,Dartford 013,Dartford 013A,Not applicable,,,,Good,South-East England and South London,,200000535394.0,,Not applicable,Not applicable,,,E02005040,E01024157,9.0,
|
||||
149261,886,Kent,3718,St Augustine's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2022,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,203.0,111.0,92.0,13.8,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10090965.0,,,,23-04-2024,St John's Road,,,Hythe,Kent,CT21 4BE,www.st-augustines-hythe.kent.sch.uk,1303266578.0,Mrs,Nicola,Clarke,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,Hythe,Folkestone and Hythe,(England/Wales) Urban city and town,E10000016,615271.0,135480.0,Folkestone and Hythe 008,Folkestone and Hythe 008A,Not applicable,,,,,South-East England and South London,,50017101.0,,Not applicable,Not applicable,,,E02005109,E01024523,28.0,
|
||||
149319,886,Kent,4029,Aylesford School,Academy sponsor led,Academies,Open,New Provision,01-09-2022,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1044.0,No Special Classes,19-01-2023,873.0,453.0,420.0,20.7,Supported by a multi-academy trust,CHARACTER EDUCATION TRUST,Linked to a sponsor,Wrotham School,Not applicable,,10091142.0,,,,06-06-2024,Teapot Lane,,,Aylesford,Kent,ME20 7JU,www.aylesford.kent.sch.uk,1622717341.0,Miss,Tanya,Kelvie,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford South & Ditton,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,571899.0,158410.0,Tonbridge and Malling 005,Tonbridge and Malling 005B,,,,,,South-East England and South London,United Kingdom,200000966311.0,,Not applicable,Not applicable,,,E02005153,E01024718,163.0,
|
||||
149554,886,Kent,4030,The Royal Harbour Academy,Academy sponsor led,Academies,Open,New Provision,01-04-2023,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,916.0,No Special Classes,,,,,,Supported by a multi-academy trust,COASTAL ACADEMIES TRUST,Linked to a sponsor,Coastal Academies Trust,Not applicable,,10091882.0,,,,13-05-2024,Newlands Lane,,,Ramsgate,Kent,CT12 6RH,www.rha.kent.sch.uk,1843572500.0,Mr,Simon,Pullen,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Thanet,Northwood,South Thanet,(England/Wales) Urban city and town,E10000016,637611.0,166588.0,Thanet 011,Thanet 011A,,,,,,South-East England and South London,United Kingdom,10022962106.0,,Not applicable,Not applicable,,,E02005142,E01024686,,
|
||||
149623,886,Kent,3134,John Mayne Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2023,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Not applicable,140.0,No Special Classes,,,,,,Supported by a multi-academy trust,TENTERDEN SCHOOLS TRUST,-,,Not applicable,,10092108.0,,,,18-04-2024,High Street,Biddenden,,Ashford,Kent,TN27 8AL,www.john-mayne.kent.sch.uk/,1580291424.0,Mrs,Helen,Tester,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Biddenden,Ashford,(England/Wales) Rural town and fringe,E10000016,584986.0,138375.0,Ashford 011,Ashford 011B,Not applicable,,,,,South-East England and South London,,100062563751.0,,Not applicable,Not applicable,,,E02005006,E01023979,,
|
||||
149666,886,Kent,2144,St. Clement's CofE Primary School,Academy converter,Academies,Open,Split school,01-04-2023,,,Primary,4.0,11,Not applicable,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Canterbury,Non-selective,210.0,Not applicable,,,,,,Supported by a multi-academy trust,THE DIOCESE OF CANTERBURY ACADEMIES TRUST,Linked to a sponsor,"Aquila, The Diocese of Canterbury Academies Trust",Not applicable,,10092363.0,,,,30-04-2024,Leysdown Road,,,Sheerness,Kent,ME12 4AB,www.stclementscep.co.uk,1795506910.0,Mrs,Kelly,Lockwood,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheppey East,Sittingbourne and Sheppey,(England/Wales) Rural village,E10000016,,,Swale 006,Swale 006A,,,,,,South-East England and South London,United Kingdom,100061077964.0,,Not applicable,Not applicable,,,E02005120,E01024580,,
|
||||
149840,886,Kent,4032,Chilmington Green School,Free schools,Free Schools,Open,Academy Free School,01-09-2023,,,Secondary,11.0,19,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,1140.0,Not applicable,,,,,,Supported by a multi-academy trust,UNITED LEARNING TRUST,Linked to a sponsor,United Learning Trust,Not applicable,,10093045.0,,,,01-05-2024,Jemmett Road,,,Ashford,Kent,TN23 4QE,https://chilmingtongreenschool.org.uk/,1233438800.0,Mr,Jonathan,Rutland,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Ashford,Beaver,Ashford,(England/Wales) Urban city and town,E10000016,,,Ashford 007,Ashford 007B,,,,,,South-East England and South London,United Kingdom,10012841381.0,,Not applicable,Not applicable,,,E02005002,E01023975,,
|
||||
149893,886,Kent,4033,The Abbey School,Academy converter,Academies,Open,Fresh Start,01-04-2023,,,Secondary,11.0,19,,No Nursery Classes,Has a sixth form,Mixed,None,None,,,1226.0,Not applicable,,,,,,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10092583.0,,,,22-05-2024,London Road,,,Faversham,Kent,ME13 8RZ,www.abbeyschoolfaversham.co.uk,1795532633.0,Dr,Rowland,Speller,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision and SEN unit,44.0,44.0,44.0,44.0,South East,Swale,Watling,Faversham and Mid Kent,(England/Wales) Urban city and town,E10000016,,,Swale 014,Swale 014E,,,,,,South-East England and South London,United Kingdom,100062379926.0,,Not applicable,Not applicable,,,E02005128,E01024626,,
|
||||
149975,886,Kent,2066,Maypole Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2023,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,,,,,,Supported by a multi-academy trust,INSPIRE ACADEMY MOVEMENT TRUST,-,,Not applicable,,10092968.0,,,,08-05-2024,Franklin Road,,,Dartford,Kent,DA2 7UZ,www.maypole.kent.sch.uk/,1322523830.0,Miss,Katie,McCann,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Dartford,Maypole & Leyton Cross,Dartford,(England/Wales) Urban major conurbation,E10000016,551223.0,172486.0,Dartford 010,Dartford 010C,Not applicable,,,,,South-East England and South London,,10002021970.0,,Not applicable,Not applicable,,,E02005037,E01024152,,
|
||||
149976,886,Kent,2134,Four Elms Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2023,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,108.0,No Special Classes,,,,,,Supported by a multi-academy trust,INSPIRE ACADEMY MOVEMENT TRUST,-,,Not applicable,,10092967.0,,,,15-05-2024,Bough Beech Road,Four Elms,,Edenbridge,Kent,TN8 6NE,www.four-elms.kent.sch.uk,1732700274.0,Mrs,Liz,Mitchell,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Cowden and Hever,Tonbridge and Malling,(England/Wales) Rural village,E10000016,546847.0,148273.0,Sevenoaks 015,Sevenoaks 015A,Not applicable,,,,,South-East England and South London,,10035181444.0,,Not applicable,Not applicable,,,E02005101,E01024420,,
|
||||
149978,886,Kent,3035,Seal Church of England Voluntary Controlled Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2023,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,444.0,No Special Classes,,,,,,Supported by a multi-academy trust,INSPIRE ACADEMY MOVEMENT TRUST,-,,Not applicable,,10092966.0,,,,09-05-2024,Zambra Way,Seal,,Sevenoaks,Kent,TN15 0DJ,www.sealprimary.com,1732762388.0,Mrs,Tamsin,Jones,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Sevenoaks,Seal and Weald,Sevenoaks,(England/Wales) Urban city and town,E10000016,554704.0,157107.0,Sevenoaks 010,Sevenoaks 010A,Not applicable,,,,,South-East England and South London,,10035185574.0,,Not applicable,Not applicable,,,E02005096,E01024457,,
|
||||
150156,886,Kent,5208,Ditton Church of England Junior School,Academy converter,Academies,Open,Academy Converter,01-11-2023,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,256.0,No Special Classes,,,,,,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10093280.0,,,,21-05-2024,New Road,Ditton,,Aylesford,Kent,ME20 6AE,http://www.ditton-jun.kent.sch.uk,1732843446.0,Mr,Graham,Ward,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Aylesford South & Ditton,Chatham and Aylesford,(England/Wales) Urban city and town,E10000016,571105.0,158080.0,Tonbridge and Malling 005,Tonbridge and Malling 005D,Not applicable,,,,,South-East England and South London,,10002907508.0,,Not applicable,Not applicable,,,E02005153,E01024736,,
|
||||
150255,886,Kent,2692,The Churchill School,Academy converter,Academies,Open,Academy Converter,01-12-2023,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,,,,,,Supported by a multi-academy trust,EKC SCHOOLS TRUST LIMITED,-,,Not applicable,,10093663.0,,,,23-05-2024,Haven Drive,Hawkinge,,Folkestone,Kent,CT18 7RH,http://www.thechurchillschool.co.uk,1303893892.0,Mrs,Zoe,Stone,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Folkestone and Hythe,North Downs East,Folkestone and Hythe,(England/Wales) Rural town and fringe,E10000016,621237.0,139587.0,Folkestone and Hythe 002,Folkestone and Hythe 002F,Not applicable,,,,,South-East England and South London,,50102503.0,,Not applicable,Not applicable,,,E02005103,E01033214,,
|
||||
150278,886,Kent,4034,Dover Christ Church Academy,Academy sponsor led,Academies,Open,Fresh Start,01-09-2023,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,,,990.0,Not applicable,,,,,,Supported by a multi-academy trust,TURNER SCHOOLS,Linked to a sponsor,Turner Schools,Not applicable,,10093628.0,,,,14-05-2024,Melbourne Avenue,,,Dover,,CT16 2EG,,,Mr,Jamie,MacLean,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,PMLD - Profound and Multiple Learning Difficulty,,,,,,,,,,,,,SEN unit,,,69.0,72.0,South East,Dover,Whitfield,Dover,(England/Wales) Urban city and town,E10000016,630895.0,144193.0,Dover 010,Dover 010E,,,,,,South-East England and South London,United Kingdom,100062289416.0,,Not applicable,Not applicable,,,E02005050,E01024254,,
|
||||
150743,886,Kent,4036,Leigh Academy Hugh Christie,Academy sponsor led,Academies,Open,New Provision,01-04-2024,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1226.0,Has Special Classes,,,,,,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10094758.0,,,,01-04-2024,White Cottage Road,,,Tonbridge,Kent,TN10 4PU,,1732353544.0,Mr,Palak,Shah,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Tonbridge and Malling,Trench,Tonbridge and Malling,(England/Wales) Urban city and town,E10000016,559444.0,148580.0,Tonbridge and Malling 009,Tonbridge and Malling 009A,,,,,,South-East England and South London,United Kingdom,10002906320.0,,Not applicable,Not applicable,,,E02005157,E01024730,,
|
||||
150934,886,Kent,4037,EKC Sheppey Secondary,Academy sponsor led,Academies,Proposed to open,Split school,01-09-2024,,,Secondary,11.0,16,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,,None,,,750.0,Not applicable,,,,,,Not supported by a trust,,-,,Not applicable,,10095470.0,,,,27-04-2024,Marine Parade,,,Sheerness,,ME12 2BE,,,,January,Lorman,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Sheerness,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,593060.0,174642.0,Swale 001,Swale 001B,,,,,,South-East England and South London,United Kingdom,100062378138.0,,Not applicable,Not applicable,,,E02005115,E01024610,,
|
||||
150935,886,Kent,4038,Leigh Academy Minster,Academy sponsor led,Academies,Proposed to open,Split school,01-09-2024,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,,None,,,1150.0,Not applicable,,,,,,Not supported by a trust,,-,,Not applicable,,10095469.0,,,,27-04-2024,Minster Road,,,Sheerness,,ME12 3JQ,,,,Mathieu,Stevens,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Swale,Queenborough and Halfway,Sittingbourne and Sheppey,(England/Wales) Urban city and town,E10000016,593740.0,172606.0,Swale 004,Swale 004A,,,,,,South-East England and South London,United Kingdom,200002529794.0,,Not applicable,Not applicable,,,E02005118,E01024595,,
|
||||
|
Can't render this file because it is too large.
|
Binary file not shown.
@ -1,21 +0,0 @@
|
||||
SyllabusSubject,SyllabusKeyStage,SyllabusStageID,SyllabusYearID,TopicStage,TopicSubjectCode,Topic,Lesson,TopicID,LessonID,LessonTitle,LessonTitleEnter,LessonType,NumberOfLearningOutcomes,SuggestedNumberOfPeriodsForLesson,LessonLearningObjectiveEnter,SuggestedActivities,WebLinks,SkillsLearned,TopicTitle,LessonTitleAuto,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,1,1,7B1,7B1.1,"Living, dead and never been alive","Living, dead and never been alive",Standard,3,1,Describe the features of living organisms,Class Prac: Have student carry out mini actions - use these to deduce MRS GRENDiscuss: What determines whether something is alive or not? Use objects/images to help with this.Worksheets: 7Aa1,,,Life Processes,,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,1,2,7B1,7B1.2,Plant and animal cells,Plant and animal cells,Standard,3,1,Identify the main parts of animal cells and describe their functions.,Class Prac: Use bio-viewers to look at simple animal and plants cells. Sketch cells. Label cells. Describe their functions. Venn diagram - Compare plant and animal cells. Highlight differences.Worksheet: 7Ad1,,,Life Processes,,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,1,R,7B1,7B1.R,Revision of topic 7B1 Life Processes,Null,Review,1,1,,,,,Life Processes,Revision of topic 7B1 Life Processes,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,1,T,7B1,7B1.T,Topic test: 7B1 Life Processes,Null,Assessment,1,1,,,,,Life Processes,Topic test: 7B1 Life Processes,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,2,1,7B2,7B2.1,Aerobic Respiration,Aerobic Respiration,Standard,3,1,Be able to describe what happens during respiration,Brain dump: Students write down prior knowledge – opportunity to deal with misconceptions (see starter slide). Demo: Screaming jelly baby,,,Respiration And Circulation,,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,2,2,7B2,7B2.2,Anaerobic respiration,Anaerobic respiration,Standard,3,1,Describe what happens in anaerobic respiration in humans,Explain how and why anaerobic respiration happens in humans. Extend into fermentation. Class Prac: Investigate the rate of anaerobic respiration in yeast. Compare pros and cons of aerobic and anaerobic respiration Worksheets: 8Db-2,,,Respiration And Circulation,,,,,,,,
|
||||
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,12-1,AP.PAG,AP.PAG.12-1,PAG 12 Research,PAG 12 Research,Focus,1,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,12-2,AP.PAG,AP.PAG.12-2,PAG 12 Research,PAG 12 Research,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,12-3,AP.PAG,AP.PAG.12-3,PAG 12 Research,PAG 12 Research,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,2-1,AP.PAG,AP.PAG.2-1,PAG 2 Investigating Properties of Materials,PAG 2 Investigating Properties of Materials,Focus,1,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,2-2,AP.PAG,AP.PAG.2-2,PAG 2 Investigating Properties of Materials,PAG 2 Investigating Properties of Materials,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,2-3,AP.PAG,AP.PAG.2-3,PAG 2 Investigating Properties of Materials,PAG 2 Investigating Properties of Materials,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,3-1,AP.PAG,AP.PAG.3-1,PAG 3 Investigating Electrical Properties: Resistivity of a metal,PAG 3 Investigating Electrical Properties: Resistivity of a metal,Focus,1,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,3-2,AP.PAG,AP.PAG.3-2,PAG 3 Investigating Electrical Properties: Investigating electrical characteristics,PAG 3 Investigating Electrical Properties: Investigating electrical characteristics,Focus,1,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,3-3,AP.PAG,AP.PAG.3-3,PAG 3 Investigating Electrical Properties: Determining the maximum power from a cell,PAG 3 Investigating Electrical Properties: Determining the maximum power from a cell,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,1-1,AP.PAG,AP.PAG.1-1,PAG 4 Investigating Electrical Circuits: Investigating electrical circuits,PAG 4 Investigating Electrical Circuits: Investigating electrical circuits,Focus,1,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,1-2,AP.PAG,AP.PAG.1-2,PAG 4 Investigating Electrical Circuits: Electrical circuits with more than one source of emf,PAG 4 Investigating Electrical Circuits: Electrical circuits with more than one source of emf,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,1-3,AP.PAG,AP.PAG.1-3,PAG 4 Investigating Electrical Circuits: Using non-ohmic devices as sensors,PAG 4 Investigating Electrical Circuits: Using non-ohmic devices as sensors,Focus,0,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
Physics,5,KS5.Physics,KS5.Physics.Special,A,P,PAG,1-1,AP.PAG,AP.PAG.1-1,PAG 5 Investigating Waves: Determining wavelength with diffraction grating,PAG 5 Investigating Waves: Determining wavelength with diffraction grating,Focus,1,1,,,,,A-level Physics PAG,,,,,,,,
|
||||
|
@ -1,8 +0,0 @@
|
||||
SyllabusSubject,SyllabusKeyStage,SyllabusStageID,SyllabusYearID,TopicStage,TopicSubject,Topic,Lesson,Statement,TopicID,LessonID,StatementID,TopicTitle,LessonTitle,LearningStatement,LearningStatementEnter,StatementType,StatementLevel,LearningStatementAuto,,,,,,,,,,,
|
||||
Science,3,KS3.Science,Y7.Science,7,B,1,1,1,7B1,7B1.1,7B1.1.1,Life Processes,"Living, dead and never been alive",Recall the different life processes,Recall the different life processes,Outcome,,Incomplete,,,,,,,,,,,
|
||||
Physics,5,KS5.Physics,Y12.Physics,A,P,8,1,1,AP.8,AP.8.1,AP.8.1.1,Charge And Current,Current And Charge,Understand electric current as the rate of flow of charge using the equation I = ΔQ/Δt,Understand electric current as the rate of flow of charge using the equation I = ΔQ/Δt,Outcome,,Incomplete,,,,,,,,,,,
|
||||
Physics,5,KS5.Physics,Y12.Physics,A,P,8,1,2,AP.8,AP.8.1,AP.8.1.2,Charge And Current,Current And Charge,Define the coulomb as the unit of electric charge,Define the coulomb as the unit of electric charge,Outcome,,Incomplete,,,,,,,,,,,
|
||||
Physics,5,KS5.Physics,Y12.Physics,A,P,8,1,3,AP.8,AP.8.1,AP.8.1.3,Charge And Current,Current And Charge,State the elementary charge e as 1.6 x 10^-19 C,State the elementary charge e as 1.6 x 10^-19 C,Outcome,,Incomplete,,,,,,,,,,,
|
||||
Physics,5,KS5.Physics,Y12.Physics,A,P,8,1,4,AP.8,AP.8.1,AP.8.1.4,Charge And Current,Current And Charge,Recognize that the net charge on a particle or object is quantized and is a multiple of e,Recognize that the net charge on a particle or object is quantized and is a multiple of e,Outcome,,Incomplete,,,,,,,,,,,
|
||||
Physics,5,KS5.Physics,Y12.Physics,A,P,8,2,1,AP.8,AP.8.2,AP.8.2.1,Charge And Current,Moving Charges,Explain current as the movement of electrons in metals and the movement of ions in electrolytes,Explain current as the movement of electrons in metals and the movement of ions in electrolytes,Outcome,,Incomplete,,,,,,,,,,,
|
||||
Physics,5,KS5.Physics,Y12.Physics,A,P,8,2,2,AP.8,AP.8.2,AP.8.2.2,Charge And Current,Moving Charges,Differentiate between conventional current and electron flow,Differentiate between conventional current and electron flow,Outcome,,Incomplete,,,,,,,,,,,
|
||||
|
@ -1,15 +0,0 @@
|
||||
SyllabusSubject,SyllabusKeyStage,SyllabusYear,SyllabusStageID,SyllabusYearID,TopicStage,TopicSubjectCode,Topic,TopicID,TopicTitle,TotalNumberOfLessonsForTopic,TopicType,TopicAssessmentType,,
|
||||
Science,3,7,KS3.Science,Y7.Science,I,B,1,7B1,Life Processes,11,Standard,Test,,
|
||||
Science,3,7,KS3.Science,Y7.Science,I,B,2,7B2,Respiration And Circulation,11,Standard,Test,,
|
||||
Science,3,7,KS3.Science,Y7.Science,I,B,3,7B3,Variation,11,Standard,Test,,
|
||||
Science,3,7,KS3.Science,Y7.Science,I,B,4,7B4,Food And Digestion,12,Standard,Test,,
|
||||
Science,3,7,KS3.Science,Y7.Science,I,B,5,7B5,Reproduction,11,Standard,Test,,
|
||||
Physics,Y13.Physics,A,P,15,AP.15,Ideal Gases,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,16,AP.16,Circular Motion,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,17,AP.17,Oscillations,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,18,AP.18,Gravitational Fields,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,19,AP.19,Stars,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,20,AP.20,Cosmology,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,21,AP.21,Capacitors,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,22,AP.22,Electric Fields,5,Standard,Test,,
|
||||
Physics,5,13,KS5.Physics,Y13.Physics,A,P,23,AP.23,Magnetic Fields,5,Standard,Test,,
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,105 +0,0 @@
|
||||
URN,LA (code),LA (name),EstablishmentNumber,EstablishmentName,TypeOfEstablishment (name),EstablishmentTypeGroup (name),EstablishmentStatus (name),ReasonEstablishmentOpened (name),OpenDate,ReasonEstablishmentClosed (name),CloseDate,PhaseOfEducation (name),StatutoryLowAge,StatutoryHighAge,Boarders (name),NurseryProvision (name),OfficialSixthForm (name),Gender (name),ReligiousCharacter (name),ReligiousEthos (name),Diocese (name),AdmissionsPolicy (name),SchoolCapacity,SpecialClasses (name),CensusDate,NumberOfPupils,NumberOfBoys,NumberOfGirls,PercentageFSM,TrustSchoolFlag (name),Trusts (name),SchoolSponsorFlag (name),SchoolSponsors (name),FederationFlag (name),Federations (name),UKPRN,FEHEIdentifier,FurtherEducationType (name),OfstedLastInsp,LastChangedDate,Street,Locality,Address3,Town,County (name),Postcode,SchoolWebsite,TelephoneNum,HeadTitle (name),HeadFirstName,HeadLastName,HeadPreferredJobTitle,BSOInspectorateName (name),InspectorateReport,DateOfLastInspectionVisit,NextInspectionVisit,TeenMoth (name),TeenMothPlaces,CCF (name),SENPRU (name),EBD (name),PlacesPRU,FTProv (name),EdByOther (name),Section41Approved (name),SEN1 (name),SEN2 (name),SEN3 (name),SEN4 (name),SEN5 (name),SEN6 (name),SEN7 (name),SEN8 (name),SEN9 (name),SEN10 (name),SEN11 (name),SEN12 (name),SEN13 (name),TypeOfResourcedProvision (name),ResourcedProvisionOnRoll,ResourcedProvisionCapacity,SenUnitOnRoll,SenUnitCapacity,GOR (name),DistrictAdministrative (name),AdministrativeWard (name),ParliamentaryConstituency (name),UrbanRural (name),GSSLACode (name),Easting,Northing,MSOA (name),LSOA (name),InspectorateName (name),SENStat,SENNoStat,PropsName,OfstedRating (name),RSCRegion (name),Country (name),UPRN,SiteName,QABName (name),EstablishmentAccredited (name),QABReport,CHNumber,MSOA (code),LSOA (code),FSM,AccreditationExpiryDate
|
||||
118317,887,Medway,2198,Greenvale Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,120.0,No Special Classes,19-01-2023,110.0,58.0,52.0,39.1,Not applicable,,Not applicable,,Not under a federation,,10074076.0,,Not applicable,22-11-2023,22-04-2024,Symons Avenue,,,Chatham,Kent,ME4 5UP,www.greenvale.medway.sch.uk,1634409521.0,Mrs,Amanda,Allnutt,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Chatham Central & Brompton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576183.0,166676.0,Medway 022,Medway 022B,,,,,Good,South-East England and South London,,100062391942.0,,Not applicable,Not applicable,,,E02003335,E01016023,43.0,
|
||||
118320,887,Medway,2202,New Road Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,360.0,187.0,173.0,38.6,Not applicable,,Not applicable,,Not under a federation,,10076230.0,,Not applicable,19-04-2023,04-06-2024,Bryant Street,,,Chatham,Kent,ME4 5QN,http://www.newroad.medway.sch.uk,1634843084.0,Mrs,Samantha,Cooper,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Chatham Central & Brompton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576251.0,167414.0,Medway 015,Medway 015C,,,,,Good,South-East England and South London,,44054185.0,,Not applicable,Not applicable,,,E02003328,E01016019,139.0,
|
||||
118329,887,Medway,2215,Balfour Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,266.0,140.0,126.0,23.8,Not applicable,,Not applicable,,Not under a federation,,10074074.0,,Not applicable,04-06-2019,12-04-2024,Pattens Lane,,,Rochester,Kent,ME1 2QT,http://www.balfourinf.medway.sch.uk,1634338280.0,Ms,Donna,Atkinson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Rochester and Strood,(England/Wales) Urban city and town,E06000035,575083.0,166460.0,Medway 026,Medway 026C,,,,,Good,South-East England and South London,,44035930.0,,Not applicable,Not applicable,,,E02003339,E01016124,63.0,
|
||||
118330,887,Medway,2216,Crest Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,177.0,83.0,94.0,34.5,Not applicable,,Not applicable,,Not under a federation,,10074073.0,,Not applicable,12-02-2020,29-04-2024,Fleet Road,,,Rochester,Kent,ME1 2QA,www.crestinfants.co.uk,1634844127.0,Mrs,Kerry,Seales,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574568.0,166523.0,Medway 024,Medway 024A,,,,,Good,South-East England and South London,,200000909496.0,,Not applicable,Not applicable,,,E02003337,E01016116,60.0,
|
||||
118423,887,Medway,2403,Hempstead Junior School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,360.0,No Special Classes,19-01-2023,353.0,193.0,160.0,7.9,Not applicable,,Not applicable,,Not under a federation,,10079033.0,,Not applicable,20-06-2018,06-06-2024,Birch Grove,Hempstead,,Gillingham,Kent,ME7 3HJ,www.hempsteadjnr.medway.sch.uk/,1634336963.0,Mr,Paul,Cross,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Hempstead & Wigmore,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579226.0,164022.0,Medway 035,Medway 035A,,,,,Good,South-East England and South London,,100062394567.0,,Not applicable,Not applicable,,,E02003348,E01016050,28.0,
|
||||
118442,887,Medway,2439,Horsted Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,177.0,93.0,84.0,10.2,Not applicable,,Not applicable,,Supported by a federation,The Bluebell Federation,10074065.0,,Not applicable,11-10-2023,03-06-2024,Barberry Avenue,,,Chatham,Kent,ME5 9TF,www.horstedschool.co.uk/,1634335400.0,Mrs,Sarah,Steer,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Horsted,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574928.0,164210.0,Medway 033,Medway 033C,,,,,Good,South-East England and South London,,200000909501.0,,Not applicable,Not applicable,,,E02003346,E01016123,18.0,
|
||||
118472,887,Medway,2494,Parkwood Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,540.0,No Special Classes,19-01-2023,546.0,270.0,276.0,16.8,Not applicable,,Not applicable,,Not under a federation,,10074063.0,,Not applicable,08-06-2023,07-05-2024,Deanwood Drive,Rainham,,Gillingham,Kent,ME8 9LP,www.parkwoodprimary.org.uk,1634234699.0,Headteacher,Lee,McCormack,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South East,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,581085.0,164262.0,Medway 036,Medway 036D,,,,,Requires improvement,South-East England and South London,,100062396933.0,,Not applicable,Not applicable,,,E02003349,E01016105,92.0,
|
||||
118477,887,Medway,2506,Horsted Junior School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,248.0,No Special Classes,19-01-2023,249.0,130.0,119.0,16.5,Not applicable,,Not applicable,,Supported by a federation,The Bluebell Federation,10079031.0,,Not applicable,13-09-2023,03-06-2024,Barberry Avenue,,,Chatham,Kent,ME5 9TF,www.horstedschool.co.uk,1634335400.0,Mrs,Sarah,Steer,Executive Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Horsted,Rochester and Strood,(England/Wales) Urban city and town,E06000035,575006.0,164184.0,Medway 033,Medway 033C,,,,,Good,South-East England and South London,,44018867.0,,Not applicable,Not applicable,,,E02003346,E01016123,41.0,
|
||||
118509,887,Medway,2549,Swingate Primary School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,659.0,335.0,324.0,12.0,Not applicable,,Not applicable,,Not under a federation,,10076224.0,,Not applicable,08-11-2018,21-05-2024,Sultan Road,Lordswood,,Chatham,Kent,ME5 8TJ,www.swingate.medway.sch.uk/,1634863778.0,Mr,Steven,Geary,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Lordswood & Walderslade,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,577377.0,162361.0,Medway 038,Medway 038B,,,,,Good,South-East England and South London,,44058696.0,,Not applicable,Not applicable,,,E02003351,E01016056,76.0,
|
||||
118555,887,Medway,2638,Hempstead Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,270.0,149.0,121.0,5.2,Not applicable,,Not applicable,,Not under a federation,,10074059.0,,Not applicable,13-03-2024,22-05-2024,Hempstead Road,Hempstead,,Gillingham,Kent,ME7 3QG,www.hempsteadschoolsfederation.org.uk,1634336963.0,Mr,Paul,Cross,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Hempstead & Wigmore,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579161.0,164155.0,Medway 035,Medway 035B,,,,,Good,South-East England and South London,,200000901137.0,,Not applicable,Not applicable,,,E02003348,E01016051,14.0,
|
||||
118576,887,Medway,2665,St Peter's Infant School,Community school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,100.0,No Special Classes,19-01-2023,94.0,53.0,41.0,32.3,Not applicable,,Not applicable,,Not under a federation,,10074058.0,,Not applicable,13-12-2018,02-06-2024,Holcombe Road,,,Rochester,Kent,ME1 2HU,http://www.stpetersinfants.co.uk/,1634843590.0,Mrs,Joanna,Worrall (Interim),Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574398.0,167163.0,Medway 017,Medway 017D,,,,,Good,South-East England and South London,,44025555.0,,Not applicable,Not applicable,,,E02003330,E01016118,30.0,
|
||||
118641,887,Medway,3096,"St Helen's Church of England Primary School, Cliffe",Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,211.0,107.0,104.0,18.5,Not applicable,,Not applicable,,Not under a federation,,10069109.0,,Not applicable,04-06-2019,23-04-2024,Church Street,Cliffe,,Rochester,Kent,ME3 7PU,www.sthelens.medway.sch.uk/,1634220246.0,Mrs,Stephanie,Jarvis,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood Rural,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,573629.0,176192.0,Medway 002,Medway 002A,,,,,Good,South-East England and South London,,44018290.0,,Not applicable,Not applicable,,,E02003315,E01016142,39.0,
|
||||
118643,887,Medway,3102,St Nicholas CEVC Primary School,Voluntary controlled school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,120.0,No Special Classes,19-01-2023,93.0,46.0,47.0,28.0,Not applicable,,Not applicable,,Not under a federation,,10079669.0,,Not applicable,23-01-2013,26-04-2024,St Nicholas CEVC primary School,London Road,Strood,Rochester,Kent,ME2 3HU,http://www.st-nicholas.medway.sch.uk,1634717120.0,Mrs,Ruth,Gooch,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood North & Frindsbury,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573257.0,169332.0,Medway 006,Medway 006E,,,,,Outstanding,South-East England and South London,,200000896704.0,,Not applicable,Not applicable,,,E02003319,E01016141,26.0,
|
||||
118756,887,Medway,3712,St Michael's RC Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,460.0,No Special Classes,19-01-2023,469.0,236.0,233.0,25.6,Not applicable,,Not applicable,,Not under a federation,,10076805.0,,Not applicable,06-11-2019,08-05-2024,Hills Terrace,,,Chatham,Kent,ME4 6PX,www.stmichaelsrcp.org,1634832578.0,Mrs,Nicola,Collins,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575579.0,167357.0,Medway 015,Medway 015B,,,,,Good,South-East England and South London,,44040971.0,,Not applicable,Not applicable,,,E02003328,E01016017,107.0,
|
||||
118766,887,Medway,3729,English Martyrs' Catholic Primary School,Voluntary aided school,Local authority maintained schools,"Open, but proposed to close",Not applicable,,Academy Converter,30-06-2024,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,202.0,101.0,101.0,15.8,Not applicable,,Not applicable,,Not under a federation,,10072742.0,,Not applicable,13-07-2023,28-05-2024,Frindsbury Road,Strood,,Rochester,Kent,ME2 4JA,http://www.englishmartyrs.medway.sch.uk,1634718964.0,Ms,Catherine,Thacker,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood North & Frindsbury,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573987.0,169894.0,Medway 006,Medway 006A,,,,,Good,South-East England and South London,,100062388690.0,,Not applicable,Not applicable,,,E02003319,E01016135,32.0,
|
||||
118767,887,Medway,3732,St Thomas of Canterbury RC Primary School,Voluntary aided school,Local authority maintained schools,"Open, but proposed to close",Not applicable,,Academy Converter,30-06-2024,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,236.0,No Special Classes,19-01-2023,258.0,125.0,133.0,12.1,Not applicable,,Not applicable,,Not under a federation,,10072741.0,,Not applicable,08-06-2023,28-05-2024,Romany Road,Rainham,,Gillingham,Kent,ME8 6JH,www.stthomascanterbury.org.uk/,1634234677.0,Mrs,Vicki-Louise,Gallagher,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Twydall,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579863.0,166652.0,Medway 018,Medway 018F,,,,,Good,South-East England and South London,,44087220.0,,Not applicable,Not applicable,,,E02003331,E01016165,29.0,
|
||||
118769,887,Medway,3736,St Thomas More Roman Catholic Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,420.0,No Special Classes,19-01-2023,417.0,188.0,229.0,11.3,Not applicable,,Not applicable,,Not under a federation,,10072739.0,,Not applicable,08-02-2013,09-02-2024,Bleakwood Road,,,Chatham,Kent,ME5 0NF,www.st-thomasmore.medway.sch.uk/,1634864701.0,Mrs,Victoria,Ebdon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Wayfield & Weeds Wood,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576043.0,164020.0,Medway 033,Medway 033D,,,,,Outstanding,South-East England and South London,,44045642.0,,Not applicable,Not applicable,,,E02003346,E01016173,47.0,
|
||||
118775,887,Medway,3746,St William of Perth Roman Catholic Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,208.0,106.0,102.0,7.7,Not applicable,,Not applicable,,Not under a federation,,10072738.0,,Not applicable,18-10-2023,10-05-2024,Canon Close,Maidstone Road,,Rochester,Kent,ME1 3EN,www.stwilliamofperth.org.uk/,1634404267.0,Mr,James,Willis,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573973.0,166819.0,Medway 024,Medway 024D,,,,,Good,South-East England and South London,,44010893.0,,Not applicable,Not applicable,,,E02003337,E01016132,16.0,
|
||||
118779,887,Medway,3752,St Augustine of Canterbury Catholic Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,19-01-2023,199.0,101.0,98.0,13.6,Not applicable,,Not applicable,,Not under a federation,,10072736.0,,Not applicable,07-02-2024,20-05-2024,Deanwood Drive,Rainham,,Gillingham,Kent,ME8 9NP,www.staccp.org.uk/,1634371892.0,Mrs,Louise,Prestidge,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South East,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580898.0,163428.0,Medway 036,Medway 036C,,,,,Good,South-East England and South London,,200000901485.0,,Not applicable,Not applicable,,,E02003349,E01016100,27.0,
|
||||
118782,887,Medway,3755,St Mary's Catholic Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,480.0,No Special Classes,19-01-2023,433.0,235.0,198.0,15.6,Not applicable,,Not applicable,,Not under a federation,,10072734.0,,Not applicable,26-04-2023,11-03-2024,Greenfield Road,,Https://Stmarysrcp.Medway.Sch.Uk/,Gillingham,Kent,ME7 1YH,www.stmarysrcp.medway.sch.uk/,1634855783.0,Mr,Joseph,Pomeroy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Gillingham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,577917.0,168572.0,Medway 010,Medway 010B,,,,,Good,South-East England and South London,,44069382.0,,Not applicable,Not applicable,,,E02003323,E01016032,61.0,
|
||||
118908,887,Medway,5436,St John Fisher Catholic Comprehensive School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,,Not applicable,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Non-selective,1185.0,No Special Classes,19-01-2023,1024.0,550.0,474.0,30.1,Not applicable,,Not applicable,,Not under a federation,,10006189.0,,Not applicable,22-05-2019,23-05-2024,City Way,,,Rochester,,ME1 2FA,http://www.stjohnfisher.school,1634543123.0,Mrs,Dympna,Lennon,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575287.0,167305.0,Medway 021,Medway 021A,,,,,Good,South-East England and South London,United Kingdom,44082945.0,,Not applicable,Not applicable,,,E02003334,E01016020,262.0,
|
||||
118948,887,Medway,6000,"King's School, Rochester",Other independent school,Independent schools,Open,Not applicable,01-01-1909,Not applicable,,Not applicable,3.0,18,Boarding school,Has Nursery Classes,Has a sixth form,Mixed,Church of England,Church of England,Not applicable,Non-selective,800.0,No Special Classes,20-01-2022,660.0,384.0,276.0,0.0,Not applicable,,Not applicable,,Not applicable,,10003660.0,,Not applicable,,06-06-2024,Satis House,Boley Hill,,Rochester,Kent,ME1 1TE,www.kings-rochester.co.uk,1634888555.0,Mr,Benjamin,Charles,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574095.0,168504.0,Medway 014,Medway 014C,ISI,4.0,89.0,The Governors of King's School,,South-East England and South London,,44026826.0,,Not applicable,Not applicable,,,E02003327,E01016130,0.0,
|
||||
118979,887,Medway,6001,Bryony School,Other independent school,Independent schools,Open,Not applicable,02-04-1958,Not applicable,,Not applicable,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Non-selective,232.0,No Special Classes,20-01-2022,128.0,60.0,68.0,0.0,Not applicable,,Not applicable,,Not applicable,,10071117.0,,Not applicable,24-11-2022,28-05-2024,157 Marshall Road,Rainham,Marshall Road,Rainham,,ME8 0AJ,www.bryonyschool.org.uk,1634231511.0,Mrs,Natalie,Gee,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South West,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580169.0,165278.0,Medway 030,Medway 030C,Ofsted,,1.0,Mr & Mrs Edmunds,Good,South-East England and South London,,100061264465.0,,Not applicable,Not applicable,,,E02003343,E01016091,0.0,
|
||||
118985,887,Medway,6002,St Andrew's School (Rochester),Other independent school,Independent schools,Open,Not applicable,18-03-1958,Not applicable,,Not applicable,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,360.0,No Special Classes,20-01-2022,362.0,172.0,190.0,0.0,Not applicable,,Not applicable,,Not applicable,,10080533.0,,Not applicable,,24-05-2024,24 - 26 Watts Avenue,,,Rochester,Kent,ME1 1SA,www.st-andrews.rochester.sch.uk,1634843479.0,Mrs,Emma,Steinmann-Gilbert,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574044.0,167996.0,Medway 014,Medway 014C,ISI,,8.0,Education Development Trust,,South-East England and South London,,44022882.0,,Not applicable,Not applicable,,,E02003327,E01016130,0.0,
|
||||
119006,887,Medway,6004,Rochester Independent College,Other independent school,Independent schools,Open,Not applicable,26-09-1986,Not applicable,,Not applicable,11.0,21,Boarding school,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,380.0,No Special Classes,20-01-2022,358.0,197.0,161.0,0.0,Not applicable,,Not applicable,,Not applicable,,10005511.0,,Not applicable,,21-03-2024,254 St Margaret's Banks,,,Rochester,Kent,ME1 1HY,www.rochester-college.org.uk,1634828115.0,Mr,Alistair,Brownlow,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not approved,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574653.0,167978.0,Medway 015,Medway 015F,ISI,43.0,102.0,Dukes Education,,South-East England and South London,United Kingdom,100062373676.0,,Not applicable,Not applicable,,,E02003328,E01035296,0.0,
|
||||
131527,887,Medway,3760,Burnt Oak Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,01-09-2006,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,453.0,238.0,215.0,40.6,Not applicable,,Not applicable,,Not under a federation,,10075241.0,,Not applicable,18-05-2022,20-05-2024,Richmond Road,,,Gillingham,Kent,ME7 1LS,www.burntoak.medway.sch.uk/,1634334344.0,Mrs,Maureen,Grabski,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Gillingham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,577499.0,169057.0,Medway 009,Medway 009C,,,,,Good,South-East England and South London,,44027271.0,,Not applicable,Not applicable,,,E02003322,E01016037,183.0,
|
||||
132056,887,Medway,3756,St Mary's Island Church of England (Aided) Primary School,Voluntary aided school,Local authority maintained schools,Open,Not applicable,01-09-1999,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,630.0,Not applicable,19-01-2023,671.0,329.0,342.0,10.3,Not applicable,,Not applicable,,Not under a federation,,10075517.0,,Not applicable,15-11-2023,23-04-2024,Island Way West,St Mary's Island,,Chatham,Kent,ME4 3ST,http://www.st-marys-island-cofe-primary-school.co.uk,1634891050.0,Mrs,Christine,Easton,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Medway,St Mary's Island,Rochester and Strood,(England/Wales) Urban city and town,E06000035,576620.0,170684.0,Medway 007,Medway 007F,,,,,Good,South-East England and South London,,200003623259.0,,Not applicable,Not applicable,,,E02003320,E01035290,69.0,
|
||||
134904,887,Medway,3759,Fairview Community Primary School,Community school,Local authority maintained schools,Open,Result of Amalgamation,12-09-2005,Not applicable,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,690.0,Not applicable,19-01-2023,668.0,340.0,328.0,7.0,Not applicable,,Not applicable,,Not under a federation,,10071739.0,,Not applicable,02-04-2019,21-05-2024,Drewery Drive,Wigmore,,Gillingham,Kent,ME8 0NU,www.fairviewprimary.co.uk,1634338710.0,Mrs,Karin,Tillett,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Hempstead & Wigmore,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580272.0,164185.0,Medway 030,Medway 030A,,,,,Good,South-East England and South London,,44013431.0,,Not applicable,Not applicable,,,E02003343,E01016085,44.0,
|
||||
135964,887,Medway,6905,Strood Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2009,Not applicable,,Secondary,11.0,19,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1500.0,No Special Classes,19-01-2023,1273.0,626.0,647.0,26.8,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10027834.0,,Not applicable,02-12-2021,17-05-2024,Carnation Road,Strood,,Rochester,Kent,ME2 2SX,http://www.stroodacademy.org.uk,1634717121.0,Mr,Jon,Richardson,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision and SEN unit,5.0,25.0,5.0,25.0,South East,Medway,Strood West,Rochester and Strood,(England/Wales) Urban city and town,E06000035,571638.0,169261.0,Medway 008,Medway 008D,,,,,Good,South-East England and South London,,100062388557.0,,Not applicable,Not applicable,,,E02003321,E01016155,310.0,
|
||||
136107,887,Medway,6906,Brompton Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2010,Not applicable,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,Does not apply,None,Not applicable,Non-selective,1400.0,Has Special Classes,19-01-2023,1403.0,736.0,667.0,30.4,Supported by a multi-academy trust,THE UNIVERSITY OF KENT ACADEMIES TRUST,Linked to a sponsor,University of Kent (Brompton Academy),Not applicable,,10030223.0,,Not applicable,22-09-2022,21-05-2024,Marlborough Road,,,Gillingham,Kent,ME7 5HT,http://www.bromptonacademy.org.uk,1634852341.0,Mr,Dan,Walters,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,SpLD - Specific Learning Difficulty,OTH - Other Difficulty/Disability,"SLCN - Speech, language and Communication",,,,,,,,,,,SEN unit,,,94.0,100.0,South East,Medway,Gillingham South,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,576827.0,167905.0,Medway 012,Medway 012C,,,,,Requires improvement,South-East England and South London,,44054862.0,,Not applicable,Not applicable,,,E02003325,E01016045,366.0,
|
||||
136108,887,Medway,6907,The Victory Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2010,Not applicable,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,None,,Non-selective,1500.0,No Special Classes,19-01-2023,1188.0,598.0,590.0,40.1,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10030225.0,,Not applicable,03-02-2023,07-06-2024,Magpie Hall Road,,,Chatham,Kent,ME4 5JB,http://www.thevictoryacademy.org.uk/,3333602140.0,Mr,Oliver,Owen,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Luton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576368.0,166387.0,Medway 022,Medway 022D,,,,,Good,South-East England and South London,,44051900.0,,Not applicable,Not applicable,,,E02003335,E01016062,428.0,
|
||||
136313,887,Medway,5445,The Rochester Grammar School,Academy converter,Academies,Open,Academy Converter,01-11-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,1110.0,No Special Classes,19-01-2023,1182.0,9.0,1173.0,7.0,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10032200.0,,Not applicable,18-01-2023,21-05-2024,Maidstone Road,,,Rochester,Kent,ME1 3BY,http://www.rochestergrammar.org.uk,3333602120.0,Mrs,Clare,Brinklow,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574171.0,166566.0,Medway 017,Medway 017C,,,,,Good,South-East England and South London,,44022498.0,,Not applicable,Not applicable,,,E02003330,E01016115,70.0,
|
||||
136337,887,Medway,4069,Fort Pitt Grammar School,Academy converter,Academies,Open,Academy Converter,01-11-2010,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,800.0,No Special Classes,19-01-2023,895.0,20.0,875.0,11.6,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10032192.0,,Not applicable,05-10-2022,05-03-2024,Fort Pitt Hill,,,Chatham,Kent,ME4 6TJ,http://www.fortpitt.medway.sch.uk,1634842359.0,Ms,Salena,Hirons,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Rochester and Strood,(England/Wales) Urban city and town,E06000035,575049.0,167596.0,Medway 015,Medway 015G,,,,,Outstanding,South-East England and South London,,100062392022.0,,Not applicable,Not applicable,,,E02003328,E01035297,83.0,
|
||||
136456,887,Medway,4199,Rainham School for Girls,Academy converter,Academies,Open,Academy Converter,01-02-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,Does not apply,Does not apply,Not applicable,Non-selective,1558.0,No Special Classes,19-01-2023,1668.0,36.0,1632.0,17.6,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10032961.0,,Not applicable,21-04-2022,20-05-2024,Derwent Way,Rainham,,Gillingham,Kent,ME8 0BX,http://www.rainhamgirls-tkat.org/,1634362746.0,Mrs,Vicki,Shaw,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South West,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580560.0,165785.0,Medway 029,Medway 029B,,,,,Good,South-East England and South London,,100062395978.0,,Not applicable,Not applicable,,,E02003342,E01016088,243.0,
|
||||
136594,887,Medway,4068,Holcombe Grammar School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,Does not apply,Does not apply,Not applicable,Selective,1000.0,No Special Classes,19-01-2023,1067.0,955.0,112.0,9.9,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10033242.0,,Not applicable,24-04-2018,07-06-2024,Holcombe,Maidstone Road,,Chatham,Kent,ME4 6JB,http://www.holcombegrammar.org.uk,3333602130.0,Mr,Lee,Preston,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575486.0,166035.0,Medway 027,Medway 027A,,,,,Good,South-East England and South London,,100062392018.0,,Not applicable,Not applicable,,,E02003340,E01016025,72.0,
|
||||
136662,887,Medway,4530,Sir Joseph Williamson's Mathematical School,Academy converter,Academies,Open,Academy Converter,01-04-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Boys,None,Does not apply,Not applicable,Selective,1280.0,No Special Classes,19-01-2023,1479.0,1386.0,93.0,8.7,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10033431.0,,Not applicable,22-03-2023,07-06-2024,Maidstone Road,,,Rochester,Kent,ME1 3EL,http://www.sjwms.org.uk,1634844008.0,Mr,Eliot,Hodges,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574020.0,166430.0,Medway 024,Medway 024C,,,,,Outstanding,South-East England and South London,,44022499.0,,Not applicable,Not applicable,,,E02003337,E01016128,92.0,
|
||||
136859,887,Medway,2588,Cliffe Woods Primary School,Academy converter,Academies,Open,Academy Converter,01-07-2011,,,Primary,4.0,11,No boarders,No Nursery Classes,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,415.0,197.0,218.0,5.1,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10034067.0,,Not applicable,18-03-2015,07-05-2024,View Road,Cliffe Woods,,Rochester,Kent,ME3 8UJ,www.cliffewoods.medway.sch.uk,1634220822.0,Mrs,Karen,Connolly,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood Rural,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,574188.0,173690.0,Medway 002,Medway 002C,,,,,Outstanding,South-East England and South London,,44030622.0,,Not applicable,Not applicable,,,E02003315,E01016145,21.0,
|
||||
136864,887,Medway,5420,Rainham Mark Grammar School,Academy converter,Academies,Open,Academy Converter,01-07-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Selective,1242.0,No Special Classes,19-01-2023,1567.0,839.0,728.0,7.3,Supported by a multi-academy trust,RMET,Linked to a sponsor,RMET (Rainham Mark Education Trust),Not applicable,,10034121.0,,Not applicable,25-05-2022,29-05-2024,Pump Lane,Rainham,,Gillingham,Kent,ME8 7AJ,http://rainhammark.com/,1634364151.0,Mrs,Agnes,Hart,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580729.0,166755.0,Medway 023,Medway 023C,,,,,Good,South-East England and South London,,44096394.0,,Not applicable,Not applicable,,,E02003336,E01016166,88.0,
|
||||
137119,887,Medway,4000,The Hundred of Hoo Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2011,Not applicable,,All-through,4.0,19,No boarders,Not applicable,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1900.0,Not applicable,19-01-2023,1702.0,844.0,858.0,22.3,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10034985.0,,Not applicable,05-07-2018,08-05-2024,Main Road,Hoo,,Rochester,Kent,ME3 9HH,https://www.hundredofhooacademy.org.uk/,1634251443.0,Mr,Carl,Guerin-Hassett,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,SEN unit,,,43.0,48.0,South East,Medway,Hoo St Werburgh & High Halstow,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,577407.0,172277.0,Medway 003,Medway 003D,,,,,Good,South-East England and South London,,200000900015.0,,Not applicable,Not applicable,,,E02003316,E01016077,353.0,
|
||||
137376,887,Medway,5451,The Thomas Aveling School,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,Does not apply,Not applicable,Non-selective,1204.0,No Special Classes,19-01-2023,1205.0,599.0,606.0,25.1,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10035084.0,,Not applicable,14-09-2022,14-05-2024,Arethusa Road,,,Rochester,Kent,ME1 2UW,http://www.thomasaveling.co.uk,1634844809.0,Mr,Paul,Jackson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,,,,,,,,,,,,,Resourced provision,20.0,20.0,,,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574327.0,165761.0,Medway 026,Medway 026B,,,,,Good,South-East England and South London,,44018933.0,,Not applicable,Not applicable,,,E02003339,E01016120,259.0,
|
||||
137389,887,Medway,5429,Chatham Grammar,Academy converter,Academies,Open,Academy Converter,01-09-2011,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Girls,None,Does not apply,Not applicable,Selective,976.0,No Special Classes,19-01-2023,945.0,18.0,927.0,10.9,Supported by a multi-academy trust,THE UNIVERSITY OF KENT ACADEMIES TRUST,Linked to a sponsor,University of Kent (Brompton Academy),Not applicable,,10035160.0,,Not applicable,18-10-2023,22-05-2024,Rainham Road,,,Chatham,Kent,ME5 7EH,http://www.chathamgirlsgrammar.medway.sch.uk/,1634851262.0,Ms,Wendy,Walters,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Luton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,577460.0,166933.0,Medway 020,Medway 020A,,,,,Good,South-East England and South London,,200000899518.0,,Not applicable,Not applicable,,,E02003333,E01016061,80.0,
|
||||
137990,887,Medway,2421,High Halstow Primary Academy,Academy converter,Academies,Open,Academy Converter,01-04-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,211.0,111.0,100.0,9.5,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10036980.0,,Not applicable,21-06-2023,14-03-2024,Harrison Drive,High Halstow,,Rochester,Kent,ME3 8TF,http://www.highhalstowprimaryacademy.org.uk,1634251098.0,Mrs,Gemma,Stangroom,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Hoo St Werburgh & High Halstow,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,578093.0,175447.0,Medway 003,Medway 003A,,,,,Outstanding,South-East England and South London,,44083531.0,,Not applicable,Not applicable,,,E02003316,E01016073,20.0,
|
||||
138182,887,Medway,2600,All Faiths Children's Academy,Academy converter,Academies,Open,Academy Converter,01-06-2012,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,Has Special Classes,19-01-2023,226.0,101.0,125.0,28.4,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10037460.0,,Not applicable,29-01-2020,23-04-2024,Gun Lane,Strood,,Rochester,Kent,ME2 4UF,www.allfaithschildrensacademy.org.uk,3333602100.0,Mrs,Kirstie,Jones,Acting Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,HI - Hearing Impairment,,,,,,,,,,,,,Resourced provision and SEN unit,15.0,15.0,15.0,15.0,South East,Medway,Strood North & Frindsbury,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573564.0,169506.0,Medway 006,Medway 006E,,,,,Good,South-East England and South London,,200000896787.0,,Not applicable,Not applicable,,,E02003319,E01016141,64.0,
|
||||
138328,887,Medway,2209,Chattenden Primary School,Academy converter,Academies,Open,Academy Converter,01-07-2012,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,No Special Classes,19-01-2023,209.0,101.0,108.0,34.8,Supported by a multi-academy trust,PENINSULA GATEWAY ACADEMY TRUST,-,,Not applicable,,10037769.0,,Not applicable,06-11-2018,03-05-2024,Chattenden Lane,Chattenden,,Rochester,Kent,ME3 8LF,http://www.chattenden.medway.sch.uk/,1634250861.0,Miss,Julie,North,Principal,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Hoo St Werburgh & High Halstow,Rochester and Strood,(England/Wales) Rural village,E06000035,575792.0,171843.0,Medway 003,Medway 003E,,,,,Good,South-East England and South London,,200000909509.0,,Not applicable,Not applicable,,,E02003316,E01016143,72.0,
|
||||
138510,887,Medway,2001,Phoenix Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2012,,,Primary,5.0,11,,Not applicable,Not applicable,Mixed,None,None,Not applicable,Not applicable,360.0,Not applicable,19-01-2023,359.0,179.0,180.0,49.3,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10038455.0,,Not applicable,21-06-2023,25-04-2024,Glencoe Road,,,Chatham,Kent,ME4 5QD,www.phoenixprimary.com,1634829009.0,Mrs,Melissa,Ireland-Hubbert,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Chatham Central & Brompton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576075.0,166910.0,Medway 022,Medway 022A,,,,,Good,South-East England and South London,,44051781.0,,Not applicable,Not applicable,,,E02003335,E01016018,176.0,
|
||||
138511,887,Medway,4001,The Robert Napier School,Academy sponsor led,Academies,Open,New Provision,01-09-2012,,,Secondary,11.0,18,No boarders,Not applicable,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1080.0,Not applicable,19-01-2023,1037.0,561.0,476.0,43.0,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10038456.0,,Not applicable,31-01-2019,21-05-2024,Third Avenue,,,Gillingham,Kent,ME7 2LX,http://www.robertnapier.org.uk/,1634851157.0,Mrs,Jenny,Tomkins,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Medway,Watling,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,578331.0,167405.0,Medway 019,Medway 019D,,,,,Good,South-East England and South London,,44076749.0,,Not applicable,Not applicable,,,E02003332,E01016179,407.0,
|
||||
138974,887,Medway,2002,St James Church of England Primary Academy,Academy sponsor led,Academies,Open,New Provision,01-12-2012,,,Primary,3.0,11,,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,None,Diocese of Rochester,Not applicable,210.0,Not applicable,19-01-2023,199.0,104.0,95.0,31.7,Supported by a multi-academy trust,MEDWAY ANGLICAN SCHOOLS TRUST,-,,Not applicable,,10039506.0,,Not applicable,24-05-2023,23-02-2024,High Street,Isle of Grain,,Rochester,Kent,ME3 0BS,http://www.stjamesisleofgrain.org.uk,1634270341.0,Miss,Fay,Cordingley,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,All Saints,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,588751.0,176727.0,Medway 001,Medway 001B,,,,,Good,South-East England and South London,,44004798.0,,Not applicable,Not applicable,,,E02003314,E01016071,58.0,
|
||||
139493,887,Medway,2412,The Academy of Woodlands,Academy converter,Academies,Open,Academy Converter,01-04-2013,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,758.0,386.0,372.0,32.1,Supported by a multi-academy trust,RIVERMEAD INCLUSIVE TRUST,Linked to a sponsor,Rivermead Inclusive Trust,Not applicable,,10041035.0,,Not applicable,11-12-2019,01-05-2024,Woodlands Road,,,Gillingham,Kent,ME7 2DU,www.theacademyofwoodlands.co.uk,3000658200.0,Mrs,Chloe,Brown,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Watling,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,578807.0,167818.0,Medway 013,Medway 013C,,,,,Good,South-East England and South London,,100062394529.0,,Not applicable,Not applicable,,,E02003326,E01016043,243.0,
|
||||
139927,887,Medway,2003,Kingfisher Community Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,219.0,103.0,116.0,40.6,Supported by a multi-academy trust,THE GRIFFIN SCHOOLS TRUST,Linked to a sponsor,The Griffin Schools Trust,Not applicable,,10042689.0,,Not applicable,12-09-2019,28-05-2024,Kingfisher Drive,Princes Park,Walderslade,Chatham,Kent,ME5 7NX,www.kingfisher-gst.org,1634661540.0,Ms,Fiona,Armstrong,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Princes Park,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,577072.0,165267.0,Medway 031,Medway 031E,,,,,Good,South-East England and South London,,100062392674.0,,Not applicable,Not applicable,,,E02003344,E01016084,89.0,
|
||||
139928,887,Medway,2004,Saxon Way Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,425.0,229.0,196.0,47.2,Supported by a multi-academy trust,THE GRIFFIN SCHOOLS TRUST,Linked to a sponsor,The Griffin Schools Trust,Not applicable,,10042702.0,,Not applicable,22-01-2020,04-06-2024,,Ingram Road,,Gillingham,Kent,ME7 1SJ,http://www.saxonway-gst.org/,1634336720.0,Mrs,Jennifer,Vidler-Ironmonger,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Gillingham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,578169.0,168848.0,Medway 010,Medway 010B,,,,,Good,South-East England and South London,,200000900397.0,,Not applicable,Not applicable,,,E02003323,E01016032,200.0,
|
||||
140040,887,Medway,2006,Oasis Academy Skinner Street,Academy sponsor led,Academies,Open,New Provision,01-09-2013,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,410.0,198.0,212.0,51.7,Supported by a multi-academy trust,OASIS COMMUNITY LEARNING,Linked to a sponsor,Oasis Community Learning,Not applicable,,10042952.0,,Not applicable,22-09-2021,19-04-2024,Skinner Street,,,Gillingham,Kent,ME7 1LG,www.oasisacademyskinnerstreet.org/,1634850213.0,Mrs,Victoria,Richmond,Interim Principal,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Gillingham South,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,577257.0,168594.0,Medway 012,Medway 012A,,,,,Good,South-East England and South London,,100062394010.0,,Not applicable,Not applicable,,,E02003325,E01016031,212.0,
|
||||
140186,887,Medway,2007,Lordswood School,Academy sponsor led,Academies,Open,New Provision,01-11-2013,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,410.0,220.0,190.0,26.6,Supported by a multi-academy trust,THE GRIFFIN SCHOOLS TRUST,Linked to a sponsor,The Griffin Schools Trust,Not applicable,,10043317.0,,Not applicable,16-01-2019,28-05-2024,Lordswood Lane,,,Chatham,Kent,ME5 8NN,www.lordswood-gst.org,1634336767.0,Mrs,Jayne,Lusinski,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Lordswood & Walderslade,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576856.0,163264.0,Medway 038,Medway 038D,,,,,Good,South-East England and South London,,200000909500.0,,Not applicable,Not applicable,,,E02003351,E01016060,109.0,
|
||||
140215,887,Medway,2008,New Horizons Children's Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2014,,,Primary,4.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,,Not applicable,630.0,Not applicable,19-01-2023,639.0,318.0,321.0,19.4,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10047226.0,,Not applicable,25-05-2022,03-05-2024,Park Crescent,,,Chatham,Kent,ME4 6NR,http://www.newhorizonschildrensacademy.org.uk,3333602115.0,Mr,Cormac,Murphy,Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Chatham Central & Brompton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575624.0,165866.0,Medway 027,Medway 027A,,,,,Good,South-East England and South London,,44036212.0,,Not applicable,Not applicable,,,E02003340,E01016025,124.0,
|
||||
140606,887,Medway,2009,"Gordons Children's Academy, Junior",Academy sponsor led,Academies,Open,New Provision,01-03-2014,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,400.0,Not applicable,19-01-2023,336.0,180.0,156.0,22.9,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10044931.0,,Not applicable,28-09-2022,16-01-2024,Gordon Road,Strood,,Rochester,Kent,ME2 3HQ,https://www.gordonchildrensacademy.org.uk/,3333602110.0,Mrs,Kirstie,Jones,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood North & Frindsbury,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573169.0,169601.0,Medway 006,Medway 006E,,,,,Good,South-East England and South London,United Kingdom,100062388599.0,,Not applicable,Not applicable,,,E02003319,E01016141,77.0,
|
||||
140607,887,Medway,2010,"Gordons Children's Academy, Infant",Academy sponsor led,Academies,Open,New Provision,01-03-2014,,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,189.0,Not applicable,19-01-2023,164.0,83.0,81.0,25.0,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10044932.0,,Not applicable,28-09-2022,16-01-2024,Gordon Road,Strood,,Rochester,Kent,ME2 3HQ,www.gordonchildrensacademy.org.uk,3333602110.0,Mrs,Kirstie,Jones,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood North & Frindsbury,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573169.0,169601.0,Medway 006,Medway 006E,,,,,Good,South-East England and South London,,100062388599.0,,Not applicable,Not applicable,,,E02003319,E01016141,41.0,
|
||||
140989,887,Medway,2011,Warren Wood Primary School,Academy sponsor led,Academies,Open,New Provision,01-07-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Not applicable,411.0,Has Special Classes,19-01-2023,486.0,258.0,228.0,37.8,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10046284.0,,Not applicable,16-10-2019,16-04-2024,Arethusa Road,,,Rochester,Kent,ME1 2UR,www.warrenwoodprimary.co.uk,1634401401.0,Mrs,Lucinda,Woodroof,Acting Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,"SLCN - Speech, language and Communication",,,,,,,,,,,,,SEN unit,,,30.0,34.0,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574546.0,165725.0,Medway 026,Medway 026B,,,,,Good,South-East England and South London,,44020102.0,,Not applicable,Not applicable,,,E02003339,E01016120,178.0,
|
||||
141199,887,Medway,2012,Napier Community Primary and Nursery Academy,Academy sponsor led,Academies,Open,New Provision,01-09-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,585.0,282.0,303.0,34.9,Supported by a multi-academy trust,THE KEMNAL ACADEMIES TRUST,Linked to a sponsor,The Kemnal Academies Trust,Not applicable,,10046908.0,,Not applicable,04-12-2019,11-04-2024,Napier Community Primary and Nursery Academy,Napier Road,,Gillingham,Kent,ME7 4HG,www.napierprimary.org.uk,1634574920.0,Mr,Ciaran,Mc Cann,Executive Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Gillingham South,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,577675.0,167833.0,Medway 016,Medway 016B,,,,,Good,South-East England and South London,,200000909503.0,,Not applicable,Not applicable,,,E02003329,E01016046,204.0,
|
||||
141224,887,Medway,2013,Cuxton Community Junior School,Academy sponsor led,Academies,Open,New Provision,01-09-2014,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,240.0,Not applicable,19-01-2023,228.0,112.0,116.0,14.9,Supported by a multi-academy trust,THE PRIMARY FIRST TRUST,Linked to a sponsor,The Primary First Trust,Not applicable,,10047013.0,,Not applicable,07-07-2022,21-05-2024,Bush Road,Cuxton,,Rochester,Kent,ME2 1EY,www.cuxtonschools.co.uk,1634337720.0,Mrs,Charlotte,Aldham Breary,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,"Cuxton, Halling & Riverside",Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,570725.0,166676.0,Medway 028,Medway 028C,,,,,Good,South-East England and South London,,44027917.0,,Not applicable,Not applicable,,,E02003341,E01016028,34.0,
|
||||
141276,887,Medway,2208,Cuxton Community Infant School,Academy converter,Academies,Open,Academy Converter,01-09-2014,,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,180.0,No Special Classes,19-01-2023,180.0,97.0,83.0,8.3,Supported by a multi-academy trust,THE PRIMARY FIRST TRUST,Linked to a sponsor,The Primary First Trust,Not applicable,,10047167.0,,Not applicable,30-03-2022,21-05-2024,Bush Road,Cuxton,,Rochester,Kent,ME2 1EY,www.cuxtonschools.co.uk,1634337720.0,Mrs,Charlotte,Aldham Breary,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,"Cuxton, Halling & Riverside",Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,570781.0,166686.0,Medway 028,Medway 028C,,,,,Good,South-East England and South London,,44027917.0,,Not applicable,Not applicable,,,E02003341,E01016028,15.0,
|
||||
141466,887,Medway,5457,The Howard School,Academy converter,Academies,Open,Academy Converter,01-10-2014,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,Not applicable,Non-selective,1725.0,No Special Classes,19-01-2023,1508.0,1486.0,22.0,15.3,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10047641.0,,Not applicable,24-11-2021,30-04-2024,Derwent Way,Rainham,,Gillingham,Kent,ME8 0BX,https://www.thehoward-that.org.uk/,1634388765.0,Mr,Jasbinder,Johal,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South West,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580562.0,165928.0,Medway 029,Medway 029B,,,,,Good,South-East England and South London,,100062395978.0,,Not applicable,Not applicable,,,E02003342,E01016088,193.0,
|
||||
141467,887,Medway,2646,Brompton-Westbrook Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,450.0,227.0,223.0,33.5,Supported by a multi-academy trust,THE WESTBROOK TRUST,Linked to a sponsor,The Westbrook Trust,Not applicable,,10047618.0,,Not applicable,22-01-2019,21-05-2024,Kings Bastion,Brompton,,Gillingham,Kent,ME7 5DQ,http://www.bromptonwestbrook.medway.sch.uk,1634844152.0,Mrs,Sue,Mason,Head Teacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Medway,Chatham Central & Brompton,Rochester and Strood,(England/Wales) Urban city and town,E06000035,576422.0,168177.0,Medway 015,Medway 015E,,,,,Good,South-East England and South London,,44056069.0,,Not applicable,Not applicable,,,E02003328,E01016111,146.0,
|
||||
141553,887,Medway,2194,Peninsula East Primary Academy,Academy converter,Academies,Open,Academy Converter,01-11-2014,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,250.0,No Special Classes,19-01-2023,248.0,127.0,121.0,26.9,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10047879.0,,Not applicable,15-01-2020,23-05-2024,Avery Way,Allhallows,,Rochester,,ME3 9HR,www.pepa.org.uk,1634270428.0,Mrs,Lorna,Rimmer,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Medway,All Saints,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,583168.0,176220.0,Medway 001,Medway 001A,,,,,Good,South-East England and South London,United Kingdom,44108207.0,,Not applicable,Not applicable,,,E02003314,E01016070,66.0,
|
||||
142137,887,Medway,3093,All Saints Church of England Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2015,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,379.0,No Special Classes,19-01-2023,353.0,176.0,177.0,33.2,Supported by a multi-academy trust,MEDWAY ANGLICAN SCHOOLS TRUST,-,,Not applicable,,10053760.0,,Not applicable,21-02-2024,20-05-2024,Magpie Hall Road,,,Chatham,Kent,ME4 5JY,http://www.allsaints.medway.sch.uk,1634338922.0,,Joanne,Strachan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Chatham Central & Brompton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576479.0,167253.0,Medway 015,Medway 015C,,,,,Good,South-East England and South London,,44054186.0,,Not applicable,Not applicable,,,E02003328,E01016019,111.0,
|
||||
142157,887,Medway,3095,St John's Church of England Infant School,Academy converter,Academies,Open,Academy Converter,01-09-2015,,,Primary,5.0,7,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,90.0,No Special Classes,19-01-2023,46.0,21.0,25.0,34.8,Supported by a multi-academy trust,MEDWAY ANGLICAN SCHOOLS TRUST,-,,Not applicable,,10053759.0,,Not applicable,13-06-2018,12-04-2024,4 New Street,,,Chatham,Kent,ME4 6RH,https://www.stjohns.medway.sch.uk/,1634844135.0,Miss,J,Strachan,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575450.0,167393.0,Medway 015,Medway 015B,,,,,Good,South-East England and South London,,44029362.0,,Not applicable,Not applicable,,,E02003328,E01016017,16.0,
|
||||
142160,887,Medway,3195,St Margaret's Church of England Junior School,Academy converter,Academies,Open,Academy Converter,01-09-2015,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,363.0,No Special Classes,19-01-2023,364.0,189.0,175.0,19.0,Supported by a multi-academy trust,MEDWAY ANGLICAN SCHOOLS TRUST,-,,Not applicable,,10053758.0,,Not applicable,21-09-2023,26-03-2024,Orchard Street,Rainham,,Gillingham,Kent,ME8 9AE,http://www.stmargaretsonline.net,1634230998.0,Mr,Lenny,Williams,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South West,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,581405.0,165749.0,Medway 029,Medway 029C,,,,,Good,South-East England and South London,,200000901856.0,,Not applicable,Not applicable,,,E02003342,E01016089,69.0,
|
||||
142393,887,Medway,2014,Twydall Primary School and Nursery,Academy sponsor led,Academies,Open,New Provision,01-02-2016,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,255.0,Not applicable,19-01-2023,304.0,169.0,135.0,42.7,Supported by a multi-academy trust,RMET,Linked to a sponsor,RMET (Rainham Mark Education Trust),Not applicable,,10054150.0,,,17-05-2023,29-05-2024,Twydall Lane,,,Gillingham,Kent,ME8 6JS,www.twydallprimary.org.uk,1634231761.0,Mrs,Louise,Hardie,Headteacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,PD - Physical Disability,,,,,,,,,,,,,Resourced provision,7.0,8.0,,,South East,Medway,Twydall,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579908.0,166888.0,Medway 018,Medway 018B,,,,,Good,South-East England and South London,,44087223.0,,Not applicable,Not applicable,,,E02003331,E01016161,120.0,
|
||||
142394,887,Medway,2015,Temple Mill Primary School,Academy sponsor led,Academies,Open,New Provision,01-12-2015,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,247.0,126.0,121.0,21.7,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10054151.0,,,17-10-2018,11-04-2024,Cliffe Road,Strood,,Rochester,Kent,ME2 3NL,https://www.templemill-that.org.uk/,1634629079.0,Mrs,Lisa,Lewis,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood Rural,Rochester and Strood,(England/Wales) Urban city and town,E06000035,573642.0,170455.0,Medway 004,Medway 004C,,,,,Good,South-East England and South London,,100062388732.0,,Not applicable,Not applicable,,,E02003317,E01016148,51.0,
|
||||
142399,887,Medway,2016,Byron Primary School,Academy sponsor led,Academies,Open,New Provision,01-01-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,None,Not applicable,Not applicable,525.0,Not applicable,19-01-2023,520.0,251.0,269.0,27.5,Supported by a multi-academy trust,THE WESTBROOK TRUST,Linked to a sponsor,The Westbrook Trust,Not applicable,,10054156.0,,,15-06-2022,21-05-2024,Byron Road,,,Gillingham,Kent,ME7 5XX,,1634852981.0,Mr,Jon,Carthy,Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Gillingham South,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,577482.0,167330.0,Medway 016,Medway 016C,,,,,Requires improvement,South-East England and South London,,44067058.0,,Not applicable,Not applicable,,,E02003329,E01016047,143.0,
|
||||
142568,887,Medway,6010,The GFC School,Other independent school,Independent schools,Open,New Provision,07-12-2015,,,Not applicable,11.0,16,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,None,Not applicable,Selective,50.0,Not applicable,20-01-2022,28.0,20.0,8.0,0.0,Not applicable,,Not applicable,,Not applicable,,10066496.0,,,02-05-2019,15-04-2024,Priestfield Stadium,Redfern Avenue,,Gillingham,Kent,ME7 4DD,www.thegfcschool.co.uk,1634623420.0,Mrs,Sue,Wade,Head of School,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not approved,SpLD - Specific Learning Difficulty,"SEMH - Social, Emotional and Mental Health",MLD - Moderate Learning Difficulty,,,,,,,,,,,SEN unit,,,15.0,15.0,South East,Medway,Watling,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,578373.0,168139.0,Medway 013,Medway 013B,Ofsted,16.0,12.0,Paul Scally,Good,South-East England and South London,,44007001.0,,Not applicable,Not applicable,,,E02003326,E01016042,0.0,
|
||||
142817,887,Medway,2017,Cedar Children's Academy,Academy sponsor led,Academies,Open,New Provision,01-06-2016,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,630.0,Not applicable,19-01-2023,668.0,352.0,316.0,26.9,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10056531.0,,,09-05-2019,08-01-2024,Cedar Road,Strood,,Rochester,Kent,ME2 2JP,www.cedarchildrensacademy.org.uk,3333602105.0,Ms,Tracey,Baillie,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood West,Rochester and Strood,(England/Wales) Urban city and town,E06000035,572439.0,168467.0,Medway 011,Medway 011D,,,,,Good,South-East England and South London,,200000895826.0,,Not applicable,Not applicable,,,E02003324,E01016157,180.0,
|
||||
143261,887,Medway,2203,Walderslade Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2016,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,240.0,No Special Classes,19-01-2023,235.0,110.0,125.0,16.6,Supported by a multi-academy trust,RIVERMEAD INCLUSIVE TRUST,Linked to a sponsor,Rivermead Inclusive Trust,Not applicable,,10057635.0,,Not applicable,21-11-2018,30-04-2024,Dargets Road,Walderslade,,Chatham,Kent,ME5 8BJ,www.walderslade-pri.medway.sch.uk/,1634337766.0,Mrs,Amy,Rowley-Jones,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Lordswood & Walderslade,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576195.0,163447.0,Medway 037,Medway 037B,,,,,Good,South-East England and South London,,44045350.0,,Not applicable,Not applicable,,,E02003350,E01016169,39.0,
|
||||
143262,887,Medway,2213,Hoo St Werburgh Primary School and the Marlborough,Academy converter,Academies,Open,Academy Converter,01-09-2016,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,525.0,Has Special Classes,19-01-2023,573.0,308.0,265.0,19.9,Supported by a multi-academy trust,RIVERMEAD INCLUSIVE TRUST,Linked to a sponsor,Rivermead Inclusive Trust,Not applicable,,10057859.0,,Not applicable,06-03-2024,22-05-2024,Pottery Road,Hoo St Werburgh,,Rochester,Kent,ME3 9BS,www.hoo-st-werburgh.medway.sch.uk,1634338040.0,Mr,Simon,McLean,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,,Resourced provision and SEN unit,80.0,80.0,80.0,80.0,South East,Medway,Hoo St Werburgh & High Halstow,Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,577642.0,172283.0,Medway 003,Medway 003D,,,,,Good,South-East England and South London,,200000900025.0,,Not applicable,Not applicable,,,E02003316,E01016077,114.0,
|
||||
143458,887,Medway,2684,Deanwood Primary School,Academy converter,Academies,Open,Academy Converter,01-10-2016,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,210.0,Not applicable,19-01-2023,207.0,102.0,105.0,18.4,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10061378.0,,Not applicable,02-11-2018,23-05-2024,"Deanwood Primary School, Long Catlis Road",Parkwood,,Gillingham,Kent,ME8 9TX,https://www.deanwood-that.org.uk/,1634231901.0,Mrs,Jane,Wright,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South East,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,580615.0,163584.0,Medway 036,Medway 036B,,,,,Good,South-East England and South London,,200000909507.0,,Not applicable,Not applicable,,,E02003349,E01016099,38.0,
|
||||
143832,887,Medway,3758,The Pilgrim School (A Church of England Primary With Nursery),Academy converter,Academies,Open,Academy Converter,01-12-2016,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,Not applicable,19-01-2023,229.0,102.0,127.0,18.6,Supported by a multi-academy trust,THE PILGRIM MULTI ACADEMY TRUST,-,,Not applicable,,10062013.0,,Not applicable,,01-06-2024,Warwick Crescent,Borstal,,Rochester,Kent,ME1 3LF,www.thepilgrimschool.co.uk,16343975555.0,Mrs,Alison,Mepsted,,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,572771.0,166724.0,Medway 024,Medway 024B,,,,,,South-East England and South London,,44010044.0,,Not applicable,Not applicable,,,E02003337,E01016127,39.0,
|
||||
143880,887,Medway,2214,Balfour Junior School,Academy converter,Academies,Open,Academy Converter,01-01-2017,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,480.0,No Special Classes,19-01-2023,480.0,257.0,223.0,29.0,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10062374.0,,Not applicable,05-12-2018,23-04-2024,Balfour Road,,,Chatham,Kent,ME4 6QX,www.balfourjuniorschool.org.uk,1634843833.0,Mrs,Zoe,Mayston,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Fort Pitt,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575169.0,166802.0,Medway 021,Medway 021D,,,,,Good,South-East England and South London,,44038129.0,,Not applicable,Not applicable,,,E02003334,E01016125,139.0,
|
||||
143909,887,Medway,2018,Wayfield Primary School,Academy converter,Academies,Open,Fresh Start,01-09-2016,,,Primary,2.0,11,,Has Nursery Classes,Not applicable,Mixed,None,Does not apply,,,360.0,Not applicable,19-01-2023,392.0,212.0,180.0,39.0,Supported by a multi-academy trust,THE PRIMARY FIRST TRUST,Linked to a sponsor,The Primary First Trust,Not applicable,,10062232.0,,,22-05-2019,25-04-2024,Wayfield Road,,,Chatham,Kent,ME5 0HH,www.wayfield.medway.sch.uk,3000658230.0,Miss,Ria,Henry,Head Teacher,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Wayfield & Weeds Wood,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576192.0,165414.0,Medway 027,Medway 027E,,,,,Good,South-East England and South London,,,,Not applicable,Not applicable,,,E02003340,E01016068,135.0,
|
||||
144132,887,Medway,2592,Thames View Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2017,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,450.0,No Special Classes,19-01-2023,449.0,224.0,225.0,18.0,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10063038.0,,Not applicable,17-05-2023,23-05-2024,Bloors Lane,Rainham,,Gillingham,Kent,ME8 7DX,https://www.thamesview-that.org.uk/,1634629080.0,Mrs,Leanna,Rogers,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,581008.0,166754.0,Medway 023,Medway 023C,,,,,Good,South-East England and South London,,44103856.0,,Not applicable,Not applicable,,,E02003336,E01016166,74.0,
|
||||
144133,887,Medway,2479,St Margaret's Infant School,Academy converter,Academies,Open,Academy Converter,01-04-2017,,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,300.0,155.0,145.0,10.6,Supported by a multi-academy trust,THE WESTBROOK TRUST,Linked to a sponsor,The Westbrook Trust,Not applicable,,10063037.0,,Not applicable,13-02-2020,21-05-2024,"St Margaret's Infant School, Orchard Street",Orchard Street,Rainham,Gillingham,Kent,ME8 9AE,http://www.stmargaretsinf.medway.sch.uk,1634231327.0,Mrs,Paula,Fewtrell,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South West,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,581405.0,165749.0,Medway 029,Medway 029C,,,,,Good,South-East England and South London,,100062396976.0,,Not applicable,Not applicable,,,E02003342,E01016089,30.0,
|
||||
144134,887,Medway,1107,The Rowans,Academy alternative provision converter,Academies,Open,Academy Converter,01-06-2017,,,Not applicable,5.0,16,No boarders,Not applicable,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,65.0,Not applicable,19-01-2023,26.0,18.0,8.0,65.4,Supported by a multi-academy trust,THE INSPIRING CHANGE MULTI-ACADEMY TRUST,-,,Not applicable,,10063925.0,,Not applicable,10-11-2021,19-03-2024,Silverbank,Churchill Avenue,,Chatham,Kent,ME5 0LB,www.therowans.org,1634338803.0,Mrs,Fiona,May,Headteacher,Not applicable,,,,Not applicable,,Does not have child care facilities,PRU Does have Provision for SEN,PRU Does have EBD provision,66.0,PRU offers full time provision,Does not offer tuition by another provider,Not applicable,"SEMH - Social, Emotional and Mental Health",,,,,,,,,,,,,Resourced provision,65.0,65.0,,,South East,Medway,Wayfield & Weeds Wood,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576044.0,164885.0,Medway 027,Medway 027E,,,,,Outstanding,South-East England and South London,,44112005.0,,Not applicable,Not applicable,,,E02003340,E01016068,17.0,
|
||||
144135,887,Medway,3757,Riverside Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2017,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,390.0,Not applicable,19-01-2023,406.0,220.0,186.0,32.5,Supported by a multi-academy trust,RMET,Linked to a sponsor,RMET (Rainham Mark Education Trust),Not applicable,,10063036.0,,Not applicable,13-11-2019,29-05-2024,St Edmunds Way,Rainham,,Gillingham,Kent,ME8 8ET,www.riverside.medway.sch.uk/,1634623500.0,Mrs,Helen,Robson,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,VI - Visual Impairment,ASD - Autistic Spectrum Disorder,,,,,,,,,,,,Resourced provision,17.0,15.0,,,South East,Medway,Rainham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,582416.0,166196.0,Medway 025,Medway 025C,,,,,Good,South-East England and South London,,44110167.0,,Not applicable,Not applicable,,,E02003338,E01016097,117.0,
|
||||
144423,887,Medway,2396,Barnsole Primary School,Academy converter,Academies,Open,Academy Converter,01-05-2017,,,Primary,2.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,19-01-2023,671.0,336.0,335.0,25.9,Supported by a multi-academy trust,MARITIME ACADEMY TRUST,Linked to a sponsor,Maritime Academy Trust,Not applicable,,10063725.0,,Not applicable,29-02-2024,22-05-2024,Barnsole Road,,,Gillingham,Kent,ME7 2JG,www.barnsoleprimary.medway.sch.uk,1634333400.0,Mr,Jonathan,Smales,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Watling,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,578041.0,167976.0,Medway 013,Medway 013D,,,,,Good,South-East England and South London,,44076748.0,,Not applicable,Not applicable,,,E02003326,E01016177,168.0,
|
||||
144566,887,Medway,2492,Bligh Primary School (Juniors),Academy converter,Academies,Open,Academy Converter,01-10-2017,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,330.0,No Special Classes,19-01-2023,328.0,159.0,169.0,35.7,Supported by a multi-academy trust,MARITIME ACADEMY TRUST,Linked to a sponsor,Maritime Academy Trust,Not applicable,,10064767.0,,Not applicable,22-06-2023,04-06-2024,Bligh Way,Strood,,Rochester,Kent,ME2 2XJ,www.blighprimaryschool.co.uk,1634336220.0,Mr,Christian,Markham,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood West,Rochester and Strood,(England/Wales) Urban city and town,E06000035,571407.0,169082.0,Medway 008,Medway 008D,,,,,Good,South-East England and South London,,100062388554.0,,Not applicable,Not applicable,,,E02003321,E01016155,117.0,
|
||||
144639,887,Medway,2623,Miers Court Primary School,Academy converter,Academies,Open,Academy Converter,01-08-2017,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,394.0,200.0,194.0,14.2,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10064690.0,,Not applicable,03-11-2021,17-04-2024,Silverspot Close,,,Rainham,Kent,ME8 8JR,https://www.mierscourt-that.org.uk/,1634388943.0,Mrs,Susan,Chapman,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham South East,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,581647.0,165162.0,Medway 032,Medway 032C,,,,,Good,South-East England and South London,,44101008.0,,Not applicable,Not applicable,,,E02003345,E01016104,56.0,
|
||||
144914,887,Medway,3293,St Margaret's at Troy Town CofE Voluntary Controlled Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2017,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Does not apply,Diocese of Rochester,Not applicable,210.0,No Special Classes,19-01-2023,225.0,119.0,106.0,37.3,Supported by a multi-academy trust,THE PILGRIM MULTI ACADEMY TRUST,-,,Not applicable,,10065034.0,,Not applicable,02-12-2021,05-02-2024,King Street,,,Rochester,Kent,ME1 1YF,http://www.stmargaretsattroytown.co.uk,1634843843.0,Miss,Katie,Willis,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,Not applicable,,,,,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574390.0,167980.0,Medway 014,Medway 014A,,,,,Good,South-East England and South London,,44023621.0,,Not applicable,Not applicable,,,E02003327,E01016117,76.0,
|
||||
144915,887,Medway,2537,Bligh Primary School (Infants),Academy converter,Academies,Open,Academy Converter,01-10-2017,,,Primary,2.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,325.0,157.0,168.0,27.9,Supported by a multi-academy trust,MARITIME ACADEMY TRUST,Linked to a sponsor,Maritime Academy Trust,Not applicable,,10065035.0,,Not applicable,,04-06-2024,Bligh Way,Strood,,Rochester,Kent,ME2 2XJ,www.blighprimaryschool.co.uk,1634336220.0,Mr,Christian,Markham,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood West,Rochester and Strood,(England/Wales) Urban city and town,E06000035,571407.0,169082.0,Medway 008,Medway 008D,,,,,,South-East England and South London,,100062388554.0,,Not applicable,Not applicable,,,E02003321,E01016155,80.0,
|
||||
144969,887,Medway,2019,Featherby Junior School,Academy sponsor led,Academies,Open,New Provision,01-09-2017,,,Primary,7.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,360.0,Not applicable,19-01-2023,339.0,165.0,174.0,34.2,Supported by a multi-academy trust,MARITIME ACADEMY TRUST,Linked to a sponsor,Maritime Academy Trust,Not applicable,,10065102.0,,,29-06-2022,07-05-2024,Chilham Road,,Featherby Junior School,Gillingham,Kent,ME8 6BT,www.featherby-jun.medway.sch.uk,1634231984.0,Mrs,Emma,Pape,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Twydall,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579316.0,167170.0,Medway 018,Medway 018D,,,,,Good,South-East England and South London,,44088972.0,,Not applicable,Not applicable,,,E02003331,E01016163,116.0,
|
||||
145042,887,Medway,2401,Featherby Infant and Nursery School,Academy converter,Academies,Open,Academy Converter,01-09-2017,,,Primary,3.0,7,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,270.0,No Special Classes,19-01-2023,297.0,155.0,142.0,32.1,Supported by a multi-academy trust,MARITIME ACADEMY TRUST,Linked to a sponsor,Maritime Academy Trust,Not applicable,,10065161.0,,Not applicable,17-11-2021,07-05-2024,Allington Road,,,Gillingham,Kent,ME8 6PD,http://www.featherby-inf.medway.sch.uk,1634231072.0,Mrs,Emma,Pape,Head of School,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Twydall,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579330.0,167352.0,Medway 018,Medway 018D,,,,,Good,South-East England and South London,,44088976.0,,Not applicable,Not applicable,,,E02003331,E01016163,93.0,
|
||||
145112,887,Medway,2020,Maundene School,Academy sponsor led,Academies,Open,New Provision,01-01-2018,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,,Not applicable,Not applicable,420.0,Not applicable,19-01-2023,395.0,187.0,208.0,22.0,Supported by a multi-academy trust,INSPIRE PARTNERSHIP ACADEMY TRUST,Linked to a sponsor,Inspire Partnership Academy Trust,Not applicable,,10065460.0,,,15-06-2022,02-05-2024,Swallow Rise,Walderslade,,Chatham,Kent,ME5 7QB,https://www.maundene.medway.sch.uk/,1634864721.0,Miss,Joanne,Capes,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Princes Park,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576557.0,164396.0,Medway 034,Medway 034A,,,,,Good,South-East England and South London,,44047176.0,,Not applicable,Not applicable,,,E02003347,E01016078,87.0,
|
||||
145440,887,Medway,2499,Hilltop Primary School,Academy converter,Academies,Open,Academy Converter,01-02-2018,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,448.0,No Special Classes,19-01-2023,436.0,242.0,194.0,14.3,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10066627.0,,Not applicable,29-06-2022,23-04-2024,Hilltop Road,Frindsbury,,Rochester,Kent,ME2 4QN,www.hilltopprimary.co.uk,1634710312.0,Mrs,Ewa,Eddy,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood Rural,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574043.0,170333.0,Medway 004,Medway 004C,,,,,Good,South-East England and South London,,100062389240.0,,Not applicable,Not applicable,,,E02003317,E01016148,61.0,
|
||||
145926,887,Medway,2021,Elaine Primary School,Academy sponsor led,Academies,Open,Fresh Start,01-05-2018,,,Primary,3.0,11,No boarders,Has Nursery Classes,Not applicable,Mixed,None,None,,,350.0,Has Special Classes,19-01-2023,301.0,148.0,153.0,56.1,Supported by a multi-academy trust,INSPIRE PARTNERSHIP ACADEMY TRUST,Linked to a sponsor,Inspire Partnership Academy Trust,Not applicable,,10067319.0,,,22-09-2022,16-04-2024,Elaine Avenue,,,Rochester,,ME2 2YN,http://www.elaine.medway.sch.uk/,1634294817.0,Mrs,Sarah,Martin,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,"SEMH - Social, Emotional and Mental Health",,,,,,,,,,,,,SEN unit,,,20.0,11.0,South East,Medway,Strood West,Rochester and Strood,(England/Wales) Urban city and town,E06000035,572443.0,168984.0,Medway 011,Medway 011A,,,,,Good,South-East England and South London,United Kingdom,200000895803.0,,Not applicable,Not applicable,,,E02003324,E01016150,169.0,
|
||||
146648,887,Medway,4003,Waterfront UTC,University technical college,Free Schools,Open,,01-04-2019,,,Secondary,14.0,19,,No Nursery Classes,Has a sixth form,Mixed,Does not apply,Does not apply,,Non-selective,250.0,Not applicable,19-01-2023,267.0,190.0,77.0,31.3,Supported by a multi-academy trust,THE HOWARD ACADEMY TRUST,Linked to a sponsor,The Howard Academy Trust,Not applicable,,10082095.0,,,11-01-2023,14-09-2023,South Side Three Road,,,Chatham,Kent,ME4 4FQ,www.waterfront-that.org.uk,1634505800.0,Mrs,Fiona,McLean,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,St Mary's Island,Rochester and Strood,(England/Wales) Urban city and town,E06000035,577382.0,169573.0,Medway 007,Medway 007H,,,,,Good,South-East England and South London,United Kingdom,44039551.0,,Not applicable,Not applicable,,,E02003320,E01035292,66.0,
|
||||
146872,887,Medway,2211,Halling Primary School,Academy converter,Academies,Open,Academy Converter,01-04-2019,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,420.0,No Special Classes,19-01-2023,376.0,188.0,188.0,17.3,Supported by a multi-academy trust,ALETHEIA ACADEMIES TRUST,Linked to a sponsor,Aletheia Academies Trust,Not applicable,,10082988.0,,Not applicable,07-06-2023,14-05-2024,Howlsmere Close,Halling,,Rochester,Kent,ME2 1ER,http://www.halling.medway.sch.uk/,1634240258.0,Ms,Lisa,Taylor,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,"Cuxton, Halling & Riverside",Rochester and Strood,(England/Wales) Rural town and fringe,E06000035,570607.0,163387.0,Medway 028,Medway 028A,,,,,Good,South-East England and South London,,44000585.0,,Not applicable,Not applicable,,,E02003341,E01016026,65.0,
|
||||
147446,887,Medway,2022,Wainscott Primary School,Academy sponsor led,Academies,Open,New Provision,01-09-2019,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,446.0,No Special Classes,19-01-2023,455.0,235.0,220.0,17.6,Supported by a multi-academy trust,THE PRIMARY FIRST TRUST,Linked to a sponsor,The Primary First Trust,Not applicable,,10084146.0,,,,21-05-2024,Wainscott Road,Wainscott,,Rochester,Kent,ME2 4JX,www.wainscott.medway.sch.uk,1634332550.0,Mrs,Monique,Clark,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Strood Rural,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574754.0,170678.0,Medway 004,Medway 004E,,,,,,South-East England and South London,United Kingdom,44028647.0,,Not applicable,Not applicable,,,E02003317,E01035289,80.0,
|
||||
147769,887,Medway,2023,Delce Academy,Academy converter,Academies,Open,Fresh Start,01-03-2020,,,Primary,5.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,None,Does not apply,,,360.0,Has Special Classes,19-01-2023,359.0,193.0,166.0,44.0,Supported by a multi-academy trust,INSPIRE PARTNERSHIP ACADEMY TRUST,Linked to a sponsor,Inspire Partnership Academy Trust,Not applicable,,10085699.0,,,,28-05-2024,The Tideway,,,Rochester,Kent,ME1 2NJ,www.delceacademy.co.uk,1634845242.0,Miss,Loni,Stevens,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,"SEMH - Social, Emotional and Mental Health",,,,,,,,,,,,,SEN unit,,,15.0,15.0,South East,Medway,Rochester East & Warren Wood,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574455.0,166524.0,Medway 017,Medway 017C,,,,,,South-East England and South London,United Kingdom,44022435.0,,Not applicable,Not applicable,,,E02003330,E01016115,158.0,
|
||||
148117,887,Medway,2433,Oaklands School,Academy converter,Academies,Open,Academy Converter,01-09-2020,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,450.0,No Special Classes,19-01-2023,430.0,208.0,222.0,42.2,Supported by a multi-academy trust,THE WESTBROOK TRUST,Linked to a sponsor,The Westbrook Trust,Not applicable,,10086851.0,,,04-07-2023,21-05-2024,Weedswood Road,Walderslade,,Chatham,Kent,ME5 0QS,http://www.oaklands.medway.sch.uk,1634333820.0,Mrs,Louisa,Jones,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Wayfield & Weeds Wood,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575761.0,163898.0,Medway 033,Medway 033D,Not applicable,,,,Good,South-East England and South London,,44033912.0,,Not applicable,Not applicable,,,E02003346,E01016173,175.0,
|
||||
148577,887,Medway,4004,Leigh Academy Rainham,Free schools,Free Schools,Open,New Provision,01-09-2021,,,Secondary,11.0,19,,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1150.0,Not applicable,19-01-2023,405.0,223.0,182.0,16.8,Supported by a multi-academy trust,LEIGH ACADEMIES TRUST,Linked to a sponsor,Leigh Academies Trust,Not applicable,,10088768.0,,,28-02-2024,22-05-2024,Otterham Quay Lane,Rainham,,Gillingham,Kent,ME8 8GS,https://leighacademyrainham.org.uk/,1634412440.0,Miss,Alexandra,Millward,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rainham North,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,582774.0,165824.0,Medway 025,Medway 025E,,,,,Good,South-East England and South London,United Kingdom,44073878.0,,Not applicable,Not applicable,,,E02003338,E01016102,68.0,
|
||||
149009,887,Medway,1108,Will Adams Academy,Academy alternative provision converter,Academies,Open,Academy Converter,01-04-2022,,,Not applicable,14.0,17,No boarders,Not applicable,Not applicable,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,,Not applicable,19-01-2023,39.0,21.0,18.0,39.5,Supported by a multi-academy trust,ALTERNATIVE LEARNING TRUST,Linked to a sponsor,Alternative Learning Trust,Not applicable,,10090191.0,,,,16-04-2024,Woodlands Road,,,Gillingham,Kent,ME7 2BX,www.willadamsacademy.org.uk,1634337111.0,Ms,Marie,Woolston,HeadTeacher,Not applicable,,,,Not applicable,,Not applicable,PRU Does not have Provision for SEN,Not applicable,,Not applicable,Not applicable,Not applicable,"SEMH - Social, Emotional and Mental Health",,,,,,,,,,,,,Resourced provision,45.0,45.0,,,South East,Medway,Watling,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,578774.0,167623.0,Medway 019,Medway 019A,Not applicable,,,,,South-East England and South London,,200003623419.0,,Not applicable,Not applicable,,,E02003332,E01016159,15.0,
|
||||
149069,887,Medway,2025,Rochester Riverside Church of England Primary School,Free schools,Free Schools,Open,Academy Free School,01-09-2022,,,Primary,3.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Church of England,Christian,,Non-selective,446.0,Not applicable,19-01-2023,26.0,16.0,10.0,0.0,Supported by a multi-academy trust,THE PILGRIM MULTI ACADEMY TRUST,-,,Not applicable,,10090885.0,,,,29-05-2024,Gas House Road,,,Rochester,Kent,ME1 1US,rrcoe.medway.sch.uk,1634471697.0,Mrs,Alison,Mepsted,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Rochester West & Borstal,Rochester and Strood,(England/Wales) Urban city and town,E06000035,574334.0,168802.0,Medway 015,Medway 015F,,,,,,South-East England and South London,United Kingdom,44076556.0,,Not applicable,Not applicable,,,E02003328,E01035296,0.0,
|
||||
149075,887,Medway,4005,Maritime Academy,Free schools,Free Schools,Open,Academy Free School,01-09-2022,,,Secondary,11.0,19,No boarders,No Nursery Classes,Has a sixth form,Mixed,None,None,Not applicable,Non-selective,1150.0,Not applicable,19-01-2023,142.0,77.0,65.0,27.5,Supported by a multi-academy trust,THE THINKING SCHOOLS ACADEMY TRUST,Linked to a sponsor,The Thinking Schools Academy Trust,Not applicable,,10090894.0,,,,24-05-2024,Frindsbury Hill,Rochester,Kent,Medway,,,https://www.maritimeacademy.org.uk/,3333602150.0,,Jody,Murphy,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Twydall,Gillingham and Rainham,(England/Wales) Urban city and town,E06000035,579947.0,166841.0,Medway 018,Medway 018B,,,,,,South-East England and South London,,,,Not applicable,Not applicable,,,E02003331,E01016161,39.0,
|
||||
149979,887,Medway,3753,St Benedict's Catholic Primary School,Academy converter,Academies,Open,Academy Converter,01-09-2023,,,Primary,4.0,11,No boarders,No Nursery Classes,Does not have a sixth form,Mixed,Roman Catholic,Does not apply,Archdiocese of Southwark,Not applicable,210.0,No Special Classes,,,,,,Supported by a multi-academy trust,KENT CATHOLIC SCHOOLS' PARTNERSHIP,Linked to a sponsor,Kent Catholic Schools' Partnership,Not applicable,,10092965.0,,,,06-03-2024,Lambourn Way,Lordswood,,Chatham,Kent,ME5 8PU,www.st-benedicts.medway.sch.uk/,1634669700.0,Mrs,Sarah,McAlpine,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Lordswood & Walderslade,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,577291.0,162859.0,Medway 038,Medway 038D,Not applicable,,,,,South-East England and South London,,44058733.0,,Not applicable,Not applicable,,,E02003351,E01016060,,
|
||||
150154,887,Medway,4007,Oasis Restore,Academy secure 16 to 19,Academies,Open,New Provision,15-04-2024,,,16 plus,12.0,17,,Not applicable,Not applicable,Mixed,,,,,49.0,Not applicable,,,,,,Not applicable,OASIS RESTORE TRUST,Not applicable,,Not applicable,,10094342.0,,,,15-04-2024,1 Kennington Road,,,London,,SE1 7QP,https://www.oasisrestore.org/,2079214200.0,Mr,Andrew,Willetts,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Lambeth,Waterloo & South Bank,Vauxhall,(England/Wales) Urban major conurbation,E06000035,531156.0,179383.0,Lambeth 036,Lambeth 036B,,,,,,,United Kingdom,100023225318.0,,Not applicable,Not applicable,,,E02006801,E01003014,,
|
||||
150218,887,Medway,2199,Luton Primary School,Academy converter,Academies,Open,Academy Converter,01-11-2023,,,Primary,4.0,11,No boarders,Has Nursery Classes,Does not have a sixth form,Mixed,Does not apply,Does not apply,Not applicable,Not applicable,630.0,No Special Classes,,,,,,Supported by a multi-academy trust,RIVERMEAD INCLUSIVE TRUST,Linked to a sponsor,Rivermead Inclusive Trust,Not applicable,,10093456.0,,,,03-06-2024,Luton Road,,,Chatham,Kent,ME4 5AW,www.lutonprimaryschool.co.uk,1634336900.0,Mrs,Karen,Major,Headteacher,Not applicable,,,,Not applicable,,Not applicable,Not applicable,Not applicable,,Not applicable,Not applicable,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Luton,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,577119.0,166655.0,Medway 020,Medway 020A,Not applicable,,,,,South-East England and South London,,100062391815.0,,Not applicable,Not applicable,,,E02003333,E01016061,,
|
||||
150869,887,Medway,4008,Walderslade School,Academy converter,Academies,Open,Fresh Start,01-04-2024,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Girls,None,Does not apply,,,949.0,Not applicable,,,,,,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10095249.0,,,,04-04-2024,Bradfields Avenue,Walderslade,,Chatham,Kent,ME5 0LE,,,Mrs,Louise,Campbell,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Wayfield & Weeds Wood,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,575885.0,164367.0,Medway 027,Medway 027D,,,,,,South-East England and South London,United Kingdom,44033058.0,,Not applicable,Not applicable,,,E02003340,E01016067,,
|
||||
150870,887,Medway,4009,Greenacre School,Academy converter,Academies,Open,Fresh Start,01-04-2024,,,Secondary,11.0,18,No boarders,No Nursery Classes,Has a sixth form,Boys,None,Does not apply,,,969.0,Not applicable,,,,,,Supported by a multi-academy trust,BEYOND SCHOOLS TRUST,Linked to a sponsor,FPTA Academies (Fort Pitt Grammar School and The Thomas Aveling School),Not applicable,,10095250.0,,,,04-04-2024,157 Walderslade Road,Walderslade,,Chatham,Kent,ME5 0LP,,,Mrs,Louise,Campbell,,Not applicable,,,,Not applicable,,,Not applicable,Not applicable,,,,Not applicable,,,,,,,,,,,,,,,,,,,South East,Medway,Wayfield & Weeds Wood,Chatham and Aylesford,(England/Wales) Urban city and town,E06000035,576000.0,164283.0,Medway 034,Medway 034C,,,,,,South-East England and South London,United Kingdom,44026624.0,,Not applicable,Not applicable,,,E02003347,E01016081,,
|
||||
|
@ -1,12 +1,11 @@
|
||||
services:
|
||||
backend:
|
||||
container_name: cc-api
|
||||
container_name: api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
env_file:
|
||||
- .env
|
||||
- .env.app.prod
|
||||
ports:
|
||||
- 8000:8000
|
||||
networks:
|
||||
|
||||
356
main.py
356
main.py
@ -1,20 +1,22 @@
|
||||
import os
|
||||
import argparse
|
||||
import sys
|
||||
import subprocess
|
||||
import signal
|
||||
import atexit
|
||||
import time
|
||||
from modules.logger_tool import initialise_logger
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
from fastapi import FastAPI, HTTPException
|
||||
import uvicorn
|
||||
import requests
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Optional
|
||||
from modules.database.tools.neo4j_driver_tools import get_driver
|
||||
from run.initialization.initialization import InitializationSystem
|
||||
import time
|
||||
import ssl
|
||||
|
||||
from run.setup import setup_cors
|
||||
from run.routers import register_routes
|
||||
from run.initialization import initialize_system
|
||||
from modules.task_processors import get_processor
|
||||
from modules.queue_system import ServiceType
|
||||
|
||||
# FastAPI App Setup
|
||||
app = FastAPI()
|
||||
@ -28,7 +30,8 @@ async def health_check() -> Dict[str, Any]:
|
||||
"status": "healthy",
|
||||
"services": {
|
||||
"neo4j": {"status": "healthy", "message": "Connected"},
|
||||
"supabase": {"status": "healthy", "message": "Connected"}
|
||||
"supabase": {"status": "healthy", "message": "Connected"},
|
||||
"redis": {"status": "healthy", "message": "Connected"}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +73,35 @@ async def health_check() -> Dict[str, Any]:
|
||||
}
|
||||
health_status["status"] = "unhealthy"
|
||||
|
||||
try:
|
||||
# Check Redis using new Redis manager
|
||||
from modules.redis_manager import get_redis_manager
|
||||
|
||||
# Determine environment
|
||||
environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'
|
||||
redis_manager = get_redis_manager(environment)
|
||||
|
||||
# Get comprehensive health check
|
||||
redis_health = redis_manager.health_check()
|
||||
|
||||
health_status["services"]["redis"] = {
|
||||
"status": redis_health['status'],
|
||||
"message": redis_health.get('error', f"Connected to {environment} environment (db={redis_health['database']})"),
|
||||
"environment": redis_health['environment'],
|
||||
"database": redis_health['database'],
|
||||
"queue_stats": redis_health.get('queue_stats', {})
|
||||
}
|
||||
|
||||
if redis_health['status'] != 'healthy':
|
||||
health_status["status"] = "unhealthy"
|
||||
|
||||
except Exception as e:
|
||||
health_status["services"]["redis"] = {
|
||||
"status": "unhealthy",
|
||||
"message": f"Error checking Redis: {str(e)}"
|
||||
}
|
||||
health_status["status"] = "unhealthy"
|
||||
|
||||
if health_status["status"] == "unhealthy":
|
||||
raise HTTPException(status_code=503, detail=health_status)
|
||||
|
||||
@ -78,71 +110,253 @@ async def health_check() -> Dict[str, Any]:
|
||||
# Register routes
|
||||
register_routes(app)
|
||||
|
||||
# Initialize system with retry logic
|
||||
def initialize_with_retry(max_attempts: int = 3, initial_delay: int = 5) -> bool:
|
||||
"""Initialize the system with retry logic"""
|
||||
attempt = 0
|
||||
delay = initial_delay
|
||||
|
||||
while attempt < max_attempts:
|
||||
try:
|
||||
logger.info(f"Attempting system initialization (attempt {attempt + 1}/{max_attempts})")
|
||||
initialize_system()
|
||||
logger.info("System initialization completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
attempt += 1
|
||||
if attempt == max_attempts:
|
||||
logger.error(f"System initialization failed after {max_attempts} attempts: {str(e)}")
|
||||
return False
|
||||
|
||||
logger.warning(f"Initialization attempt {attempt} failed: {str(e)}. Retrying in {delay} seconds...")
|
||||
time.sleep(delay)
|
||||
delay *= 2 # Exponential backoff
|
||||
|
||||
return False
|
||||
# Start workers in the application process to avoid uvicorn reload issues
|
||||
@app.on_event("startup")
|
||||
async def _start_workers_event():
|
||||
try:
|
||||
if os.getenv('AUTO_START_QUEUE_WORKERS', 'true').lower() != 'true':
|
||||
logger.info("AUTO_START_QUEUE_WORKERS=false, not starting in-process workers")
|
||||
return
|
||||
workers = int(os.getenv('QUEUE_WORKERS', '3'))
|
||||
services_csv = os.getenv('QUEUE_SERVICES', 'tika,docling,split_map,document_analysis,page_images')
|
||||
service_names = [s.strip().lower() for s in services_csv.split(',') if s.strip()]
|
||||
service_enums = []
|
||||
for name in service_names:
|
||||
try:
|
||||
service_enums.append(ServiceType(name))
|
||||
except Exception:
|
||||
pass
|
||||
if not service_enums:
|
||||
service_enums = list(ServiceType)
|
||||
processor = get_processor()
|
||||
started = []
|
||||
for i in range(workers):
|
||||
wid = processor.start_worker(worker_id=f"app-worker-{i+1}", services=service_enums)
|
||||
started.append(wid)
|
||||
logger.info(f"In-process queue workers started: {started} for services {[s.value for s in service_enums]}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start in-process workers: {e}")
|
||||
|
||||
def run_initialization_mode():
|
||||
"""Run only the initialization process"""
|
||||
logger.info("Running in initialization mode")
|
||||
logger.info("Starting system initialization...")
|
||||
@app.on_event("shutdown")
|
||||
async def _shutdown_workers_event():
|
||||
try:
|
||||
processor = get_processor()
|
||||
processor.shutdown(timeout=30)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error during workers shutdown: {e}")
|
||||
|
||||
# Global subprocess handles (only for workers now)
|
||||
workers_process: Optional[subprocess.Popen] = None
|
||||
|
||||
# Global Redis manager for cleanup
|
||||
redis_manager = None
|
||||
|
||||
def start_queue_workers():
|
||||
"""Start queue workers as a subprocess (tied to API lifecycle)."""
|
||||
global workers_process
|
||||
if os.getenv('AUTO_START_QUEUE_WORKERS', 'true').lower() != 'true':
|
||||
logger.info("AUTO_START_QUEUE_WORKERS=false, not starting workers")
|
||||
return
|
||||
|
||||
# If already started, skip
|
||||
if workers_process is not None and workers_process.poll() is None:
|
||||
logger.info("Queue workers already running")
|
||||
return
|
||||
|
||||
services = os.getenv(
|
||||
'QUEUE_SERVICES',
|
||||
'tika,docling,split_map,document_analysis,page_images'
|
||||
)
|
||||
workers = int(os.getenv('QUEUE_WORKERS', '3'))
|
||||
check_interval = os.getenv('QUEUE_CHECK_INTERVAL', '15')
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
'start_queue_workers.py',
|
||||
'--workers', str(workers),
|
||||
'--services', services,
|
||||
'--check-interval', check_interval,
|
||||
]
|
||||
# Workers will auto-detect environment and use appropriate Redis database
|
||||
|
||||
log_path = os.getenv('QUEUE_WORKERS_LOG', './queue_workers.log')
|
||||
try:
|
||||
log_file = open(log_path, 'a')
|
||||
logger.info(f"Starting queue workers ({workers}) for services [{services}] → {log_path}")
|
||||
workers_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=log_file,
|
||||
stderr=log_file,
|
||||
preexec_fn=os.setsid if os.name != 'nt' else None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start queue workers: {e}")
|
||||
|
||||
def stop_queue_workers():
|
||||
"""Stop queue workers subprocess."""
|
||||
global workers_process
|
||||
if workers_process is not None:
|
||||
try:
|
||||
logger.info("Stopping queue workers...")
|
||||
if os.name != 'nt':
|
||||
os.killpg(os.getpgid(workers_process.pid), signal.SIGTERM)
|
||||
else:
|
||||
workers_process.terminate()
|
||||
try:
|
||||
workers_process.wait(timeout=10)
|
||||
logger.info("Queue workers stopped gracefully")
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("Queue workers did not stop gracefully, forcing shutdown...")
|
||||
if os.name != 'nt':
|
||||
os.killpg(os.getpgid(workers_process.pid), signal.SIGKILL)
|
||||
else:
|
||||
workers_process.kill()
|
||||
workers_process.wait()
|
||||
logger.info("Queue workers force stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping queue workers: {e}")
|
||||
finally:
|
||||
workers_process = None
|
||||
|
||||
def _install_signal_handlers():
|
||||
def signal_handler(signum, frame):
|
||||
logger.info(f"Received signal {signum}, shutting down...")
|
||||
stop_queue_workers()
|
||||
# Gracefully shutdown Redis manager if it exists
|
||||
global redis_manager
|
||||
if redis_manager:
|
||||
redis_manager.shutdown()
|
||||
sys.exit(0)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
def run_infrastructure_mode():
|
||||
"""Run infrastructure setup: Neo4j schema, calendar, and Supabase buckets"""
|
||||
logger.info("Running in infrastructure mode")
|
||||
logger.info("Starting infrastructure setup...")
|
||||
|
||||
if initialize_with_retry():
|
||||
logger.info("Initialization completed successfully")
|
||||
try:
|
||||
from run.initialization import initialize_infrastructure_mode
|
||||
initialize_infrastructure_mode()
|
||||
logger.info("Infrastructure setup completed successfully")
|
||||
return True
|
||||
else:
|
||||
logger.error("Initialization failed after multiple attempts")
|
||||
except Exception as e:
|
||||
logger.error(f"Infrastructure setup failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_demo_school_mode():
|
||||
"""Run demo school creation"""
|
||||
logger.info("Running in demo school mode")
|
||||
logger.info("Starting demo school creation...")
|
||||
|
||||
try:
|
||||
from run.initialization import initialize_demo_school_mode
|
||||
initialize_demo_school_mode()
|
||||
logger.info("Demo school creation completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Demo school creation failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_demo_users_mode():
|
||||
"""Run demo users creation"""
|
||||
logger.info("Running in demo users mode")
|
||||
logger.info("Starting demo users creation...")
|
||||
|
||||
try:
|
||||
from run.initialization import initialize_demo_users_mode
|
||||
initialize_demo_users_mode()
|
||||
logger.info("Demo users creation completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Demo users creation failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def run_gais_data_mode():
|
||||
"""Run GAIS data import"""
|
||||
logger.info("Running in GAIS data import mode")
|
||||
logger.info("Starting GAIS data import...")
|
||||
|
||||
try:
|
||||
from run.initialization import initialize_gais_data_mode
|
||||
initialize_gais_data_mode()
|
||||
logger.info("GAIS data import completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"GAIS data import failed: {str(e)}")
|
||||
return False
|
||||
|
||||
# Old clear_dev_redis_queue function removed - now handled by Redis Manager
|
||||
|
||||
def run_development_mode():
|
||||
"""Run the server in development mode with auto-reload"""
|
||||
logger.info("Running in development mode")
|
||||
|
||||
# Initialize Redis manager for development (auto-clears data)
|
||||
global redis_manager
|
||||
from modules.redis_manager import get_redis_manager
|
||||
redis_manager = get_redis_manager('dev')
|
||||
|
||||
if not redis_manager.initialize_environment():
|
||||
logger.error("Failed to initialize Redis for development")
|
||||
return False
|
||||
|
||||
# Workers are started in app startup event
|
||||
|
||||
logger.info("Starting uvicorn server with auto-reload...")
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=int(os.getenv('UVICORN_PORT', 8000)),
|
||||
log_level=os.getenv('LOG_LEVEL', 'info'),
|
||||
proxy_headers=True,
|
||||
timeout_keep_alive=10,
|
||||
reload=True
|
||||
)
|
||||
# Install signal handlers for graceful shutdown
|
||||
_install_signal_handlers()
|
||||
|
||||
try:
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=int(os.getenv('UVICORN_PORT', 8080)),
|
||||
log_level=os.getenv('LOG_LEVEL', 'info'),
|
||||
proxy_headers=True,
|
||||
timeout_keep_alive=10,
|
||||
reload=True
|
||||
)
|
||||
finally:
|
||||
stop_queue_workers()
|
||||
if redis_manager:
|
||||
redis_manager.shutdown()
|
||||
|
||||
def run_production_mode():
|
||||
"""Run the server in production mode"""
|
||||
logger.info("Running in production mode")
|
||||
|
||||
# Initialize Redis manager for production (preserves data, recovers tasks)
|
||||
global redis_manager
|
||||
from modules.redis_manager import get_redis_manager
|
||||
redis_manager = get_redis_manager('prod')
|
||||
|
||||
if not redis_manager.initialize_environment():
|
||||
logger.error("Failed to initialize Redis for production")
|
||||
return False
|
||||
|
||||
# Workers are started in app startup event
|
||||
|
||||
logger.info("Starting uvicorn server in production mode...")
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=int(os.getenv('UVICORN_PORT', 8000)),
|
||||
log_level=os.getenv('LOG_LEVEL', 'info'),
|
||||
proxy_headers=True,
|
||||
timeout_keep_alive=10,
|
||||
workers=int(os.getenv('UVICORN_WORKERS', '1'))
|
||||
)
|
||||
# Install signal handlers for graceful shutdown
|
||||
_install_signal_handlers()
|
||||
|
||||
try:
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=int(os.getenv('UVICORN_PORT', 8080)),
|
||||
log_level=os.getenv('LOG_LEVEL', 'info'),
|
||||
proxy_headers=True,
|
||||
timeout_keep_alive=10,
|
||||
workers=int(os.getenv('UVICORN_WORKERS', '1'))
|
||||
)
|
||||
finally:
|
||||
stop_queue_workers()
|
||||
if redis_manager:
|
||||
redis_manager.shutdown()
|
||||
|
||||
def parse_arguments():
|
||||
"""Parse command line arguments"""
|
||||
@ -151,15 +365,18 @@ def parse_arguments():
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Startup modes:
|
||||
init - Run initialization scripts (database setup, etc.)
|
||||
dev - Run development server with auto-reload
|
||||
prod - Run production server (for Docker/containerized deployment)
|
||||
infra - Setup infrastructure (Neo4j schema, calendar, Supabase buckets)
|
||||
demo-school - Create demo school (KevlarAI)
|
||||
demo-users - Create demo users
|
||||
gais-data - Import GAIS data (Edubase, etc.)
|
||||
dev - Run development server with auto-reload
|
||||
prod - Run production server (for Docker/containerized deployment)
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--mode', '-m',
|
||||
choices=['init', 'dev', 'prod'],
|
||||
choices=['infra', 'demo-school', 'demo-users', 'gais-data', 'dev', 'prod'],
|
||||
default='dev',
|
||||
help='Startup mode (default: dev)'
|
||||
)
|
||||
@ -177,9 +394,24 @@ if __name__ == "__main__":
|
||||
|
||||
logger.info(f"Starting ClassroomCopilot API in {args.mode} mode")
|
||||
|
||||
if args.mode == 'init':
|
||||
# Run initialization only
|
||||
success = run_initialization_mode()
|
||||
if args.mode == 'infra':
|
||||
# Run infrastructure setup
|
||||
success = run_infrastructure_mode()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'demo-school':
|
||||
# Run demo school creation
|
||||
success = run_demo_school_mode()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'demo-users':
|
||||
# Run demo users creation
|
||||
success = run_demo_users_mode()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'gais-data':
|
||||
# Run GAIS data import
|
||||
success = run_gais_data_mode()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.mode == 'dev':
|
||||
|
||||
@ -18,13 +18,14 @@ class SupabaseBearer(HTTPBearer):
|
||||
|
||||
try:
|
||||
token = credentials.credentials
|
||||
payload = verify_supabase_token(token)
|
||||
# Decode using the string-based verifier to avoid async dependency conflicts
|
||||
payload = verify_supabase_jwt_str(token)
|
||||
return payload
|
||||
except Exception as e:
|
||||
logger.error(f"Token verification failed: {str(e)}")
|
||||
raise HTTPException(status_code=403, detail="Invalid token or expired token.")
|
||||
|
||||
def verify_supabase_token(token: str) -> dict:
|
||||
def verify_supabase_jwt_str(token: str) -> dict:
|
||||
"""Verify a Supabase JWT token and return its payload."""
|
||||
try:
|
||||
jwt_secret = os.getenv("JWT_SECRET")
|
||||
@ -63,7 +64,7 @@ def decodeSupabaseJWT(token: str) -> dict:
|
||||
# Initialize the security instance
|
||||
security = HTTPBearer()
|
||||
|
||||
async def verify_supabase_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
||||
async def verify_supabase_token_dep(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
||||
try:
|
||||
token = credentials.credentials
|
||||
# Verify token using your Supabase JWT secret
|
||||
|
||||
571
modules/bundle_metadata.py
Normal file
571
modules/bundle_metadata.py
Normal file
@ -0,0 +1,571 @@
|
||||
"""
|
||||
Uniform Bundle Metadata Architecture
|
||||
|
||||
This module defines standardized metadata structures for all document processing pipelines
|
||||
to ensure consistency and interoperability across OCR, No-OCR, and VLM bundles.
|
||||
|
||||
Features:
|
||||
- Uniform metadata schema for all pipeline types
|
||||
- Consistent grouping and ordering mechanisms
|
||||
- Pipeline-agnostic bundle identification
|
||||
- Enhanced metadata for frontend display
|
||||
- Backward compatibility with existing bundles
|
||||
"""
|
||||
|
||||
import uuid
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List, Union, Literal
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
|
||||
class PipelineType(Enum):
|
||||
"""Supported pipeline types"""
|
||||
STANDARD = "standard"
|
||||
VLM = "vlm"
|
||||
ASR = "asr"
|
||||
|
||||
class ProcessingMode(Enum):
|
||||
"""How content was processed"""
|
||||
WHOLE_DOCUMENT = "whole_document"
|
||||
SPLIT_SECTIONS = "split_sections"
|
||||
INDIVIDUAL_PAGES = "individual_pages"
|
||||
PAGE_BUNDLE = "page_bundle"
|
||||
|
||||
class BundleType(Enum):
|
||||
"""Type of bundle created"""
|
||||
SINGLE_ARTEFACT = "single_artefact"
|
||||
SPLIT_PACK = "split_pack"
|
||||
PAGE_BUNDLE = "page_bundle"
|
||||
VLM_SECTION_BUNDLE = "vlm_section_bundle"
|
||||
# New unified bundle types
|
||||
DOCLING_BUNDLE = "docling_bundle" # Single coherent processing unit
|
||||
DOCLING_BUNDLE_SPLIT = "docling_bundle_split" # Container for multi-unit processing
|
||||
|
||||
@dataclass
|
||||
class BundleMetadata:
|
||||
"""
|
||||
Standardized metadata structure for all document processing bundles.
|
||||
|
||||
This ensures consistency across OCR, No-OCR, and VLM pipelines while
|
||||
maintaining backward compatibility.
|
||||
"""
|
||||
|
||||
# Core identification
|
||||
bundle_id: str
|
||||
file_id: str
|
||||
pipeline: PipelineType
|
||||
processing_mode: ProcessingMode
|
||||
bundle_type: BundleType
|
||||
|
||||
# Grouping and ordering
|
||||
group_id: Optional[str] = None
|
||||
split_order: Optional[int] = None
|
||||
split_total: Optional[int] = None
|
||||
split_heading: Optional[str] = None
|
||||
|
||||
# Content information
|
||||
page_range: Optional[List[int]] = None # [start_page, end_page]
|
||||
page_count: Optional[int] = None
|
||||
section_title: Optional[str] = None
|
||||
section_level: Optional[int] = None
|
||||
|
||||
# Processing details
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
settings_fingerprint: Optional[str] = None
|
||||
processing_time: Optional[float] = None
|
||||
|
||||
# Pipeline-specific metadata
|
||||
pipeline_metadata: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Producer information
|
||||
producer: str = "manual" # manual, auto_phase2, auto_split, etc.
|
||||
created_at: Optional[str] = None
|
||||
|
||||
# Status and quality
|
||||
status: str = "completed"
|
||||
quality_score: Optional[float] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""Set defaults and compute derived fields"""
|
||||
if self.created_at is None:
|
||||
self.created_at = datetime.utcnow().isoformat()
|
||||
|
||||
if self.bundle_id is None:
|
||||
self.bundle_id = str(uuid.uuid4())
|
||||
|
||||
# Compute settings fingerprint if config provided
|
||||
if self.config and not self.settings_fingerprint:
|
||||
self.settings_fingerprint = self._compute_settings_fingerprint(self.config)
|
||||
|
||||
def _compute_settings_fingerprint(self, config: Dict[str, Any]) -> str:
|
||||
"""Compute a fingerprint for configuration settings (excluding page_range)"""
|
||||
try:
|
||||
config_for_hash = dict(config)
|
||||
config_for_hash.pop('page_range', None) # Exclude page ranges from fingerprint
|
||||
config_str = json.dumps(config_for_hash, sort_keys=True, ensure_ascii=False)
|
||||
return hashlib.sha1(config_str.encode('utf-8')).hexdigest()[:16]
|
||||
except Exception:
|
||||
return str(uuid.uuid4())[:16]
|
||||
|
||||
def to_artefact_extra(self) -> Dict[str, Any]:
|
||||
"""Convert to format suitable for document_artefacts.extra field"""
|
||||
extra = {}
|
||||
|
||||
# Core fields
|
||||
extra['bundle_metadata_version'] = '1.0'
|
||||
extra['pipeline'] = self.pipeline.value
|
||||
extra['processing_mode'] = self.processing_mode.value
|
||||
extra['bundle_type'] = self.bundle_type.value
|
||||
|
||||
# Store original pipeline type for UI differentiation
|
||||
if hasattr(self, 'original_pipeline_type'):
|
||||
extra['original_pipeline'] = self.original_pipeline_type
|
||||
|
||||
# Grouping fields
|
||||
if self.group_id:
|
||||
extra['group_id'] = self.group_id
|
||||
if self.split_order is not None:
|
||||
extra['split_order'] = self.split_order
|
||||
if self.split_total is not None:
|
||||
extra['split_total'] = self.split_total
|
||||
if self.split_heading:
|
||||
extra['split_heading'] = self.split_heading
|
||||
|
||||
# Content fields
|
||||
if self.page_range:
|
||||
extra['page_range'] = self.page_range
|
||||
if self.page_count is not None:
|
||||
extra['page_count'] = self.page_count
|
||||
if self.section_title:
|
||||
extra['section_title'] = self.section_title
|
||||
if self.section_level is not None:
|
||||
extra['section_level'] = self.section_level
|
||||
|
||||
# Processing fields
|
||||
if self.config:
|
||||
extra['config'] = self.config
|
||||
if self.settings_fingerprint:
|
||||
extra['settings_fingerprint'] = self.settings_fingerprint
|
||||
if self.processing_time is not None:
|
||||
extra['processing_time'] = self.processing_time
|
||||
|
||||
# Pipeline-specific metadata
|
||||
if self.pipeline_metadata:
|
||||
extra['pipeline_metadata'] = self.pipeline_metadata
|
||||
|
||||
# Producer and quality
|
||||
extra['producer'] = self.producer
|
||||
if self.quality_score is not None:
|
||||
extra['quality_score'] = self.quality_score
|
||||
|
||||
return extra
|
||||
|
||||
@classmethod
|
||||
def from_artefact_extra(cls, file_id: str, artefact_id: str, extra: Dict[str, Any]) -> 'BundleMetadata':
|
||||
"""Create BundleMetadata from document_artefacts.extra field"""
|
||||
|
||||
# Extract core fields with fallbacks for backward compatibility
|
||||
pipeline_str = extra.get('pipeline', 'standard')
|
||||
try:
|
||||
pipeline = PipelineType(pipeline_str)
|
||||
except ValueError:
|
||||
pipeline = PipelineType.STANDARD
|
||||
|
||||
processing_mode_str = extra.get('processing_mode', 'whole_document')
|
||||
try:
|
||||
processing_mode = ProcessingMode(processing_mode_str)
|
||||
except ValueError:
|
||||
processing_mode = ProcessingMode.WHOLE_DOCUMENT
|
||||
|
||||
bundle_type_str = extra.get('bundle_type', 'single_artefact')
|
||||
try:
|
||||
bundle_type = BundleType(bundle_type_str)
|
||||
except ValueError:
|
||||
bundle_type = BundleType.SINGLE_ARTEFACT
|
||||
|
||||
return cls(
|
||||
bundle_id=artefact_id,
|
||||
file_id=file_id,
|
||||
pipeline=pipeline,
|
||||
processing_mode=processing_mode,
|
||||
bundle_type=bundle_type,
|
||||
group_id=extra.get('group_id'),
|
||||
split_order=extra.get('split_order'),
|
||||
split_total=extra.get('split_total'),
|
||||
split_heading=extra.get('split_heading'),
|
||||
page_range=extra.get('page_range'),
|
||||
page_count=extra.get('page_count'),
|
||||
section_title=extra.get('section_title'),
|
||||
section_level=extra.get('section_level'),
|
||||
config=extra.get('config'),
|
||||
settings_fingerprint=extra.get('settings_fingerprint'),
|
||||
processing_time=extra.get('processing_time'),
|
||||
pipeline_metadata=extra.get('pipeline_metadata'),
|
||||
producer=extra.get('producer', 'manual'),
|
||||
created_at=extra.get('created_at'),
|
||||
quality_score=extra.get('quality_score')
|
||||
)
|
||||
|
||||
class BundleMetadataBuilder:
|
||||
"""Helper class to build standardized bundle metadata"""
|
||||
|
||||
def __init__(self, file_id: str, pipeline: PipelineType):
|
||||
self.file_id = file_id
|
||||
self.pipeline = pipeline
|
||||
self.metadata = BundleMetadata(
|
||||
bundle_id=str(uuid.uuid4()),
|
||||
file_id=file_id,
|
||||
pipeline=pipeline,
|
||||
processing_mode=ProcessingMode.WHOLE_DOCUMENT,
|
||||
bundle_type=BundleType.SINGLE_ARTEFACT
|
||||
)
|
||||
|
||||
def set_processing_mode(self, mode: ProcessingMode) -> 'BundleMetadataBuilder':
|
||||
"""Set processing mode"""
|
||||
self.metadata.processing_mode = mode
|
||||
return self
|
||||
|
||||
def set_bundle_type(self, bundle_type: BundleType) -> 'BundleMetadataBuilder':
|
||||
"""Set bundle type"""
|
||||
self.metadata.bundle_type = bundle_type
|
||||
return self
|
||||
|
||||
def set_group_info(self, group_id: str, split_order: int = None,
|
||||
split_total: int = None, split_heading: str = None) -> 'BundleMetadataBuilder':
|
||||
"""Set grouping information for split documents"""
|
||||
self.metadata.group_id = group_id
|
||||
self.metadata.split_order = split_order
|
||||
self.metadata.split_total = split_total
|
||||
self.metadata.split_heading = split_heading
|
||||
return self
|
||||
|
||||
def set_page_info(self, page_range: List[int] = None,
|
||||
page_count: int = None) -> 'BundleMetadataBuilder':
|
||||
"""Set page information"""
|
||||
self.metadata.page_range = page_range
|
||||
self.metadata.page_count = page_count
|
||||
return self
|
||||
|
||||
def set_section_info(self, title: str = None, level: int = None) -> 'BundleMetadataBuilder':
|
||||
"""Set section information"""
|
||||
self.metadata.section_title = title
|
||||
self.metadata.section_level = level
|
||||
return self
|
||||
|
||||
def set_config(self, config: Dict[str, Any]) -> 'BundleMetadataBuilder':
|
||||
"""Set processing configuration"""
|
||||
self.metadata.config = config
|
||||
self.metadata.settings_fingerprint = self.metadata._compute_settings_fingerprint(config)
|
||||
return self
|
||||
|
||||
def set_producer(self, producer: str) -> 'BundleMetadataBuilder':
|
||||
"""Set producer information"""
|
||||
self.metadata.producer = producer
|
||||
return self
|
||||
|
||||
def set_pipeline_metadata(self, metadata: Dict[str, Any]) -> 'BundleMetadataBuilder':
|
||||
"""Set pipeline-specific metadata"""
|
||||
self.metadata.pipeline_metadata = metadata
|
||||
return self
|
||||
|
||||
def set_quality_score(self, score: float) -> 'BundleMetadataBuilder':
|
||||
"""Set quality score"""
|
||||
self.metadata.quality_score = score
|
||||
return self
|
||||
|
||||
def build(self) -> BundleMetadata:
|
||||
"""Build the final metadata"""
|
||||
return self.metadata
|
||||
|
||||
def create_standard_metadata(
|
||||
file_id: str,
|
||||
pipeline: Literal["ocr", "no_ocr", "vlm"] = "no_ocr",
|
||||
processing_mode: Literal["whole_document", "split_sections", "individual_pages", "pages", "sections", "chunks"] = "split_sections",
|
||||
config: Dict[str, Any] = None,
|
||||
group_id: str = None,
|
||||
split_order: int = None,
|
||||
split_total: int = None,
|
||||
split_heading: str = None,
|
||||
page_range: List[int] = None,
|
||||
producer: str = "auto_phase2"
|
||||
) -> BundleMetadata:
|
||||
"""
|
||||
Convenience function to create standardized metadata for common use cases.
|
||||
"""
|
||||
|
||||
# Map pipeline strings to enums
|
||||
pipeline_map = {
|
||||
"ocr": PipelineType.STANDARD,
|
||||
"no_ocr": PipelineType.STANDARD,
|
||||
"vlm": PipelineType.VLM
|
||||
}
|
||||
|
||||
# Enhanced processing mode mapping with new bundle architecture
|
||||
processing_mode_map = {
|
||||
"whole_document": ProcessingMode.WHOLE_DOCUMENT,
|
||||
"split_sections": ProcessingMode.SPLIT_SECTIONS,
|
||||
"individual_pages": ProcessingMode.INDIVIDUAL_PAGES,
|
||||
"split_by_pages": ProcessingMode.INDIVIDUAL_PAGES, # Split by pages processing
|
||||
"split_by_sections": ProcessingMode.SPLIT_SECTIONS, # Split by sections processing
|
||||
"split_by_chunks": ProcessingMode.SPLIT_SECTIONS, # Split by chunks processing
|
||||
"pages": ProcessingMode.INDIVIDUAL_PAGES, # Alias for page-based processing
|
||||
"sections": ProcessingMode.SPLIT_SECTIONS, # Alias for section-based processing
|
||||
"chunks": ProcessingMode.SPLIT_SECTIONS, # Chunks treated as sections
|
||||
}
|
||||
|
||||
# Determine bundle type based on processing mode and grouping
|
||||
if processing_mode == "whole_document":
|
||||
bundle_type = BundleType.DOCLING_BUNDLE
|
||||
else:
|
||||
bundle_type = BundleType.DOCLING_BUNDLE_SPLIT
|
||||
|
||||
builder = BundleMetadataBuilder(file_id, pipeline_map[pipeline])
|
||||
builder.set_processing_mode(processing_mode_map[processing_mode])
|
||||
builder.set_bundle_type(bundle_type)
|
||||
builder.set_producer(producer)
|
||||
|
||||
# Store original pipeline type for UI differentiation
|
||||
builder.metadata.original_pipeline_type = pipeline
|
||||
|
||||
if config:
|
||||
# Add pipeline-specific config markers
|
||||
enhanced_config = dict(config)
|
||||
if pipeline == "ocr":
|
||||
enhanced_config["do_ocr"] = True
|
||||
elif pipeline == "no_ocr":
|
||||
enhanced_config["do_ocr"] = False
|
||||
elif pipeline == "vlm":
|
||||
enhanced_config["pipeline"] = "vlm"
|
||||
|
||||
builder.set_config(enhanced_config)
|
||||
|
||||
if group_id:
|
||||
builder.set_group_info(group_id, split_order, split_total, split_heading)
|
||||
|
||||
if page_range:
|
||||
builder.set_page_info(page_range)
|
||||
|
||||
# Set section info if we have a heading
|
||||
if split_heading:
|
||||
builder.set_section_info(split_heading)
|
||||
|
||||
return builder.build()
|
||||
|
||||
def create_bundle_split_metadata(
|
||||
file_id: str,
|
||||
pipeline: Literal["ocr", "no_ocr", "vlm"] = "no_ocr",
|
||||
split_mode: Literal["split_by_pages", "split_by_sections", "split_by_chunks"] = "split_by_sections",
|
||||
config: Dict[str, Any] = None,
|
||||
group_id: str = None,
|
||||
producer: str = "auto_phase2",
|
||||
processing_data: Dict[str, Any] = None
|
||||
) -> BundleMetadata:
|
||||
"""
|
||||
Create metadata specifically for split bundle processing.
|
||||
|
||||
This is used for the new docling_bundle_split task type.
|
||||
"""
|
||||
|
||||
# Map split modes to processing modes
|
||||
mode_map = {
|
||||
"split_by_pages": "pages",
|
||||
"split_by_sections": "sections",
|
||||
"split_by_chunks": "sections" # Chunks treated as sections
|
||||
}
|
||||
|
||||
processing_mode = mode_map[split_mode]
|
||||
|
||||
metadata = create_standard_metadata(
|
||||
file_id=file_id,
|
||||
pipeline=pipeline,
|
||||
processing_mode=processing_mode,
|
||||
config=config,
|
||||
group_id=group_id,
|
||||
producer=producer
|
||||
)
|
||||
|
||||
# Add split-specific metadata
|
||||
if processing_data:
|
||||
split_metadata = {
|
||||
'split_mode': split_mode,
|
||||
'processing_data': processing_data
|
||||
}
|
||||
if metadata.pipeline_metadata:
|
||||
metadata.pipeline_metadata.update(split_metadata)
|
||||
else:
|
||||
metadata.pipeline_metadata = split_metadata
|
||||
|
||||
return metadata
|
||||
|
||||
def get_bundle_display_name(metadata: BundleMetadata) -> str:
|
||||
"""Generate a user-friendly display name for a bundle"""
|
||||
|
||||
# Use explicit display name if available
|
||||
if hasattr(metadata, 'display_name') and metadata.display_name:
|
||||
return metadata.display_name
|
||||
|
||||
# Generate based on bundle type and processing mode
|
||||
if metadata.bundle_type == BundleType.DOCLING_BUNDLE:
|
||||
return "Complete Document"
|
||||
|
||||
elif metadata.bundle_type == BundleType.DOCLING_BUNDLE_SPLIT:
|
||||
if metadata.processing_mode == ProcessingMode.INDIVIDUAL_PAGES:
|
||||
if metadata.page_range:
|
||||
return f"Page {metadata.page_range[0]}"
|
||||
return "Page Bundle"
|
||||
elif metadata.processing_mode == ProcessingMode.SPLIT_SECTIONS:
|
||||
if metadata.section_title:
|
||||
order_prefix = f"{metadata.split_order:02d}. " if metadata.split_order else ""
|
||||
page_suffix = ""
|
||||
if metadata.page_range and len(metadata.page_range) >= 2:
|
||||
page_suffix = f" (p{metadata.page_range[0]}-{metadata.page_range[1]})"
|
||||
return f"{order_prefix}{metadata.section_title}{page_suffix}"
|
||||
return f"Section {metadata.split_order or 1}"
|
||||
else:
|
||||
return "Document Bundle"
|
||||
|
||||
# Fallback
|
||||
return metadata.section_title or metadata.split_heading or f"Bundle {metadata.bundle_id[:8]}"
|
||||
|
||||
def create_organized_bundle_manifest(bundles: list, split_mode: str, pipeline: str) -> dict:
|
||||
"""
|
||||
Create an organized master manifest for split bundles with proper labeling and ordering.
|
||||
|
||||
Args:
|
||||
bundles: List of individual bundle data
|
||||
split_mode: The splitting mode used (pages, sections, chunks)
|
||||
pipeline: The pipeline type (no_ocr, ocr, vlm)
|
||||
|
||||
Returns:
|
||||
Enhanced manifest with organization metadata
|
||||
"""
|
||||
|
||||
# Sort bundles by their ordering key
|
||||
if split_mode == 'split_by_pages':
|
||||
sorted_bundles = sorted(bundles, key=lambda x: x.get('page_number', 0))
|
||||
display_name = f"{pipeline.upper()} Document Pages ({len(bundles)} pages)"
|
||||
organization = {
|
||||
'type': 'pages',
|
||||
'sort_field': 'page_number',
|
||||
'sort_order': 'asc',
|
||||
'grouping': 'individual_pages'
|
||||
}
|
||||
elif split_mode == 'split_by_sections':
|
||||
sorted_bundles = sorted(bundles, key=lambda x: x.get('split_order', 0))
|
||||
display_name = f"{pipeline.upper()} Document Sections ({len(bundles)} sections)"
|
||||
organization = {
|
||||
'type': 'sections',
|
||||
'sort_field': 'split_order',
|
||||
'sort_order': 'asc',
|
||||
'grouping': 'split_map_sections',
|
||||
'has_titles': True,
|
||||
'ordering_preserved': True
|
||||
}
|
||||
elif split_mode == 'split_by_chunks':
|
||||
sorted_bundles = sorted(bundles, key=lambda x: x.get('split_order', 0))
|
||||
display_name = f"{pipeline.upper()} Document Chunks ({len(bundles)} chunks)"
|
||||
organization = {
|
||||
'type': 'chunks',
|
||||
'sort_field': 'split_order',
|
||||
'sort_order': 'asc',
|
||||
'grouping': 'fallback_chunks'
|
||||
}
|
||||
else:
|
||||
sorted_bundles = bundles
|
||||
display_name = f"{pipeline.upper()} Document Bundles"
|
||||
organization = {
|
||||
'type': 'unknown',
|
||||
'sort_field': 'split_order',
|
||||
'sort_order': 'asc'
|
||||
}
|
||||
|
||||
return {
|
||||
'bundles': sorted_bundles,
|
||||
'display_name': display_name,
|
||||
'organization': organization,
|
||||
'total_bundles': len(bundles),
|
||||
'pipeline': pipeline,
|
||||
'split_mode': split_mode
|
||||
}
|
||||
|
||||
# Pipeline display names
|
||||
pipeline_names = {
|
||||
PipelineType.STANDARD: "Standard",
|
||||
PipelineType.VLM: "VLM",
|
||||
PipelineType.ASR: "ASR"
|
||||
}
|
||||
|
||||
pipeline_name = pipeline_names.get(metadata.pipeline, metadata.pipeline.value)
|
||||
|
||||
# OCR indication for standard pipeline
|
||||
if metadata.pipeline == PipelineType.STANDARD and metadata.config:
|
||||
ocr_enabled = metadata.config.get('do_ocr', False)
|
||||
pipeline_name = f"{pipeline_name} ({'OCR' if ocr_enabled else 'No-OCR'})"
|
||||
|
||||
# Processing mode indication
|
||||
if metadata.processing_mode == ProcessingMode.INDIVIDUAL_PAGES:
|
||||
mode = "Page-by-page"
|
||||
elif metadata.processing_mode == ProcessingMode.SPLIT_SECTIONS:
|
||||
mode = "Sections"
|
||||
else:
|
||||
mode = "Whole doc"
|
||||
|
||||
# Section or page info
|
||||
content_info = ""
|
||||
if metadata.split_heading:
|
||||
content_info = f" - {metadata.split_heading}"
|
||||
elif metadata.page_range and len(metadata.page_range) == 2:
|
||||
if metadata.page_range[0] == metadata.page_range[1]:
|
||||
content_info = f" - Page {metadata.page_range[0]}"
|
||||
else:
|
||||
content_info = f" - Pages {metadata.page_range[0]}-{metadata.page_range[1]}"
|
||||
|
||||
# Producer info
|
||||
producer_info = ""
|
||||
if metadata.producer == "auto_phase2":
|
||||
producer_info = " (Auto)"
|
||||
elif metadata.producer.startswith("auto"):
|
||||
producer_info = " (Auto)"
|
||||
|
||||
return f"{pipeline_name} {mode}{content_info}{producer_info}"
|
||||
|
||||
def group_bundles_by_metadata(bundles: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
|
||||
"""
|
||||
Group bundles by their metadata for display purposes.
|
||||
|
||||
Returns a dictionary mapping group keys to lists of bundles.
|
||||
"""
|
||||
|
||||
groups = {}
|
||||
ungrouped = []
|
||||
|
||||
for bundle in bundles:
|
||||
extra = bundle.get('extra', {})
|
||||
|
||||
# Skip bundles without the new metadata format
|
||||
if not extra.get('bundle_metadata_version'):
|
||||
ungrouped.append(bundle)
|
||||
continue
|
||||
|
||||
metadata = BundleMetadata.from_artefact_extra(
|
||||
bundle['file_id'],
|
||||
bundle['id'],
|
||||
extra
|
||||
)
|
||||
|
||||
if metadata.group_id:
|
||||
group_key = f"group:{metadata.group_id}"
|
||||
if group_key not in groups:
|
||||
groups[group_key] = []
|
||||
groups[group_key].append(bundle)
|
||||
else:
|
||||
ungrouped.append(bundle)
|
||||
|
||||
# Add ungrouped bundles as individual groups
|
||||
for bundle in ungrouped:
|
||||
single_key = f"single:{bundle['id']}"
|
||||
groups[single_key] = [bundle]
|
||||
|
||||
return groups
|
||||
@ -1,280 +0,0 @@
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
import modules.database.schemas.nodes.calendars as calendar_schemas
|
||||
import modules.database.schemas.entities as entities
|
||||
import modules.database.schemas.relationships.calendars as cal_rels
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False, entity_node=None, time_chunk_node=None):
|
||||
logger.info(f"Creating calendar for {start_date} to {end_date}")
|
||||
|
||||
logger.info("Initializing Neontology connection")
|
||||
|
||||
neon.init_neontology_connection()
|
||||
|
||||
filesystem = ClassroomCopilotFilesystem(db_name, init_run_type="school")
|
||||
|
||||
def create_tldraw_file_for_node(node, node_path):
|
||||
node_data = {
|
||||
"unique_id": node.unique_id,
|
||||
"type": node.__class__.__name__,
|
||||
"name": node.name if hasattr(node, 'name') else 'Unnamed Node'
|
||||
}
|
||||
logger.debug(f"Creating tldraw file for node: {node_data}")
|
||||
filesystem.create_default_tldraw_file(node_path, node_data)
|
||||
|
||||
created_years = {}
|
||||
created_months = {}
|
||||
created_weeks = {}
|
||||
created_days = {}
|
||||
|
||||
last_year_node = None
|
||||
last_month_node = None
|
||||
last_week_node = None
|
||||
last_day_node = None
|
||||
|
||||
calendar_nodes = {
|
||||
'calendar_node': None,
|
||||
'calendar_year_nodes': [],
|
||||
'calendar_month_nodes': [],
|
||||
'calendar_week_nodes': [],
|
||||
'calendar_day_nodes': []
|
||||
}
|
||||
|
||||
calendar_type = None
|
||||
if attach_to_calendar_node and entity_node:
|
||||
calendar_type = "entity_calendar"
|
||||
logger.info(f"Attaching calendar to entity node: {entity_node.unique_id}")
|
||||
entity_unique_id = entity_node.unique_id
|
||||
calendar_unique_id = f"Calendar_{entity_unique_id}"
|
||||
calendar_name = f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}"
|
||||
calendar_path = os.path.join(entity_node.path, "calendar")
|
||||
calendar_node = calendar_schemas.CalendarNode(
|
||||
unique_id=calendar_unique_id,
|
||||
name=calendar_name,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
path=calendar_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(calendar_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_node'] = calendar_node
|
||||
logger.info(f"Calendar node created: {calendar_node.unique_id}")
|
||||
|
||||
# Create a node tldraw file for the calendar node
|
||||
create_tldraw_file_for_node(calendar_node, calendar_path)
|
||||
|
||||
import backend.modules.database.schemas.relationships.owner_relationships as entity_cal_rels
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
entity_cal_rels.EntityHasCalendar(source=entity_node, target=calendar_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {entity_node.unique_id} to {calendar_node.unique_id}")
|
||||
if entity_node and not attach_to_calendar_node:
|
||||
calendar_type = "time_entity"
|
||||
else:
|
||||
logger.error("Invalid combination of parameters for calendar creation.")
|
||||
raise ValueError("Invalid combination of parameters for calendar creation.")
|
||||
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
year = current_date.year
|
||||
month = current_date.month
|
||||
day = current_date.day
|
||||
iso_year, iso_week, iso_weekday = current_date.isocalendar()
|
||||
|
||||
# Create directories for year, month, week, and day
|
||||
_, year_path = filesystem.create_year_directory(year, calendar_path)
|
||||
_, month_path = filesystem.create_month_directory(year, month, calendar_path)
|
||||
_, week_path = filesystem.create_week_directory(year, iso_week, calendar_path)
|
||||
_, day_path = filesystem.create_day_directory(year, month, day, calendar_path)
|
||||
|
||||
calendar_year_unique_id = f"CalendarYear_{year}"
|
||||
|
||||
if year not in created_years:
|
||||
year_node = calendar_schemas.CalendarYearNode(
|
||||
unique_id=calendar_year_unique_id,
|
||||
year=str(year),
|
||||
path=year_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(year_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_year_nodes'].append(year_node)
|
||||
created_years[year] = year_node
|
||||
create_tldraw_file_for_node(year_node, year_path)
|
||||
logger.info(f"Year node created: {year_node.unique_id}")
|
||||
|
||||
if attach_to_calendar_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.CalendarIncludesYear(source=calendar_node, target=year_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {calendar_node.unique_id} to {year_node.unique_id}")
|
||||
if last_year_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.YearFollowsYear(source=last_year_node, target=year_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_year_node.unique_id} to {year_node.unique_id}")
|
||||
last_year_node = year_node
|
||||
|
||||
calendar_month_unique_id = f"CalendarMonth_{year}_{month}"
|
||||
|
||||
month_key = f"{year}-{month}"
|
||||
if month_key not in created_months:
|
||||
month_node = calendar_schemas.CalendarMonthNode(
|
||||
unique_id=calendar_month_unique_id,
|
||||
year=str(year),
|
||||
month=str(month),
|
||||
month_name=datetime(year, month, 1).strftime('%B'),
|
||||
path=month_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(month_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_month_nodes'].append(month_node)
|
||||
created_months[month_key] = month_node
|
||||
create_tldraw_file_for_node(month_node, month_path)
|
||||
logger.info(f"Month node created: {month_node.unique_id}")
|
||||
|
||||
# Check for the end of year transition for months
|
||||
if last_month_node:
|
||||
if int(month) == 1 and int(last_month_node.month) == 12 and int(last_month_node.year) == year - 1:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.MonthFollowsMonth(source=last_month_node, target=month_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_month_node.unique_id} to {month_node.unique_id}")
|
||||
elif int(month) == int(last_month_node.month) + 1:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.MonthFollowsMonth(source=last_month_node, target=month_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_month_node.unique_id} to {month_node.unique_id}")
|
||||
last_month_node = month_node
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.YearIncludesMonth(source=year_node, target=month_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {year_node.unique_id} to {month_node.unique_id}")
|
||||
|
||||
calendar_week_unique_id = f"CalendarWeek_{iso_year}_{iso_week}"
|
||||
|
||||
week_key = f"{iso_year}-W{iso_week}"
|
||||
if week_key not in created_weeks:
|
||||
# Get the date of the first monday of the week
|
||||
week_start_date = current_date - timedelta(days=current_date.weekday())
|
||||
week_node = calendar_schemas.CalendarWeekNode(
|
||||
unique_id=calendar_week_unique_id,
|
||||
start_date=week_start_date,
|
||||
week_number=str(iso_week),
|
||||
iso_week=f"{iso_year}-W{iso_week:02}",
|
||||
path=week_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(week_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_week_nodes'].append(week_node)
|
||||
created_weeks[week_key] = week_node
|
||||
create_tldraw_file_for_node(week_node, week_path)
|
||||
logger.info(f"Week node created: {week_node.unique_id}")
|
||||
|
||||
if last_week_node and ((last_week_node.iso_week.split('-')[0] == str(iso_year) and int(last_week_node.week_number) == int(iso_week) - 1) or
|
||||
(last_week_node.iso_week.split('-')[0] != str(iso_year) and int(last_week_node.week_number) == 52 and int(iso_week) == 1)):
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.WeekFollowsWeek(source=last_week_node, target=week_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_week_node.unique_id} to {week_node.unique_id}")
|
||||
last_week_node = week_node
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.YearIncludesWeek(source=year_node, target=week_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {year_node.unique_id} to {week_node.unique_id}")
|
||||
|
||||
calendar_day_unique_id = f"CalendarDay_{year}_{month}_{day}"
|
||||
|
||||
day_key = f"{year}-{month}-{day}"
|
||||
day_node = calendar_schemas.CalendarDayNode(
|
||||
unique_id=calendar_day_unique_id,
|
||||
date=current_date,
|
||||
day_of_week=current_date.strftime('%A'),
|
||||
iso_day=f"{year}-{month:02}-{day:02}",
|
||||
path=day_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(day_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_day_nodes'].append(day_node)
|
||||
created_days[day_key] = day_node
|
||||
create_tldraw_file_for_node(day_node, day_path)
|
||||
logger.info(f"Day node created: {day_node.unique_id}")
|
||||
|
||||
if last_day_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.DayFollowsDay(source=last_day_node, target=day_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_day_node.unique_id} to {day_node.unique_id}")
|
||||
last_day_node = day_node
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.MonthIncludesDay(source=month_node, target=day_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {month_node.unique_id} to {day_node.unique_id}")
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.WeekIncludesDay(source=week_node, target=day_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {week_node.unique_id} to {day_node.unique_id}")
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
if time_chunk_node:
|
||||
time_chunk_interval = time_chunk_node
|
||||
# Get every calendar day node and create time chunks of length time_chunk_node minutes for the whole day
|
||||
for day_node in calendar_nodes['calendar_day_nodes']:
|
||||
day_path = day_node.path
|
||||
total_time_chunks_in_day = (24 * 60) / time_chunk_interval
|
||||
for i in range(total_time_chunks_in_day):
|
||||
time_chunk_unique_id = f"CalendarTimeChunk_{day_node.unique_id}_{i}"
|
||||
time_chunk_start_time = day_node.date.time() + timedelta(minutes=i * time_chunk_interval)
|
||||
time_chunk_end_time = time_chunk_start_time + timedelta(minutes=time_chunk_interval)
|
||||
time_chunk_node = calendar_schemas.CalendarTimeChunkNode(
|
||||
unique_id=time_chunk_unique_id,
|
||||
start_time=time_chunk_start_time,
|
||||
end_time=time_chunk_end_time,
|
||||
path=day_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(time_chunk_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_time_chunk_nodes'].append(time_chunk_node)
|
||||
logger.info(f"Time chunk node created: {time_chunk_node.unique_id}")
|
||||
# Create a relationship between the time chunk node and the day node
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.DayIncludesTimeChunk(source=day_node, target=time_chunk_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {day_node.unique_id} to {time_chunk_node.unique_id}")
|
||||
# Create sequential relationship between the time chunk nodes
|
||||
if i > 0:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_rels.TimeChunkFollowsTimeChunk(source=calendar_nodes['calendar_time_chunk_nodes'][i-1], target=time_chunk_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {calendar_nodes['calendar_time_chunk_nodes'][i-1].unique_id} to {time_chunk_node.unique_id}")
|
||||
|
||||
logger.info(f'Created calendar: {calendar_nodes["calendar_node"].unique_id}')
|
||||
return calendar_nodes
|
||||
@ -1,401 +0,0 @@
|
||||
from enum import Enum
|
||||
from typing import Optional, List, Dict, Any
|
||||
import logging
|
||||
|
||||
from modules.database.admin.neontology_provider import NeontologyProvider
|
||||
|
||||
class NodeLabels(Enum):
|
||||
SCHOOL = "School"
|
||||
DEPARTMENT_STRUCTURE = "DepartmentStructure"
|
||||
CURRICULUM_STRUCTURE = "CurriculumStructure"
|
||||
PASTORAL_STRUCTURE = "PastoralStructure"
|
||||
DEPARTMENT = "Department"
|
||||
KEY_STAGE = "KeyStage"
|
||||
YEAR_GROUP = "YearGroup"
|
||||
|
||||
class RelationshipTypes(Enum):
|
||||
HAS_DEPARTMENT_STRUCTURE = "HAS_DEPARTMENT_STRUCTURE"
|
||||
HAS_CURRICULUM_STRUCTURE = "HAS_CURRICULUM_STRUCTURE"
|
||||
HAS_PASTORAL_STRUCTURE = "HAS_PASTORAL_STRUCTURE"
|
||||
HAS_DEPARTMENT = "HAS_DEPARTMENT"
|
||||
INCLUDES_KEY_STAGE = "INCLUDES_KEY_STAGE"
|
||||
INCLUDES_YEAR_GROUP = "INCLUDES_YEAR_GROUP"
|
||||
|
||||
class PropertyKeys(Enum):
|
||||
UNIQUE_ID = "unique_id"
|
||||
PATH = "path"
|
||||
URN = "urn"
|
||||
ESTABLISHMENT_NUMBER = "establishment_number"
|
||||
ESTABLISHMENT_NAME = "establishment_name"
|
||||
ESTABLISHMENT_TYPE = "establishment_type"
|
||||
ESTABLISHMENT_STATUS = "establishment_status"
|
||||
PHASE_OF_EDUCATION = "phase_of_education"
|
||||
STATUTORY_LOW_AGE = "statutory_low_age"
|
||||
STATUTORY_HIGH_AGE = "statutory_high_age"
|
||||
RELIGIOUS_CHARACTER = "religious_character"
|
||||
SCHOOL_CAPACITY = "school_capacity"
|
||||
SCHOOL_WEBSITE = "school_website"
|
||||
OFSTED_RATING = "ofsted_rating"
|
||||
DEPARTMENT_NAME = "department_name"
|
||||
KEY_STAGE = "key_stage"
|
||||
KEY_STAGE_NAME = "key_stage_name"
|
||||
YEAR_GROUP = "year_group"
|
||||
YEAR_GROUP_NAME = "year_group_name"
|
||||
CREATED = "created"
|
||||
MERGED = "merged"
|
||||
|
||||
class SchemaDefinition:
|
||||
"""Class to hold schema definition queries and information"""
|
||||
|
||||
@staticmethod
|
||||
def get_schema_info() -> Dict[str, List[Dict]]:
|
||||
"""Returns a dictionary containing the schema definition for nodes and relationships."""
|
||||
return {
|
||||
"nodes": [
|
||||
{
|
||||
"label": "School",
|
||||
"description": "Represents a school entity",
|
||||
"required_properties": ["unique_id", "urn", "name"],
|
||||
"optional_properties": ["address", "postcode", "phone", "email", "website"]
|
||||
},
|
||||
{
|
||||
"label": "DepartmentStructure",
|
||||
"description": "Represents the department structure of a school",
|
||||
"required_properties": ["unique_id", "name"],
|
||||
"optional_properties": ["description", "head_of_department"]
|
||||
},
|
||||
{
|
||||
"label": "CurriculumStructure",
|
||||
"description": "Represents the curriculum structure of a school",
|
||||
"required_properties": ["unique_id", "name"],
|
||||
"optional_properties": ["description", "key_stage", "subject"]
|
||||
},
|
||||
{
|
||||
"label": "PastoralStructure",
|
||||
"description": "Represents the pastoral structure of a school",
|
||||
"required_properties": ["unique_id", "name"],
|
||||
"optional_properties": ["description", "year_group", "form_group"]
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"type": "HAS_DEPARTMENT_STRUCTURE",
|
||||
"description": "Links a school to its department structure",
|
||||
"source": "School",
|
||||
"target": "DepartmentStructure",
|
||||
"properties": ["created_at"]
|
||||
},
|
||||
{
|
||||
"type": "HAS_CURRICULUM_STRUCTURE",
|
||||
"description": "Links a school to its curriculum structure",
|
||||
"source": "School",
|
||||
"target": "CurriculumStructure",
|
||||
"properties": ["created_at"]
|
||||
},
|
||||
{
|
||||
"type": "HAS_PASTORAL_STRUCTURE",
|
||||
"description": "Links a school to its pastoral structure",
|
||||
"source": "School",
|
||||
"target": "PastoralStructure",
|
||||
"properties": ["created_at"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_schema_creation_queries() -> List[str]:
|
||||
"""Returns a list of Cypher queries to create the schema."""
|
||||
return [
|
||||
# Node Uniqueness Constraints
|
||||
f"CREATE CONSTRAINT school_unique_id IF NOT EXISTS FOR (n:{NodeLabels.SCHOOL.value}) REQUIRE n.{PropertyKeys.UNIQUE_ID.value} IS UNIQUE",
|
||||
f"CREATE CONSTRAINT department_unique_id IF NOT EXISTS FOR (n:{NodeLabels.DEPARTMENT_STRUCTURE.value}) REQUIRE n.{PropertyKeys.UNIQUE_ID.value} IS UNIQUE",
|
||||
f"CREATE CONSTRAINT curriculum_unique_id IF NOT EXISTS FOR (n:{NodeLabels.CURRICULUM_STRUCTURE.value}) REQUIRE n.{PropertyKeys.UNIQUE_ID.value} IS UNIQUE",
|
||||
f"CREATE CONSTRAINT pastoral_unique_id IF NOT EXISTS FOR (n:{NodeLabels.PASTORAL_STRUCTURE.value}) REQUIRE n.{PropertyKeys.UNIQUE_ID.value} IS UNIQUE",
|
||||
|
||||
# Indexes for Performance
|
||||
f"CREATE INDEX school_urn IF NOT EXISTS FOR (n:{NodeLabels.SCHOOL.value}) ON (n.{PropertyKeys.URN.value})",
|
||||
f"CREATE INDEX school_name IF NOT EXISTS FOR (n:{NodeLabels.SCHOOL.value}) ON (n.{PropertyKeys.ESTABLISHMENT_NAME.value})",
|
||||
f"CREATE INDEX department_name IF NOT EXISTS FOR (n:{NodeLabels.DEPARTMENT_STRUCTURE.value}) ON (n.{PropertyKeys.DEPARTMENT_NAME.value})",
|
||||
f"CREATE INDEX curriculum_name IF NOT EXISTS FOR (n:{NodeLabels.CURRICULUM_STRUCTURE.value}) ON (n.name)",
|
||||
f"CREATE INDEX pastoral_name IF NOT EXISTS FOR (n:{NodeLabels.PASTORAL_STRUCTURE.value}) ON (n.name)",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_schema_verification_queries() -> Dict[str, str]:
|
||||
"""Returns a dictionary of queries to verify the schema state."""
|
||||
return {
|
||||
"constraints": "SHOW CONSTRAINTS",
|
||||
"indexes": "SHOW INDEXES",
|
||||
"labels": "CALL db.labels()"
|
||||
}
|
||||
|
||||
class GraphNamingProvider:
|
||||
@staticmethod
|
||||
def get_school_unique_id(urn: str) -> str:
|
||||
"""Generate unique ID for a school node."""
|
||||
return f"School_{urn}"
|
||||
|
||||
@staticmethod
|
||||
def get_department_structure_unique_id(school_unique_id: str) -> str:
|
||||
"""Generate unique ID for a department structure node."""
|
||||
return f"DepartmentStructure_{school_unique_id}"
|
||||
|
||||
@staticmethod
|
||||
def get_curriculum_structure_unique_id(school_unique_id: str) -> str:
|
||||
"""Generate unique ID for a curriculum structure node."""
|
||||
return f"CurriculumStructure_{school_unique_id}"
|
||||
|
||||
@staticmethod
|
||||
def get_pastoral_structure_unique_id(school_unique_id: str) -> str:
|
||||
"""Generate unique ID for a pastoral structure node."""
|
||||
return f"PastoralStructure_{school_unique_id}"
|
||||
|
||||
@staticmethod
|
||||
def get_department_unique_id(school_unique_id: str, department_name: str) -> str:
|
||||
"""Generate unique ID for a department node."""
|
||||
return f"Department_{school_unique_id}_{department_name.replace(' ', '_')}"
|
||||
|
||||
@staticmethod
|
||||
def get_key_stage_unique_id(curriculum_structure_unique_id: str, key_stage: str) -> str:
|
||||
"""Generate unique ID for a key stage node."""
|
||||
return f"KeyStage_{curriculum_structure_unique_id}_KStg{key_stage}"
|
||||
|
||||
@staticmethod
|
||||
def get_year_group_unique_id(school_unique_id: str, year_group: int) -> str:
|
||||
"""Generate unique ID for a year group node."""
|
||||
return f"YearGroup_{school_unique_id}_YGrp{year_group}"
|
||||
|
||||
@staticmethod
|
||||
def get_school_path(database_name: str, urn: str) -> str:
|
||||
"""Generate path for a school node."""
|
||||
return f"/schools/{database_name}/{urn}"
|
||||
|
||||
@staticmethod
|
||||
def get_department_path(school_path: str, department_name: str) -> str:
|
||||
"""Generate path for a department node."""
|
||||
return f"{school_path}/departments/{department_name}"
|
||||
|
||||
@staticmethod
|
||||
def get_department_structure_path(school_path: str) -> str:
|
||||
"""Generate path for a department structure node."""
|
||||
return f"{school_path}/departments"
|
||||
|
||||
@staticmethod
|
||||
def get_curriculum_path(school_path: str) -> str:
|
||||
"""Generate path for a curriculum structure node."""
|
||||
return f"{school_path}/curriculum"
|
||||
|
||||
@staticmethod
|
||||
def get_pastoral_path(school_path: str) -> str:
|
||||
"""Generate path for a pastoral structure node."""
|
||||
return f"{school_path}/pastoral"
|
||||
|
||||
@staticmethod
|
||||
def get_key_stage_path(curriculum_path: str, key_stage: str) -> str:
|
||||
"""Generate path for a key stage node."""
|
||||
return f"{curriculum_path}/key_stage_{key_stage}"
|
||||
|
||||
@staticmethod
|
||||
def get_year_group_path(pastoral_path: str, year_group: int) -> str:
|
||||
"""Generate path for a year group node."""
|
||||
return f"{pastoral_path}/year_{year_group}"
|
||||
|
||||
@staticmethod
|
||||
def get_cypher_match_school(unique_id: str) -> str:
|
||||
"""Generate Cypher MATCH clause for finding a school node."""
|
||||
return f"MATCH (s:{NodeLabels.SCHOOL.value} {{{PropertyKeys.UNIQUE_ID.value}: $school_id}})"
|
||||
|
||||
@staticmethod
|
||||
def get_cypher_check_basic_structure() -> str:
|
||||
"""Generate Cypher query for checking basic structure existence and validity."""
|
||||
return """
|
||||
// Find the school node
|
||||
MATCH (s:{school})
|
||||
|
||||
// Check for department structure with any relationship
|
||||
OPTIONAL MATCH (s)-[r1]-(dept_struct:{dept_struct})
|
||||
|
||||
// Check for curriculum structure with any relationship
|
||||
OPTIONAL MATCH (s)-[r2]-(curr_struct:{curr_struct})
|
||||
|
||||
// Check for pastoral structure with any relationship
|
||||
OPTIONAL MATCH (s)-[r3]-(past_struct:{past_struct})
|
||||
|
||||
// Return structure information
|
||||
RETURN {{
|
||||
has_basic:
|
||||
dept_struct IS NOT NULL AND r1 IS NOT NULL AND
|
||||
curr_struct IS NOT NULL AND r2 IS NOT NULL AND
|
||||
past_struct IS NOT NULL AND r3 IS NOT NULL,
|
||||
department_structure: {{
|
||||
exists: dept_struct IS NOT NULL AND r1 IS NOT NULL
|
||||
}},
|
||||
curriculum_structure: {{
|
||||
exists: curr_struct IS NOT NULL AND r2 IS NOT NULL
|
||||
}},
|
||||
pastoral_structure: {{
|
||||
exists: past_struct IS NOT NULL AND r3 IS NOT NULL
|
||||
}}
|
||||
}} as status
|
||||
""".format(
|
||||
school=NodeLabels.SCHOOL.value,
|
||||
dept_struct=NodeLabels.DEPARTMENT_STRUCTURE.value,
|
||||
curr_struct=NodeLabels.CURRICULUM_STRUCTURE.value,
|
||||
past_struct=NodeLabels.PASTORAL_STRUCTURE.value
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_cypher_check_detailed_structure() -> str:
|
||||
"""Generate Cypher query for checking detailed structure existence and validity."""
|
||||
return """
|
||||
// Find the school node
|
||||
MATCH (s:{school} {{unique_id: $school_id}})
|
||||
|
||||
// Check for department structure and departments
|
||||
OPTIONAL MATCH (s)-[r1]-(dept_struct:{dept_struct})
|
||||
WHERE dept_struct.unique_id = 'DepartmentStructure_' + s.unique_id
|
||||
WITH s, dept_struct, r1,
|
||||
CASE WHEN dept_struct IS NOT NULL
|
||||
THEN [(dept_struct)-[r]-(d:{dept}) | d]
|
||||
ELSE []
|
||||
END as departments
|
||||
|
||||
// Check for curriculum structure and key stages
|
||||
OPTIONAL MATCH (s)-[r2]-(curr_struct:{curr_struct})
|
||||
WHERE curr_struct.unique_id = 'CurriculumStructure_' + s.unique_id
|
||||
WITH s, dept_struct, r1, departments, curr_struct, r2,
|
||||
CASE WHEN curr_struct IS NOT NULL
|
||||
THEN [(curr_struct)-[r]-(k:{key_stage}) | k]
|
||||
ELSE []
|
||||
END as key_stages
|
||||
|
||||
// Check for pastoral structure and year groups
|
||||
OPTIONAL MATCH (s)-[r3]-(past_struct:{past_struct})
|
||||
WHERE past_struct.unique_id = 'PastoralStructure_' + s.unique_id
|
||||
WITH dept_struct, r1, departments, curr_struct, r2, key_stages, past_struct, r3,
|
||||
CASE WHEN past_struct IS NOT NULL
|
||||
THEN [(past_struct)-[r]-(y:{year_group}) | y]
|
||||
ELSE []
|
||||
END as year_groups
|
||||
|
||||
// Return structure information
|
||||
RETURN {{
|
||||
has_detailed:
|
||||
dept_struct IS NOT NULL AND r1 IS NOT NULL AND size(departments) > 0 AND
|
||||
curr_struct IS NOT NULL AND r2 IS NOT NULL AND size(key_stages) > 0 AND
|
||||
past_struct IS NOT NULL AND r3 IS NOT NULL AND size(year_groups) > 0,
|
||||
department_structure: {{
|
||||
exists: dept_struct IS NOT NULL AND r1 IS NOT NULL,
|
||||
has_departments: size(departments) > 0,
|
||||
department_count: size(departments),
|
||||
node_id: dept_struct.unique_id
|
||||
}},
|
||||
curriculum_structure: {{
|
||||
exists: curr_struct IS NOT NULL AND r2 IS NOT NULL,
|
||||
has_key_stages: size(key_stages) > 0,
|
||||
key_stage_count: size(key_stages),
|
||||
node_id: curr_struct.unique_id
|
||||
}},
|
||||
pastoral_structure: {{
|
||||
exists: past_struct IS NOT NULL AND r3 IS NOT NULL,
|
||||
has_year_groups: size(year_groups) > 0,
|
||||
year_group_count: size(year_groups),
|
||||
node_id: past_struct.unique_id
|
||||
}}
|
||||
}} as status
|
||||
""".format(
|
||||
school=NodeLabels.SCHOOL.value,
|
||||
dept_struct=NodeLabels.DEPARTMENT_STRUCTURE.value,
|
||||
curr_struct=NodeLabels.CURRICULUM_STRUCTURE.value,
|
||||
past_struct=NodeLabels.PASTORAL_STRUCTURE.value,
|
||||
dept=NodeLabels.DEPARTMENT.value,
|
||||
key_stage=NodeLabels.KEY_STAGE.value,
|
||||
year_group=NodeLabels.YEAR_GROUP.value
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_schema_definition() -> SchemaDefinition:
|
||||
"""Get the schema definition instance"""
|
||||
return SchemaDefinition()
|
||||
|
||||
@staticmethod
|
||||
def get_schema_creation_queries() -> List[str]:
|
||||
"""Get queries to create the schema"""
|
||||
return SchemaDefinition.get_schema_creation_queries()
|
||||
|
||||
@staticmethod
|
||||
def get_schema_verification_queries() -> Dict[str, str]:
|
||||
"""Get queries to verify schema state"""
|
||||
return SchemaDefinition.get_schema_verification_queries()
|
||||
|
||||
@staticmethod
|
||||
def get_schema_info() -> Dict[str, List[Dict]]:
|
||||
"""Get human-readable schema information"""
|
||||
return SchemaDefinition.get_schema_info()
|
||||
|
||||
class GraphProvider:
|
||||
def __init__(self):
|
||||
"""Initialize the graph provider with Neo4j connection."""
|
||||
self.neontology = NeontologyProvider()
|
||||
self.graph_naming = GraphNamingProvider()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def check_schema_status(self, database_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Checks the current state of the schema in the specified database.
|
||||
Returns a dictionary containing information about constraints, indexes, and labels.
|
||||
"""
|
||||
try:
|
||||
verification_queries = SchemaDefinition.get_schema_verification_queries()
|
||||
expected_schema = SchemaDefinition.get_schema_info()
|
||||
|
||||
# Get current schema state
|
||||
constraints = self.neontology.run_query(verification_queries["constraints"], {}, database_name)
|
||||
indexes = self.neontology.run_query(verification_queries["indexes"], {}, database_name)
|
||||
labels = self.neontology.run_query(verification_queries["labels"], {}, database_name)
|
||||
|
||||
# Process results
|
||||
current_constraints = [c["name"] for c in constraints]
|
||||
current_indexes = [i["name"] for i in indexes]
|
||||
current_labels = [l["label"] for l in labels]
|
||||
|
||||
# Expected values
|
||||
expected_labels = [node["label"] for node in expected_schema["nodes"]]
|
||||
|
||||
return {
|
||||
"constraints": current_constraints,
|
||||
"constraints_valid": len(current_constraints) >= 4, # We expect at least 4 unique constraints
|
||||
"indexes": current_indexes,
|
||||
"indexes_valid": len(current_indexes) >= 5, # We expect at least 5 indexes
|
||||
"labels": current_labels,
|
||||
"labels_valid": all(label in current_labels for label in expected_labels)
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking schema status: {str(e)}")
|
||||
return {
|
||||
"constraints": [], "constraints_valid": False,
|
||||
"indexes": [], "indexes_valid": False,
|
||||
"labels": [], "labels_valid": False
|
||||
}
|
||||
|
||||
def initialize_schema(self, database_name: str) -> None:
|
||||
"""
|
||||
Initializes the schema for the specified database by creating all necessary
|
||||
constraints and indexes.
|
||||
"""
|
||||
try:
|
||||
creation_queries = SchemaDefinition.get_schema_creation_queries()
|
||||
|
||||
for query in creation_queries:
|
||||
self.neontology.cypher_write(query, {}, database_name)
|
||||
|
||||
self.logger.info(f"Schema initialized successfully for database {database_name}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error initializing schema: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_schema_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns the schema definition information.
|
||||
"""
|
||||
return SchemaDefinition.get_schema_info()
|
||||
@ -1,212 +0,0 @@
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
load_dotenv(find_dotenv())
|
||||
import os
|
||||
import modules.logger_tool as logger
|
||||
from modules.database.tools.neontology.graphconnection import GraphConnection, init_neontology
|
||||
from modules.database.tools.neontology.basenode import BaseNode
|
||||
from modules.database.tools.neontology.baserelationship import BaseRelationship
|
||||
from typing import Optional, Dict, Any, List
|
||||
from neo4j import Record as Neo4jRecord
|
||||
import re
|
||||
|
||||
log_name = 'api_modules_database_admin_neontology_provider'
|
||||
log_dir = os.getenv("LOG_PATH", "/logs") # Default path as fallback
|
||||
logging = logger.get_logger(
|
||||
name=log_name,
|
||||
log_level=os.getenv("LOG_LEVEL", "DEBUG"),
|
||||
log_path=log_dir,
|
||||
log_file=log_name,
|
||||
runtime=True,
|
||||
log_format='default'
|
||||
)
|
||||
|
||||
class NeontologyProvider:
|
||||
"""Provider class for managing Neontology connections and operations."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the provider with Neo4j connection details from environment."""
|
||||
self.bolt_url = os.getenv("APP_BOLT_URL")
|
||||
self.user = os.getenv("USER_NEO4J")
|
||||
self.password = os.getenv("PASSWORD_NEO4J")
|
||||
self.connection = None
|
||||
self.current_database = None
|
||||
|
||||
def _validate_database_name(self, database: str) -> str:
|
||||
"""
|
||||
Validate and format database name to handle special characters.
|
||||
|
||||
Args:
|
||||
database: The database name to validate
|
||||
|
||||
Returns:
|
||||
str: The validated database name
|
||||
|
||||
Raises:
|
||||
ValueError: If database name is invalid
|
||||
"""
|
||||
if not database:
|
||||
raise ValueError("Database name cannot be empty")
|
||||
|
||||
# Check for valid database name pattern
|
||||
# Allow letters, numbers, underscores, and dots
|
||||
if not re.match(r'^[a-zA-Z0-9_\.]+$', database):
|
||||
raise ValueError("Database name contains invalid characters")
|
||||
|
||||
# For database names with multiple dots, we need to handle them specially
|
||||
# Neo4j treats dots as special characters in some contexts
|
||||
if database.count('.') > 1:
|
||||
# Replace dots with underscores except for the first one
|
||||
parts = database.split('.')
|
||||
if len(parts) > 2:
|
||||
# Keep the first dot, replace others with underscore
|
||||
formatted_name = f"{parts[0]}.{'.'.join(parts[1:])}"
|
||||
logging.info(f"Reformatted database name from {database} to {formatted_name}")
|
||||
return formatted_name
|
||||
|
||||
return database
|
||||
|
||||
def connect(self, database: str = 'neo4j') -> None:
|
||||
"""Establish connection to Neo4j using Neontology."""
|
||||
try:
|
||||
# Validate and format database name
|
||||
formatted_database = self._validate_database_name(database)
|
||||
|
||||
# If we're switching databases, ensure we close the old connection
|
||||
if self.current_database != formatted_database and self.connection is not None:
|
||||
self.close()
|
||||
|
||||
# Initialize Neontology connection if needed
|
||||
if self.connection is None:
|
||||
init_neontology(
|
||||
neo4j_uri=self.bolt_url,
|
||||
neo4j_username=self.user,
|
||||
neo4j_password=self.password
|
||||
)
|
||||
# Get the GraphConnection instance
|
||||
self.connection = GraphConnection()
|
||||
self.current_database = formatted_database
|
||||
logging.info(f"Neontology connection initialized with host: {self.host}, port: {self.port}, database: {formatted_database}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to initialize Neontology connection: {str(e)}")
|
||||
raise
|
||||
|
||||
def reset_connection(self) -> None:
|
||||
"""Reset the connection, forcing a new one to be created on next use."""
|
||||
if self.connection:
|
||||
self.close()
|
||||
|
||||
def create_or_merge_node(self, node: BaseNode, database: str = 'neo4j', operation: str = "merge") -> None:
|
||||
"""Create or merge a node in the Neo4j database."""
|
||||
try:
|
||||
if not self.connection or self.current_database != database:
|
||||
self.connect(database)
|
||||
|
||||
if operation == "create":
|
||||
node.create(database=database)
|
||||
elif operation == "merge":
|
||||
node.merge(database=database)
|
||||
else:
|
||||
logging.error(f"Invalid operation: {operation}")
|
||||
raise ValueError(f"Invalid operation: {operation}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error in processing node: {e}")
|
||||
raise
|
||||
|
||||
def create_or_merge_relationship(self, relationship: BaseRelationship, database: str = 'neo4j', operation: str = "merge") -> None:
|
||||
"""Create or merge a relationship in the Neo4j database."""
|
||||
try:
|
||||
if not self.connection or self.current_database != database:
|
||||
self.connect(database)
|
||||
|
||||
if operation == "create":
|
||||
relationship.create(database=database)
|
||||
elif operation == "merge":
|
||||
relationship.merge(database=database)
|
||||
else:
|
||||
logging.error(f"Invalid operation: {operation}")
|
||||
raise ValueError(f"Invalid operation: {operation}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error in processing relationship: {e}")
|
||||
raise
|
||||
|
||||
def cypher_write(self, cypher: str, params: Dict[str, Any] = {}, database: str = 'neo4j') -> None:
|
||||
"""Execute a write transaction."""
|
||||
try:
|
||||
if not self.connection or self.current_database != database:
|
||||
self.connect(database)
|
||||
self.connection.cypher_write(cypher, params)
|
||||
except Exception as e:
|
||||
logging.error(f"Error in cypher write: {e}")
|
||||
raise
|
||||
|
||||
def cypher_read(self, cypher: str, params: Dict[str, Any] = {}, database: str = 'neo4j') -> Optional[Neo4jRecord]:
|
||||
"""Execute a read transaction returning a single record."""
|
||||
try:
|
||||
if not self.connection or self.current_database != database:
|
||||
self.connect(database)
|
||||
return self.connection.cypher_read(cypher, params)
|
||||
except Exception as e:
|
||||
logging.error(f"Error in cypher read: {e}")
|
||||
raise
|
||||
|
||||
def cypher_read_many(self, cypher: str, params: Dict[str, Any] = {}, database: str = 'neo4j') -> List[Neo4jRecord]:
|
||||
"""Execute a read transaction returning multiple records."""
|
||||
try:
|
||||
if not self.connection or self.current_database != database:
|
||||
self.connect(database)
|
||||
return self.connection.cypher_read_many(cypher, params)
|
||||
except Exception as e:
|
||||
logging.error(f"Error in cypher read many: {e}")
|
||||
raise
|
||||
|
||||
def run_query(self, cypher: str, params: Dict[str, Any] = {}, database: str = 'neo4j') -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Execute a Cypher query and return results as a list of dictionaries.
|
||||
This is a convenience method that handles both single and multiple record results.
|
||||
|
||||
Args:
|
||||
cypher: The Cypher query to execute
|
||||
params: Query parameters
|
||||
database: Target database name
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: Query results as a list of dictionaries
|
||||
"""
|
||||
try:
|
||||
if not self.connection or self.current_database != database:
|
||||
self.connect(database)
|
||||
|
||||
# Use cypher_read_many for consistent return type
|
||||
records = self.connection.cypher_read_many(cypher, params)
|
||||
|
||||
# Convert Neo4j records to dictionaries
|
||||
results = []
|
||||
for record in records:
|
||||
# Handle both Record and dict types
|
||||
if isinstance(record, Neo4jRecord):
|
||||
results.append(dict(record))
|
||||
else:
|
||||
results.append(record)
|
||||
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error in run_query: {e}")
|
||||
raise
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the Neontology connection."""
|
||||
if self.connection:
|
||||
# The connection will be closed when the GraphConnection instance is deleted
|
||||
self.connection = None
|
||||
self.current_database = None
|
||||
logging.info("Neontology connection closed")
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit."""
|
||||
self.close()
|
||||
@ -1,797 +0,0 @@
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
import backend.modules.database.schemas.entities as neo_entity
|
||||
import modules.database.schemas.curriculum_neo as neo_curriculum
|
||||
import modules.database.schemas.relationships.curriculum_relationships as curriculum_relationships
|
||||
import modules.database.schemas.relationships.entity_relationships as ent_rels
|
||||
import modules.database.schemas.relationships.entity_curriculum_rels as ent_cur_rels
|
||||
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
import pandas as pd
|
||||
|
||||
# Default values for nodes
|
||||
default_topic_values = {
|
||||
'topic_assessment_type': 'Null',
|
||||
'topic_type': 'Null',
|
||||
'total_number_of_lessons_for_topic': '1',
|
||||
'topic_title': 'Null'
|
||||
}
|
||||
|
||||
default_topic_lesson_values = {
|
||||
'topic_lesson_title': 'Null',
|
||||
'topic_lesson_type': 'Null',
|
||||
'topic_lesson_length': '1',
|
||||
'topic_lesson_suggested_activities': 'Null',
|
||||
'topic_lesson_skills_learned': 'Null',
|
||||
'topic_lesson_weblinks': 'Null',
|
||||
}
|
||||
|
||||
default_learning_statement_values = {
|
||||
'lesson_learning_statement': 'Null',
|
||||
'lesson_learning_statement_type': 'Student learning outcome'
|
||||
}
|
||||
|
||||
# Helper function to sort year groups numerically where possible
|
||||
def sort_year_groups(df):
|
||||
df = df.copy()
|
||||
df['YearGroupNumeric'] = pd.to_numeric(df['YearGroup'], errors='coerce')
|
||||
return df.sort_values(by='YearGroupNumeric')
|
||||
|
||||
def create_curriculum(dataframes, school_db_name, curriculum_db_name, school_node):
|
||||
|
||||
fs_handler = ClassroomCopilotFilesystem(school_db_name, init_run_type="school")
|
||||
|
||||
logger.info(f"Initialising neo4j connection...")
|
||||
neon.init_neontology_connection()
|
||||
|
||||
keystagesyllabus_df = dataframes['keystagesyllabuses']
|
||||
yeargroupsyllabus_df = dataframes['yeargroupsyllabuses']
|
||||
topic_df = dataframes['topics']
|
||||
lesson_df = dataframes['lessons']
|
||||
statement_df = dataframes['statements']
|
||||
# resource_df = dataframes['resources'] # TODO
|
||||
|
||||
node_library = {}
|
||||
node_library['key_stage_nodes'] = {}
|
||||
node_library['year_group_nodes'] = {}
|
||||
node_library['key_stage_syllabus_nodes'] = {}
|
||||
node_library['year_group_syllabus_nodes'] = {}
|
||||
node_library['topic_nodes'] = {}
|
||||
node_library['topic_lesson_nodes'] = {}
|
||||
node_library['statement_nodes'] = {}
|
||||
node_library['department_nodes'] = {}
|
||||
node_library['subject_nodes'] = {}
|
||||
curriculum_node = None
|
||||
pastoral_node = None
|
||||
key_stage_nodes_created = {}
|
||||
year_group_nodes_created = {}
|
||||
last_year_group_node = None
|
||||
last_key_stage_node = None
|
||||
|
||||
# Create Curriculum and Pastoral nodes and relationships with School in both databases
|
||||
_, curriculum_path = fs_handler.create_school_curriculum_directory(school_node.path)
|
||||
_, pastoral_path = fs_handler.create_school_pastoral_directory(school_node.path)
|
||||
|
||||
# Create Department Structure node
|
||||
department_structure_node_unique_id = f"DepartmentStructure_{school_node.unique_id}"
|
||||
department_structure_node = neo_entity.DepartmentStructureNode(
|
||||
unique_id=department_structure_node_unique_id,
|
||||
path=os.path.join(school_node.path, "departments")
|
||||
)
|
||||
# Create in school database only
|
||||
neon.create_or_merge_neontology_node(department_structure_node, database=school_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_structure_node.path, department_structure_node.to_dict())
|
||||
node_library['department_structure_node'] = department_structure_node
|
||||
|
||||
# Link Department Structure to School
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_rels.SchoolHasDepartmentStructure(source=school_node, target=department_structure_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created department structure node and linked to school")
|
||||
|
||||
# Create Curriculum Structure node
|
||||
curriculum_structure_node_unique_id = f"CurriculumStructure_{school_node.unique_id}"
|
||||
curriculum_node = neo_curriculum.CurriculumStructureNode(
|
||||
unique_id=curriculum_structure_node_unique_id,
|
||||
path=curriculum_path
|
||||
)
|
||||
# Create in school database only
|
||||
neon.create_or_merge_neontology_node(curriculum_node, database=school_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(curriculum_node.path, curriculum_node.to_dict())
|
||||
node_library['curriculum_node'] = curriculum_node
|
||||
|
||||
# Create relationship in school database only
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_cur_rels.SchoolHasCurriculumStructure(source=school_node, target=curriculum_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created curriculum node and relationship with school")
|
||||
|
||||
# Create Pastoral Structure node
|
||||
pastoral_structure_node_unique_id = f"PastoralStructure_{school_node.unique_id}"
|
||||
pastoral_node = neo_curriculum.PastoralStructureNode(
|
||||
unique_id=pastoral_structure_node_unique_id,
|
||||
path=pastoral_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(pastoral_node, database=school_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(pastoral_node.path, pastoral_node.to_dict())
|
||||
node_library['pastoral_node'] = pastoral_node
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_cur_rels.SchoolHasPastoralStructure(source=school_node, target=pastoral_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created pastoral node and relationship with school")
|
||||
|
||||
# Create departments and subjects
|
||||
# First get unique departments
|
||||
unique_departments = keystagesyllabus_df['Department'].dropna().unique()
|
||||
|
||||
for department_name in unique_departments:
|
||||
department_unique_id = f"Department_{school_node.unique_id}_{department_name.replace(' ', '_')}"
|
||||
_, department_path = fs_handler.create_school_department_directory(school_node.path, department_name)
|
||||
|
||||
department_node = neo_entity.DepartmentNode(
|
||||
unique_id=department_unique_id,
|
||||
department_name=department_name,
|
||||
path=department_path
|
||||
)
|
||||
# Create department in school database only
|
||||
neon.create_or_merge_neontology_node(department_node, database=school_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_node.path, department_node.to_dict())
|
||||
node_library['department_nodes'][department_name] = department_node
|
||||
|
||||
# Link department to department structure in school database
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_rels.DepartmentStructureHasDepartment(source=department_structure_node, target=department_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created department node for {department_name} and linked to department structure")
|
||||
|
||||
# Create subjects and link to departments
|
||||
# First get unique subjects from key stage syllabuses (which have department info)
|
||||
unique_subjects = keystagesyllabus_df[['Subject', 'SubjectCode', 'Department']].drop_duplicates()
|
||||
|
||||
# Then add any additional subjects from year group syllabuses (without department info)
|
||||
additional_subjects = yeargroupsyllabus_df[['Subject', 'SubjectCode']].drop_duplicates()
|
||||
additional_subjects = additional_subjects[~additional_subjects['SubjectCode'].isin(unique_subjects['SubjectCode'])]
|
||||
|
||||
# Process subjects from key stage syllabuses first (these have department info)
|
||||
for _, subject_row in unique_subjects.iterrows():
|
||||
subject_unique_id = f"Subject_{school_node.unique_id}_{subject_row['SubjectCode']}"
|
||||
department_node = node_library['department_nodes'].get(subject_row['Department'])
|
||||
if not department_node:
|
||||
logger.warning(f"No department found for subject {subject_row['Subject']} with code {subject_row['SubjectCode']}")
|
||||
continue
|
||||
|
||||
_, subject_path = fs_handler.create_department_subject_directory(
|
||||
department_node.path,
|
||||
subject_row['Subject'] # Use full subject name instead of SubjectCode
|
||||
)
|
||||
logger.info(f"Created subject directory for {subject_path}")
|
||||
|
||||
subject_node = neo_curriculum.SubjectNode(
|
||||
unique_id=subject_unique_id,
|
||||
subject_code=subject_row['SubjectCode'],
|
||||
subject_name=subject_row['Subject'],
|
||||
path=subject_path
|
||||
)
|
||||
# Create subject in both databases
|
||||
neon.create_or_merge_neontology_node(subject_node, database=school_db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(subject_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(subject_node.path, subject_node.to_dict())
|
||||
node_library['subject_nodes'][subject_row['Subject']] = subject_node
|
||||
|
||||
# Link subject to department in school database only
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_rels.DepartmentManagesSubject(source=department_node, target=subject_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created subject node for {subject_row['Subject']} and linked to department {subject_row['Department']}")
|
||||
|
||||
# Process any additional subjects from year group syllabuses (these won't have department info)
|
||||
for _, subject_row in additional_subjects.iterrows():
|
||||
subject_unique_id = f"Subject_{school_node.unique_id}_{subject_row['SubjectCode']}"
|
||||
# Create in a special "Unassigned" department
|
||||
unassigned_dept_name = "Unassigned Department"
|
||||
if unassigned_dept_name not in node_library['department_nodes']:
|
||||
_, dept_path = fs_handler.create_school_department_directory(school_node.path, unassigned_dept_name)
|
||||
department_node = neo_entity.DepartmentNode(
|
||||
unique_id=f"Department_{school_node.unique_id}_Unassigned",
|
||||
department_name=unassigned_dept_name,
|
||||
path=dept_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(department_node, database=school_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_node.path, department_node.to_dict())
|
||||
node_library['department_nodes'][unassigned_dept_name] = department_node
|
||||
|
||||
# Link unassigned department to department structure
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_rels.DepartmentStructureHasDepartment(source=department_structure_node, target=department_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created unassigned department node and linked to department structure")
|
||||
|
||||
_, subject_path = fs_handler.create_department_subject_directory(
|
||||
node_library['department_nodes'][unassigned_dept_name].path,
|
||||
subject_row['Subject']
|
||||
)
|
||||
|
||||
subject_node = neo_curriculum.SubjectNode(
|
||||
unique_id=subject_unique_id,
|
||||
subject_code=subject_row['SubjectCode'],
|
||||
subject_name=subject_row['Subject'],
|
||||
path=subject_path
|
||||
)
|
||||
# Create subject in both databases
|
||||
neon.create_or_merge_neontology_node(subject_node, database=school_db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(subject_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(subject_node.path, subject_node.to_dict())
|
||||
node_library['subject_nodes'][subject_row['Subject']] = subject_node
|
||||
|
||||
# Link subject to unassigned department in school database only
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ent_rels.DepartmentManagesSubject(
|
||||
source=node_library['department_nodes'][unassigned_dept_name],
|
||||
target=subject_node
|
||||
),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.warning(f"Created subject node for {subject_row['Subject']} in unassigned department")
|
||||
|
||||
# Process key stages and syllabuses
|
||||
logger.info(f"Processing key stages")
|
||||
last_key_stage_node = None
|
||||
# Track last syllabus nodes per subject
|
||||
last_key_stage_syllabus_nodes = {} # Dictionary to track last key stage syllabus node per subject
|
||||
last_year_group_syllabus_nodes = {} # Dictionary to track last year group syllabus node per subject
|
||||
topics_processed = set() # Track which topics have been processed
|
||||
lessons_processed = set() # Track which lessons have been processed
|
||||
statements_processed = set() # Track which statements have been processed
|
||||
|
||||
# First create all key stage nodes and key stage syllabus nodes
|
||||
for index, ks_row in keystagesyllabus_df.sort_values('KeyStage').iterrows():
|
||||
key_stage = str(ks_row['KeyStage'])
|
||||
logger.debug(f"Processing key stage syllabus row - Subject: {ks_row['Subject']}, Key Stage: {key_stage}")
|
||||
|
||||
subject_node = node_library['subject_nodes'].get(ks_row['Subject'])
|
||||
if not subject_node:
|
||||
logger.warning(f"No subject node found for subject {ks_row['Subject']}")
|
||||
continue
|
||||
|
||||
if key_stage not in key_stage_nodes_created:
|
||||
key_stage_node_unique_id = f"KeyStage_{curriculum_node.unique_id}_KStg{key_stage}"
|
||||
key_stage_node = neo_curriculum.KeyStageNode(
|
||||
unique_id=key_stage_node_unique_id,
|
||||
key_stage_name=f"Key Stage {key_stage}",
|
||||
key_stage=str(key_stage),
|
||||
path=os.path.join(curriculum_node.path, "key_stages", f"KS{key_stage}")
|
||||
)
|
||||
# Create key stage node in both databases
|
||||
neon.create_or_merge_neontology_node(key_stage_node, database=school_db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(key_stage_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(key_stage_node.path, key_stage_node.to_dict())
|
||||
key_stage_nodes_created[key_stage] = key_stage_node
|
||||
node_library['key_stage_nodes'][key_stage] = key_stage_node
|
||||
|
||||
# Create relationship with curriculum structure in school database only
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.CurriculumStructureIncludesKeyStage(source=curriculum_node, target=key_stage_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created key stage node {key_stage_node_unique_id} and relationship with curriculum structure")
|
||||
|
||||
# Create sequential relationship between key stages in both databases
|
||||
if last_key_stage_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageFollowsKeyStage(source=last_key_stage_node, target=key_stage_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageFollowsKeyStage(source=last_key_stage_node, target=key_stage_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between key stages {last_key_stage_node.unique_id} and {key_stage_node.unique_id}")
|
||||
last_key_stage_node = key_stage_node
|
||||
|
||||
# Create key stage syllabus under the subject's curriculum directory
|
||||
_, key_stage_syllabus_path = fs_handler.create_curriculum_key_stage_syllabus_directory(
|
||||
curriculum_node.path,
|
||||
key_stage,
|
||||
ks_row['Subject'],
|
||||
ks_row['ID']
|
||||
)
|
||||
logger.debug(f"Creating key stage syllabus node for {ks_row['Subject']} KS{key_stage} with ID {ks_row['ID']}")
|
||||
|
||||
key_stage_syllabus_node_unique_id = f"KeyStageSyllabus_{curriculum_node.unique_id}_{ks_row['Title'].replace(' ', '')}"
|
||||
key_stage_syllabus_node = neo_curriculum.KeyStageSyllabusNode(
|
||||
unique_id=key_stage_syllabus_node_unique_id,
|
||||
ks_syllabus_id=ks_row['ID'],
|
||||
ks_syllabus_name=ks_row['Title'],
|
||||
ks_syllabus_key_stage=str(ks_row['KeyStage']),
|
||||
ks_syllabus_subject=ks_row['Subject'],
|
||||
ks_syllabus_subject_code=ks_row['Subject'],
|
||||
path=key_stage_syllabus_path
|
||||
)
|
||||
# Create key stage syllabus node in both databases
|
||||
neon.create_or_merge_neontology_node(key_stage_syllabus_node, database=school_db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(key_stage_syllabus_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(key_stage_syllabus_node.path, key_stage_syllabus_node.to_dict())
|
||||
node_library['key_stage_syllabus_nodes'][ks_row['ID']] = key_stage_syllabus_node
|
||||
logger.debug(f"Created key stage syllabus node {key_stage_syllabus_node_unique_id} for {ks_row['Subject']} KS{key_stage}")
|
||||
|
||||
# Link key stage syllabus to its subject in both databases
|
||||
if subject_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.SubjectHasKeyStageSyllabus(source=subject_node, target=key_stage_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.SubjectHasKeyStageSyllabus(source=subject_node, target=key_stage_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between subject {subject_node.unique_id} and key stage syllabus {key_stage_syllabus_node.unique_id}")
|
||||
|
||||
# Link key stage syllabus to its key stage in both databases
|
||||
key_stage_node = key_stage_nodes_created.get(key_stage)
|
||||
if key_stage_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageIncludesKeyStageSyllabus(source=key_stage_node, target=key_stage_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageIncludesKeyStageSyllabus(source=key_stage_node, target=key_stage_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between key stage {key_stage_node.unique_id} and key stage syllabus {key_stage_syllabus_node.unique_id}")
|
||||
|
||||
# Create sequential relationship between key stage syllabuses in both databases
|
||||
last_key_stage_syllabus_node = last_key_stage_syllabus_nodes.get(ks_row['Subject'])
|
||||
if last_key_stage_syllabus_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageSyllabusFollowsKeyStageSyllabus(source=last_key_stage_syllabus_node, target=key_stage_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageSyllabusFollowsKeyStageSyllabus(source=last_key_stage_syllabus_node, target=key_stage_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between key stage syllabuses {last_key_stage_syllabus_node.unique_id} and {key_stage_syllabus_node.unique_id}")
|
||||
last_key_stage_syllabus_nodes[ks_row['Subject']] = key_stage_syllabus_node
|
||||
|
||||
# Now process year groups and their syllabuses
|
||||
for index, ks_row in keystagesyllabus_df.sort_values('KeyStage').iterrows():
|
||||
key_stage = str(ks_row['KeyStage'])
|
||||
related_yeargroups = sort_year_groups(yeargroupsyllabus_df[yeargroupsyllabus_df['KeyStage'] == ks_row['KeyStage']])
|
||||
|
||||
logger.info(f"Processing year groups for key stage {key_stage}")
|
||||
for yg_index, yg_row in related_yeargroups.iterrows():
|
||||
year_group = yg_row['YearGroup']
|
||||
subject_code = yg_row['SubjectCode']
|
||||
numeric_year_group = pd.to_numeric(year_group, errors='coerce')
|
||||
|
||||
if pd.notna(numeric_year_group):
|
||||
numeric_year_group = int(numeric_year_group)
|
||||
if numeric_year_group not in year_group_nodes_created:
|
||||
# Create year group directory under pastoral structure
|
||||
_, year_group_path = fs_handler.create_pastoral_year_group_directory(pastoral_node.path, year_group)
|
||||
logger.info(f"Created year group directory for {year_group_path}")
|
||||
|
||||
year_group_node_unique_id = f"YearGroup_{school_node.unique_id}_YGrp{numeric_year_group}"
|
||||
year_group_node = neo_curriculum.YearGroupNode(
|
||||
unique_id=year_group_node_unique_id,
|
||||
year_group=str(numeric_year_group),
|
||||
year_group_name=f"Year {numeric_year_group}",
|
||||
path=year_group_path
|
||||
)
|
||||
# Create year group node in both databases but use same directory
|
||||
neon.create_or_merge_neontology_node(year_group_node, database=school_db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(year_group_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(year_group_node.path, year_group_node.to_dict())
|
||||
|
||||
# Create sequential relationship between year groups in both databases
|
||||
if last_year_group_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupFollowsYearGroup(source=last_year_group_node, target=year_group_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupFollowsYearGroup(source=last_year_group_node, target=year_group_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between year groups {last_year_group_node.unique_id} and {year_group_node.unique_id} across key stages")
|
||||
last_year_group_node = year_group_node
|
||||
|
||||
# Create relationship with Pastoral Structure in school database only
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.PastoralStructureIncludesYearGroup(source=pastoral_node, target=year_group_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created year group node {year_group_node_unique_id} and relationship with pastoral structure")
|
||||
|
||||
year_group_nodes_created[numeric_year_group] = year_group_node
|
||||
node_library['year_group_nodes'][str(numeric_year_group)] = year_group_node
|
||||
|
||||
# Curriculum specific database initialisation begins here
|
||||
# Create year group syllabus nodes in both databases
|
||||
year_group_node = year_group_nodes_created.get(numeric_year_group)
|
||||
if year_group_node:
|
||||
# Create syllabus directory under curriculum structure
|
||||
_, year_group_syllabus_path = fs_handler.create_curriculum_year_group_syllabus_directory(
|
||||
curriculum_node.path,
|
||||
yg_row['Subject'],
|
||||
year_group,
|
||||
yg_row['ID']
|
||||
)
|
||||
logger.info(f"Created year group syllabus directory for {year_group_syllabus_path}")
|
||||
|
||||
year_group_syllabus_node_unique_id = f"YearGroupSyllabus_{school_node.unique_id}_{yg_row['ID']}"
|
||||
year_group_syllabus_node = neo_curriculum.YearGroupSyllabusNode(
|
||||
unique_id=year_group_syllabus_node_unique_id,
|
||||
yr_syllabus_id=yg_row['ID'],
|
||||
yr_syllabus_name=yg_row['Title'],
|
||||
yr_syllabus_year_group=str(yg_row['YearGroup']),
|
||||
yr_syllabus_subject=yg_row['Subject'],
|
||||
yr_syllabus_subject_code=yg_row['Subject'],
|
||||
path=year_group_syllabus_path
|
||||
)
|
||||
|
||||
# Create year group syllabus node in both databases but use same directory
|
||||
neon.create_or_merge_neontology_node(year_group_syllabus_node, database=school_db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(year_group_syllabus_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(year_group_syllabus_node.path, year_group_syllabus_node.to_dict())
|
||||
node_library['year_group_syllabus_nodes'][yg_row['ID']] = year_group_syllabus_node
|
||||
|
||||
# Create sequential relationship between year group syllabuses in both databases
|
||||
last_year_group_syllabus_node = last_year_group_syllabus_nodes.get(yg_row['Subject'])
|
||||
# Only create sequential relationship if this year group is higher than the last one
|
||||
if last_year_group_syllabus_node:
|
||||
last_year = pd.to_numeric(last_year_group_syllabus_node.yr_syllabus_year_group, errors='coerce')
|
||||
current_year = pd.to_numeric(year_group_syllabus_node.yr_syllabus_year_group, errors='coerce')
|
||||
if pd.notna(last_year) and pd.notna(current_year) and current_year > last_year:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupSyllabusFollowsYearGroupSyllabus(source=last_year_group_syllabus_node, target=year_group_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupSyllabusFollowsYearGroupSyllabus(source=last_year_group_syllabus_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between year group syllabuses {last_year_group_syllabus_node.unique_id} and {year_group_syllabus_node.unique_id}")
|
||||
last_year_group_syllabus_nodes[yg_row['Subject']] = year_group_syllabus_node
|
||||
|
||||
# Create relationships in both databases using MATCH to avoid cartesian products
|
||||
subject_node = node_library['subject_nodes'].get(yg_row['Subject'])
|
||||
if subject_node:
|
||||
# Link to subject
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.SubjectHasYearGroupSyllabus(source=subject_node, target=year_group_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.SubjectHasYearGroupSyllabus(source=subject_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between subject {subject_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
|
||||
# Link to year group
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupHasYearGroupSyllabus(source=year_group_node, target=year_group_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupHasYearGroupSyllabus(source=year_group_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between year group {year_group_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
|
||||
# Link to key stage syllabus if it exists for the same subject
|
||||
key_stage_syllabus_node = node_library['key_stage_syllabus_nodes'].get(ks_row['ID'])
|
||||
if key_stage_syllabus_node and yg_row['Subject'] == ks_row['Subject']:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageSyllabusIncludesYearGroupSyllabus(source=key_stage_syllabus_node, target=year_group_syllabus_node),
|
||||
database=school_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageSyllabusIncludesYearGroupSyllabus(source=key_stage_syllabus_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between key stage syllabus {key_stage_syllabus_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
|
||||
# Processing of curriculum topic begins here
|
||||
# Process topics for this year group syllabus only if not already processed
|
||||
topics_for_syllabus = topic_df[topic_df['SyllabusYearID'] == yg_row['ID']]
|
||||
for _, topic_row in topics_for_syllabus.iterrows():
|
||||
if topic_row['TopicID'] in topics_processed:
|
||||
continue
|
||||
topics_processed.add(topic_row['TopicID'])
|
||||
|
||||
# Get the correct subject from the topic row
|
||||
topic_subject = topic_row['SyllabusSubject']
|
||||
topic_key_stage = topic_row['SyllabusKeyStage']
|
||||
|
||||
logger.debug(f"Processing topic {topic_row['TopicID']} for subject {topic_subject} and key stage {topic_key_stage}")
|
||||
logger.debug(f"Available key stage syllabus nodes: {[node.ks_syllabus_subject + '_KS' + node.ks_syllabus_key_stage for node in node_library['key_stage_syllabus_nodes'].values()]}")
|
||||
|
||||
# Find the key stage syllabus node by iterating through all nodes
|
||||
matching_syllabus_node = None
|
||||
for syllabus_node in node_library['key_stage_syllabus_nodes'].values():
|
||||
logger.debug(f"Checking syllabus node - Subject: {syllabus_node.ks_syllabus_subject}, Key Stage: {syllabus_node.ks_syllabus_key_stage}")
|
||||
logger.debug(f"Comparing with - Subject: {topic_subject}, Key Stage: {str(topic_key_stage)}")
|
||||
logger.debug(f"Types - Node Subject: {type(syllabus_node.ks_syllabus_subject)}, Topic Subject: {type(topic_subject)}")
|
||||
logger.debug(f"Types - Node Key Stage: {type(syllabus_node.ks_syllabus_key_stage)}, Topic Key Stage: {type(str(topic_key_stage))}")
|
||||
|
||||
if (syllabus_node.ks_syllabus_subject == topic_subject and
|
||||
syllabus_node.ks_syllabus_key_stage == str(topic_key_stage)):
|
||||
matching_syllabus_node = syllabus_node
|
||||
logger.debug(f"Found matching syllabus node: {syllabus_node.unique_id}")
|
||||
break
|
||||
|
||||
if not matching_syllabus_node:
|
||||
logger.warning(f"No key stage syllabus node found for subject {topic_subject} and key stage {topic_key_stage}, skipping topic creation")
|
||||
continue
|
||||
|
||||
|
||||
_, topic_path = fs_handler.create_curriculum_topic_directory(matching_syllabus_node.path, topic_row['TopicID'])
|
||||
logger.info(f"Created topic directory for {topic_path}")
|
||||
|
||||
topic_node_unique_id = f"Topic_{matching_syllabus_node.unique_id}_{topic_row['TopicID']}"
|
||||
topic_node = neo_curriculum.TopicNode(
|
||||
unique_id=topic_node_unique_id,
|
||||
topic_id=topic_row['TopicID'],
|
||||
topic_title=topic_row.get('TopicTitle', default_topic_values['topic_title']),
|
||||
total_number_of_lessons_for_topic=str(topic_row.get('TotalNumberOfLessonsForTopic', default_topic_values['total_number_of_lessons_for_topic'])),
|
||||
topic_type=topic_row.get('TopicType', default_topic_values['topic_type']),
|
||||
topic_assessment_type=topic_row.get('TopicAssessmentType', default_topic_values['topic_assessment_type']),
|
||||
path=topic_path
|
||||
)
|
||||
# Create topic node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(topic_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(topic_node.path, topic_node.to_dict())
|
||||
node_library['topic_nodes'][topic_row['TopicID']] = topic_node
|
||||
|
||||
# Link topic to key stage syllabus as well as year group syllabus
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageSyllabusIncludesTopic(source=matching_syllabus_node, target=topic_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.YearGroupSyllabusIncludesTopic(source=year_group_syllabus_node, target=topic_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationships between topic {topic_node_unique_id} and key stage syllabus {matching_syllabus_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
|
||||
# Process lessons for this topic only if not already processed
|
||||
lessons_for_topic = lesson_df[
|
||||
(lesson_df['TopicID'] == topic_row['TopicID']) &
|
||||
(lesson_df['SyllabusSubject'] == topic_subject)
|
||||
].copy()
|
||||
lessons_for_topic.loc[:, 'Lesson'] = lessons_for_topic['Lesson'].astype(str)
|
||||
lessons_for_topic = lessons_for_topic.sort_values('Lesson')
|
||||
|
||||
previous_lesson_node = None
|
||||
for _, lesson_row in lessons_for_topic.iterrows():
|
||||
if lesson_row['LessonID'] in lessons_processed:
|
||||
continue
|
||||
lessons_processed.add(lesson_row['LessonID'])
|
||||
|
||||
_, lesson_path = fs_handler.create_curriculum_lesson_directory(topic_path, lesson_row['LessonID'])
|
||||
logger.info(f"Created lesson directory for {lesson_path}")
|
||||
|
||||
lesson_data = {
|
||||
'unique_id': f"TopicLesson_{topic_node_unique_id}_{lesson_row['LessonID']}",
|
||||
'topic_lesson_id': lesson_row['LessonID'],
|
||||
'topic_lesson_title': lesson_row.get('LessonTitle', default_topic_lesson_values['topic_lesson_title']),
|
||||
'topic_lesson_type': lesson_row.get('LessonType', default_topic_lesson_values['topic_lesson_type']),
|
||||
'topic_lesson_length': str(lesson_row.get('SuggestedNumberOfPeriodsForLesson', default_topic_lesson_values['topic_lesson_length'])),
|
||||
'topic_lesson_suggested_activities': lesson_row.get('SuggestedActivities', default_topic_lesson_values['topic_lesson_suggested_activities']),
|
||||
'topic_lesson_skills_learned': lesson_row.get('SkillsLearned', default_topic_lesson_values['topic_lesson_skills_learned']),
|
||||
'topic_lesson_weblinks': lesson_row.get('WebLinks', default_topic_lesson_values['topic_lesson_weblinks']),
|
||||
'path': lesson_path
|
||||
}
|
||||
for key, value in lesson_data.items():
|
||||
if pd.isna(value):
|
||||
lesson_data[key] = default_topic_lesson_values.get(key, 'Null')
|
||||
|
||||
lesson_node = neo_curriculum.TopicLessonNode(**lesson_data)
|
||||
# Create lesson node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(lesson_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(lesson_node.path, lesson_node.to_dict())
|
||||
node_library['topic_lesson_nodes'][lesson_row['LessonID']] = lesson_node
|
||||
|
||||
# Link lesson to topic
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.TopicIncludesTopicLesson(source=topic_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created lesson node {lesson_node.unique_id} and relationship with topic {topic_node.unique_id}")
|
||||
|
||||
# Create sequential relationships between lessons
|
||||
if lesson_row['Lesson'].isdigit() and previous_lesson_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.TopicLessonFollowsTopicLesson(source=previous_lesson_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between lessons {previous_lesson_node.unique_id} and {lesson_node.unique_id}")
|
||||
previous_lesson_node = lesson_node
|
||||
|
||||
# Process learning statements for this lesson only if not already processed
|
||||
statements_for_lesson = statement_df[
|
||||
(statement_df['LessonID'] == lesson_row['LessonID']) &
|
||||
(statement_df['SyllabusSubject'] == topic_subject)
|
||||
]
|
||||
for _, statement_row in statements_for_lesson.iterrows():
|
||||
if statement_row['StatementID'] in statements_processed:
|
||||
continue
|
||||
statements_processed.add(statement_row['StatementID'])
|
||||
|
||||
_, statement_path = fs_handler.create_curriculum_learning_statement_directory(lesson_path, statement_row['StatementID'])
|
||||
|
||||
statement_data = {
|
||||
'unique_id': f"LearningStatement_{lesson_node.unique_id}_{statement_row['StatementID']}",
|
||||
'lesson_learning_statement_id': statement_row['StatementID'],
|
||||
'lesson_learning_statement': statement_row.get('LearningStatement', default_learning_statement_values['lesson_learning_statement']),
|
||||
'lesson_learning_statement_type': statement_row.get('StatementType', default_learning_statement_values['lesson_learning_statement_type']),
|
||||
'path': statement_path
|
||||
}
|
||||
for key in statement_data:
|
||||
if pd.isna(statement_data[key]):
|
||||
statement_data[key] = default_learning_statement_values.get(key, 'Null')
|
||||
|
||||
statement_node = neo_curriculum.LearningStatementNode(**statement_data)
|
||||
# Create statement node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(statement_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(statement_node.path, statement_node.to_dict())
|
||||
node_library['statement_nodes'][statement_row['StatementID']] = statement_node
|
||||
|
||||
# Link learning statement to lesson
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.LessonIncludesLearningStatement(source=lesson_node, target=statement_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created learning statement node {statement_node.unique_id} and relationship with lesson {lesson_node.unique_id}")
|
||||
else:
|
||||
logger.warning(f"No year group node found for year group {year_group}, skipping syllabus creation")
|
||||
|
||||
# After processing all year groups and their syllabuses, process any remaining topics
|
||||
logger.info("Processing topics without year groups")
|
||||
for _, topic_row in topic_df.iterrows():
|
||||
if topic_row['TopicID'] in topics_processed:
|
||||
continue
|
||||
|
||||
topic_subject = topic_row['SyllabusSubject']
|
||||
topic_key_stage = topic_row['SyllabusKeyStage']
|
||||
|
||||
logger.debug(f"Processing topic {topic_row['TopicID']} for subject {topic_subject} and key stage {topic_key_stage} without year group")
|
||||
|
||||
# Find the key stage syllabus node
|
||||
matching_syllabus_node = None
|
||||
for syllabus_node in node_library['key_stage_syllabus_nodes'].values():
|
||||
if (syllabus_node.ks_syllabus_subject == topic_subject and
|
||||
syllabus_node.ks_syllabus_key_stage == str(topic_key_stage)):
|
||||
matching_syllabus_node = syllabus_node
|
||||
break
|
||||
|
||||
if not matching_syllabus_node:
|
||||
logger.warning(f"No key stage syllabus node found for subject {topic_subject} and key stage {topic_key_stage}, skipping topic creation")
|
||||
continue
|
||||
|
||||
_, topic_path = fs_handler.create_curriculum_topic_directory(matching_syllabus_node.path, topic_row['TopicID'])
|
||||
logger.info(f"Created topic directory for {topic_path}")
|
||||
|
||||
topic_node_unique_id = f"Topic_{matching_syllabus_node.unique_id}_{topic_row['TopicID']}"
|
||||
topic_node = neo_curriculum.TopicNode(
|
||||
unique_id=topic_node_unique_id,
|
||||
topic_id=topic_row['TopicID'],
|
||||
topic_title=topic_row.get('TopicTitle', default_topic_values['topic_title']),
|
||||
total_number_of_lessons_for_topic=str(topic_row.get('TotalNumberOfLessonsForTopic', default_topic_values['total_number_of_lessons_for_topic'])),
|
||||
topic_type=topic_row.get('TopicType', default_topic_values['topic_type']),
|
||||
topic_assessment_type=topic_row.get('TopicAssessmentType', default_topic_values['topic_assessment_type']),
|
||||
path=topic_path
|
||||
)
|
||||
# Create topic node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(topic_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(topic_node.path, topic_node.to_dict())
|
||||
node_library['topic_nodes'][topic_row['TopicID']] = topic_node
|
||||
topics_processed.add(topic_row['TopicID'])
|
||||
|
||||
# Link topic to key stage syllabus
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.KeyStageSyllabusIncludesTopic(source=matching_syllabus_node, target=topic_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between topic {topic_node_unique_id} and key stage syllabus {matching_syllabus_node.unique_id}")
|
||||
|
||||
# Process lessons for this topic
|
||||
lessons_for_topic = lesson_df[
|
||||
(lesson_df['TopicID'] == topic_row['TopicID']) &
|
||||
(lesson_df['SyllabusSubject'] == topic_subject)
|
||||
].copy()
|
||||
lessons_for_topic.loc[:, 'Lesson'] = lessons_for_topic['Lesson'].astype(str)
|
||||
lessons_for_topic = lessons_for_topic.sort_values('Lesson')
|
||||
|
||||
previous_lesson_node = None
|
||||
for _, lesson_row in lessons_for_topic.iterrows():
|
||||
if lesson_row['LessonID'] in lessons_processed:
|
||||
continue
|
||||
lessons_processed.add(lesson_row['LessonID'])
|
||||
|
||||
_, lesson_path = fs_handler.create_curriculum_lesson_directory(topic_path, lesson_row['LessonID'])
|
||||
logger.info(f"Created lesson directory for {lesson_path}")
|
||||
|
||||
lesson_data = {
|
||||
'unique_id': f"TopicLesson_{topic_node_unique_id}_{lesson_row['LessonID']}",
|
||||
'topic_lesson_id': lesson_row['LessonID'],
|
||||
'topic_lesson_title': lesson_row.get('LessonTitle', default_topic_lesson_values['topic_lesson_title']),
|
||||
'topic_lesson_type': lesson_row.get('LessonType', default_topic_lesson_values['topic_lesson_type']),
|
||||
'topic_lesson_length': str(lesson_row.get('SuggestedNumberOfPeriodsForLesson', default_topic_lesson_values['topic_lesson_length'])),
|
||||
'topic_lesson_suggested_activities': lesson_row.get('SuggestedActivities', default_topic_lesson_values['topic_lesson_suggested_activities']),
|
||||
'topic_lesson_skills_learned': lesson_row.get('SkillsLearned', default_topic_lesson_values['topic_lesson_skills_learned']),
|
||||
'topic_lesson_weblinks': lesson_row.get('WebLinks', default_topic_lesson_values['topic_lesson_weblinks']),
|
||||
'path': lesson_path
|
||||
}
|
||||
for key, value in lesson_data.items():
|
||||
if pd.isna(value):
|
||||
lesson_data[key] = default_topic_lesson_values.get(key, 'Null')
|
||||
|
||||
lesson_node = neo_curriculum.TopicLessonNode(**lesson_data)
|
||||
# Create lesson node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(lesson_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(lesson_node.path, lesson_node.to_dict())
|
||||
node_library['topic_lesson_nodes'][lesson_row['LessonID']] = lesson_node
|
||||
|
||||
# Link lesson to topic
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.TopicIncludesTopicLesson(source=topic_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created lesson node {lesson_node.unique_id} and relationship with topic {topic_node.unique_id}")
|
||||
|
||||
# Create sequential relationships between lessons
|
||||
if lesson_row['Lesson'].isdigit() and previous_lesson_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.TopicLessonFollowsTopicLesson(source=previous_lesson_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between lessons {previous_lesson_node.unique_id} and {lesson_node.unique_id}")
|
||||
previous_lesson_node = lesson_node
|
||||
|
||||
# Process learning statements for this lesson
|
||||
statements_for_lesson = statement_df[
|
||||
(statement_df['LessonID'] == lesson_row['LessonID']) &
|
||||
(statement_df['SyllabusSubject'] == topic_subject)
|
||||
]
|
||||
for _, statement_row in statements_for_lesson.iterrows():
|
||||
if statement_row['StatementID'] in statements_processed:
|
||||
continue
|
||||
statements_processed.add(statement_row['StatementID'])
|
||||
|
||||
_, statement_path = fs_handler.create_curriculum_learning_statement_directory(lesson_path, statement_row['StatementID'])
|
||||
|
||||
statement_data = {
|
||||
'unique_id': f"LearningStatement_{lesson_node.unique_id}_{statement_row['StatementID']}",
|
||||
'lesson_learning_statement_id': statement_row['StatementID'],
|
||||
'lesson_learning_statement': statement_row.get('LearningStatement', default_learning_statement_values['lesson_learning_statement']),
|
||||
'lesson_learning_statement_type': statement_row.get('StatementType', default_learning_statement_values['lesson_learning_statement_type']),
|
||||
'path': statement_path
|
||||
}
|
||||
for key in statement_data:
|
||||
if pd.isna(statement_data[key]):
|
||||
statement_data[key] = default_learning_statement_values.get(key, 'Null')
|
||||
|
||||
statement_node = neo_curriculum.LearningStatementNode(**statement_data)
|
||||
# Create statement node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(statement_node, database=curriculum_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(statement_node.path, statement_node.to_dict())
|
||||
node_library['statement_nodes'][statement_row['StatementID']] = statement_node
|
||||
|
||||
# Link learning statement to lesson
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
curriculum_relationships.LessonIncludesLearningStatement(source=lesson_node, target=statement_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created learning statement node {statement_node.unique_id} and relationship with lesson {lesson_node.unique_id}")
|
||||
|
||||
return node_library
|
||||
@ -1,512 +0,0 @@
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
from supabase import create_client
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
import modules.database.init.xl_tools as xl
|
||||
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
||||
import modules.database.tools.neo4j_driver_tools as driver_tools
|
||||
import modules.database.tools.neo4j_session_tools as session_tools
|
||||
import modules.database.schemas.nodes.schools.schools as school_schemas
|
||||
import modules.database.schemas.nodes.schools.curriculum as curriculum_schemas
|
||||
import modules.database.schemas.nodes.schools.pastoral as pastoral_schemas
|
||||
import modules.database.schemas.nodes.structures.schools as school_structures
|
||||
import modules.database.schemas.entities as entities
|
||||
from modules.database.admin.neontology_provider import NeontologyProvider
|
||||
from modules.database.admin.graph_provider import GraphNamingProvider
|
||||
from modules.database.schemas.relationships import curriculum_relationships, entity_relationships, entity_curriculum_rels
|
||||
|
||||
class SchoolManager:
|
||||
def __init__(self):
|
||||
self.driver = driver_tools.get_driver()
|
||||
self.logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
self.neontology = NeontologyProvider()
|
||||
self.graph_naming = GraphNamingProvider()
|
||||
|
||||
# Initialize Supabase client with correct URL and service role key
|
||||
supabase_url = os.getenv("SUPABASE_URL")
|
||||
service_role_key = os.getenv("SERVICE_ROLE_KEY")
|
||||
|
||||
self.logger.info(f"Initializing Supabase client with URL: {supabase_url}")
|
||||
self.supabase = create_client(supabase_url, service_role_key)
|
||||
|
||||
# Set headers for admin operations
|
||||
self.supabase.headers = {
|
||||
"apiKey": service_role_key,
|
||||
"Authorization": f"Bearer {service_role_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# Set storage client headers explicitly
|
||||
self.supabase.storage._client.headers.update({
|
||||
"apiKey": service_role_key,
|
||||
"Authorization": f"Bearer {service_role_key}",
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
|
||||
def create_schools_database(self):
|
||||
"""Creates the main cc.institutes database in Neo4j"""
|
||||
try:
|
||||
db_name = "cc.institutes"
|
||||
with self.driver.session() as session:
|
||||
return self._extracted_from_create_private_database(
|
||||
session, db_name, f'Created database {db_name}'
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating schools database: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_school_node(self, school_data):
|
||||
"""Creates a school node in cc.institutes database and stores TLDraw file in Supabase"""
|
||||
try:
|
||||
# Convert Supabase school data to SchoolNode using GraphNamingProvider
|
||||
school_unique_id = self.graph_naming.get_school_unique_id(school_data['urn'])
|
||||
school_path = self.graph_naming.get_school_path("cc.institutes", school_data['urn'])
|
||||
|
||||
school_node = entities.school_schemas.SchoolNode(
|
||||
unique_id=school_unique_id,
|
||||
path=school_path,
|
||||
urn=school_data['urn'],
|
||||
establishment_number=school_data['establishment_number'],
|
||||
establishment_name=school_data['establishment_name'],
|
||||
establishment_type=school_data['establishment_type'],
|
||||
establishment_status=school_data['establishment_status'],
|
||||
phase_of_education=school_data['phase_of_education'] if school_data['phase_of_education'] not in [None, ''] else None,
|
||||
statutory_low_age=int(school_data['statutory_low_age']) if school_data.get('statutory_low_age') is not None else 0,
|
||||
statutory_high_age=int(school_data['statutory_high_age']) if school_data.get('statutory_high_age') is not None else 0,
|
||||
religious_character=school_data.get('religious_character') if school_data.get('religious_character') not in [None, ''] else None,
|
||||
school_capacity=int(school_data['school_capacity']) if school_data.get('school_capacity') is not None else 0,
|
||||
school_website=school_data.get('school_website', ''),
|
||||
ofsted_rating=school_data.get('ofsted_rating') if school_data.get('ofsted_rating') not in [None, ''] else None
|
||||
)
|
||||
|
||||
# Create default tldraw file data
|
||||
tldraw_data = {
|
||||
"document": {
|
||||
"version": 1,
|
||||
"id": school_data['urn'],
|
||||
"name": school_data['establishment_name'],
|
||||
"meta": {
|
||||
"created_at": "",
|
||||
"updated_at": "",
|
||||
"creator_id": "",
|
||||
"is_template": False,
|
||||
"is_snapshot": False,
|
||||
"is_draft": False,
|
||||
"template_id": None,
|
||||
"snapshot_id": None,
|
||||
"draft_id": None
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"schemaVersion": 1,
|
||||
"storeVersion": 4,
|
||||
"recordVersions": {
|
||||
"asset": {
|
||||
"version": 1,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {}
|
||||
},
|
||||
"camera": {
|
||||
"version": 1
|
||||
},
|
||||
"document": {
|
||||
"version": 2
|
||||
},
|
||||
"instance": {
|
||||
"version": 22
|
||||
},
|
||||
"instance_page_state": {
|
||||
"version": 5
|
||||
},
|
||||
"page": {
|
||||
"version": 1
|
||||
},
|
||||
"shape": {
|
||||
"version": 3,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {
|
||||
"cc-school-node": 1
|
||||
}
|
||||
},
|
||||
"instance_presence": {
|
||||
"version": 5
|
||||
},
|
||||
"pointer": {
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": school_data['establishment_name'],
|
||||
"meta": {},
|
||||
"id": school_data['urn'],
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
},
|
||||
"shape:school-node": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"rotation": 0,
|
||||
"type": "cc-school-node",
|
||||
"id": school_unique_id,
|
||||
"parentId": "page",
|
||||
"index": "a1",
|
||||
"props": school_node.to_dict(),
|
||||
"typeName": "shape"
|
||||
},
|
||||
"instance:instance": {
|
||||
"id": "instance",
|
||||
"currentPageId": "page",
|
||||
"typeName": "instance"
|
||||
},
|
||||
"camera:camera": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 1,
|
||||
"id": "camera",
|
||||
"typeName": "camera"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Store tldraw file in Supabase storage
|
||||
file_path = f"{school_data['urn']}/tldraw.json"
|
||||
file_options = {
|
||||
"content-type": "application/json",
|
||||
"x-upsert": "true", # Update if exists
|
||||
"metadata": {
|
||||
"establishment_urn": school_data['urn'],
|
||||
"establishment_name": school_data['establishment_name']
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
# Create a fresh service role client for storage operations
|
||||
self.logger.info("Creating fresh service role client for storage operations")
|
||||
service_client = create_client(
|
||||
os.getenv("SUPABASE_URL"),
|
||||
os.getenv("SERVICE_ROLE_KEY")
|
||||
)
|
||||
|
||||
self.logger.debug(f"Service client created with URL: {os.getenv('SUPABASE_URL')}")
|
||||
|
||||
service_client.headers = {
|
||||
"apiKey": os.getenv("SERVICE_ROLE_KEY"),
|
||||
"Authorization": f"Bearer {os.getenv('SERVICE_ROLE_KEY')}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
service_client.storage._client.headers.update({
|
||||
"apiKey": os.getenv("SERVICE_ROLE_KEY"),
|
||||
"Authorization": f"Bearer {os.getenv('SERVICE_ROLE_KEY')}",
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
|
||||
self.logger.debug("Headers set for service client and storage client")
|
||||
|
||||
# Upload to Supabase storage using service role client
|
||||
self.logger.info(f"Uploading tldraw file for school {school_data['urn']}")
|
||||
self.logger.debug(f"File path: {file_path}")
|
||||
self.logger.debug(f"File options: {file_options}")
|
||||
|
||||
# First, ensure the bucket exists
|
||||
self.logger.info("Checking if bucket cc.institutes exists")
|
||||
try:
|
||||
bucket = service_client.storage.get_bucket("cc.institutes")
|
||||
self.logger.info("Bucket cc.institutes exists")
|
||||
except Exception as bucket_error:
|
||||
self.logger.error(f"Error checking bucket: {str(bucket_error)}")
|
||||
if hasattr(bucket_error, 'response'):
|
||||
self.logger.error(f"Bucket error response: {bucket_error.response.text if hasattr(bucket_error.response, 'text') else bucket_error.response}")
|
||||
raise bucket_error
|
||||
|
||||
# Attempt the upload
|
||||
self.logger.info("Attempting file upload")
|
||||
result = service_client.storage.from_("cc.institutes").upload(
|
||||
path=file_path,
|
||||
file=json.dumps(tldraw_data).encode(),
|
||||
file_options=file_options
|
||||
)
|
||||
self.logger.info(f"Upload successful. Result: {result}")
|
||||
|
||||
except Exception as upload_error:
|
||||
self.logger.error(f"Error uploading tldraw file: {str(upload_error)}")
|
||||
if hasattr(upload_error, 'response'):
|
||||
self.logger.error(f"Upload error response: {upload_error.response.text if hasattr(upload_error.response, 'text') else upload_error.response}")
|
||||
raise upload_error
|
||||
|
||||
# Create node in Neo4j using Neontology
|
||||
with self.neontology as neo:
|
||||
self.logger.info(f"Creating school node in Neo4j: {school_node.to_dict()}")
|
||||
neo.create_or_merge_node(school_node, database="cc.institutes", operation="merge")
|
||||
return {"status": "success", "node": school_node}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating school node: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_private_database(self, school_data):
|
||||
"""Creates a private database for a specific school"""
|
||||
try:
|
||||
private_db_name = f"cc.institutes.{school_data['urn']}"
|
||||
with self.driver.session() as session:
|
||||
return self._extracted_from_create_private_database(
|
||||
session, private_db_name, 'Created private database '
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating private database: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
# TODO Rename this here and in `create_schools_database` and `create_private_database`
|
||||
def _extracted_from_create_private_database(self, session, arg1, arg2):
|
||||
session_tools.create_database(session, arg1)
|
||||
self.logger.info(f"{arg2}{arg1}")
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Database {arg1} created successfully",
|
||||
}
|
||||
|
||||
def create_basic_structure(self, school_node, database_name):
|
||||
"""Creates basic structural nodes in the specified database"""
|
||||
try:
|
||||
# Create filesystem paths
|
||||
fs_handler = ClassroomCopilotFilesystem(database_name, init_run_type="school")
|
||||
|
||||
# Create Department Structure node
|
||||
department_structure_node_unique_id = f"DepartmentStructure_{school_node.unique_id}"
|
||||
_, department_path = fs_handler.create_school_department_directory(school_node.path, "departments")
|
||||
department_structure_node = entities.school_schemas.DepartmentNode(
|
||||
unique_id=department_structure_node_unique_id,
|
||||
path=department_path
|
||||
)
|
||||
|
||||
# Create Curriculum Structure node
|
||||
_, curriculum_path = fs_handler.create_school_curriculum_directory(school_node.path)
|
||||
curriculum_node = school_structures.CurriculumStructureNode(
|
||||
unique_id=f"CurriculumStructure_{school_node.unique_id}",
|
||||
path=curriculum_path
|
||||
)
|
||||
|
||||
# Create Pastoral Structure node
|
||||
_, pastoral_path = fs_handler.create_school_pastoral_directory(school_node.path)
|
||||
pastoral_node = school_structures.PastoralStructureNode(
|
||||
unique_id=f"PastoralStructure_{school_node.unique_id}",
|
||||
path=pastoral_path
|
||||
)
|
||||
|
||||
with self.neontology as neo:
|
||||
# Create nodes
|
||||
neo.create_or_merge_node(department_structure_node, database=str(database_name), operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_structure_node.path, department_structure_node.to_dict())
|
||||
|
||||
neo.create_or_merge_node(curriculum_node, database=str(database_name), operation='merge')
|
||||
fs_handler.create_default_tldraw_file(curriculum_node.path, curriculum_node.to_dict())
|
||||
|
||||
neo.create_or_merge_node(pastoral_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(pastoral_node.path, pastoral_node.to_dict())
|
||||
|
||||
# Create relationships
|
||||
neo.create_or_merge_relationship(
|
||||
entity_relationships.SchoolHasDepartmentStructure(source=school_node, target=department_structure_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
neo.create_or_merge_relationship(
|
||||
entity_curriculum_rels.SchoolHasCurriculumStructure(source=school_node, target=curriculum_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
neo.create_or_merge_relationship(
|
||||
entity_curriculum_rels.SchoolHasPastoralStructure(source=school_node, target=pastoral_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Basic structure created successfully",
|
||||
"nodes": {
|
||||
"department_structure": department_structure_node,
|
||||
"curriculum_structure": curriculum_node,
|
||||
"pastoral_structure": pastoral_node
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating basic structure: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_detailed_structure(self, school_node, database_name, excel_file):
|
||||
"""Creates detailed structural nodes from Excel file"""
|
||||
try:
|
||||
# First, store the Excel file in Supabase
|
||||
file_path = f"{school_node.urn}/structure.xlsx"
|
||||
file_options = {
|
||||
"content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"x-upsert": "true"
|
||||
}
|
||||
|
||||
# Upload Excel file to storage
|
||||
self.supabase.storage.from_("cc.institutes").upload(
|
||||
path=file_path,
|
||||
file=excel_file,
|
||||
file_options=file_options
|
||||
)
|
||||
|
||||
# Process Excel file
|
||||
dataframes = xl.create_dataframes(excel_file)
|
||||
|
||||
# Get existing basic structure nodes
|
||||
with self.neontology as neo:
|
||||
result = neo.cypher_read("""
|
||||
MATCH (s:School {unique_id: $school_id})
|
||||
OPTIONAL MATCH (s)-[:HAS_DEPARTMENT_STRUCTURE]->(ds:DepartmentStructure)
|
||||
OPTIONAL MATCH (s)-[:HAS_CURRICULUM_STRUCTURE]->(cs:CurriculumStructure)
|
||||
OPTIONAL MATCH (s)-[:HAS_PASTORAL_STRUCTURE]->(ps:PastoralStructure)
|
||||
RETURN ds, cs, ps
|
||||
""", {"school_id": school_node.unique_id}, database=database_name)
|
||||
|
||||
if not result:
|
||||
raise Exception("Basic structure not found")
|
||||
|
||||
department_structure = result['ds']
|
||||
curriculum_structure = result['cs']
|
||||
pastoral_structure = result['ps']
|
||||
|
||||
# Create departments and subjects
|
||||
unique_departments = dataframes['keystagesyllabuses']['Department'].dropna().unique()
|
||||
|
||||
fs_handler = ClassroomCopilotFilesystem(database_name, init_run_type="school")
|
||||
node_library = {}
|
||||
|
||||
with self.neontology as neo:
|
||||
for department_name in unique_departments:
|
||||
_, department_path = fs_handler.create_school_department_directory(school_node.path, department_name)
|
||||
|
||||
department_node = school_schemas.DepartmentNode(
|
||||
unique_id=f"Department_{school_node.unique_id}_{department_name.replace(' ', '_')}",
|
||||
department_name=department_name,
|
||||
path=department_path
|
||||
)
|
||||
neo.create_or_merge_node(department_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_node.path, department_node.to_dict())
|
||||
node_library[f'department_{department_name}'] = department_node
|
||||
|
||||
# Link to department structure
|
||||
neo.create_or_merge_relationship(
|
||||
entity_relationships.DepartmentStructureHasDepartment(
|
||||
source=department_structure,
|
||||
target=department_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
# Create year groups
|
||||
year_groups = self.sort_year_groups(dataframes['yeargroupsyllabuses'])['YearGroup'].unique()
|
||||
last_year_group_node = None
|
||||
|
||||
for year_group in year_groups:
|
||||
numeric_year_group = pd.to_numeric(year_group, errors='coerce')
|
||||
if pd.notna(numeric_year_group):
|
||||
_, year_group_path = fs_handler.create_pastoral_year_group_directory(
|
||||
pastoral_structure.path,
|
||||
str(int(numeric_year_group))
|
||||
)
|
||||
|
||||
year_group_node = pastoral_schemas.YearGroupNode(
|
||||
unique_id=f"YearGroup_{school_node.unique_id}_YGrp{int(numeric_year_group)}",
|
||||
year_group=str(int(numeric_year_group)),
|
||||
year_group_name=f"Year {int(numeric_year_group)}",
|
||||
path=year_group_path
|
||||
)
|
||||
neo.create_or_merge_node(year_group_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(year_group_node.path, year_group_node.to_dict())
|
||||
node_library[f'year_group_{int(numeric_year_group)}'] = year_group_node
|
||||
|
||||
# Create sequential relationship
|
||||
if last_year_group_node:
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.YearGroupFollowsYearGroup(
|
||||
source=last_year_group_node,
|
||||
target=year_group_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
last_year_group_node = year_group_node
|
||||
|
||||
# Link to pastoral structure
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.PastoralStructureIncludesYearGroup(
|
||||
source=pastoral_structure,
|
||||
target=year_group_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
# Create key stages
|
||||
key_stages = dataframes['keystagesyllabuses']['KeyStage'].unique()
|
||||
last_key_stage_node = None
|
||||
|
||||
for key_stage in sorted(key_stages):
|
||||
_, key_stage_path = fs_handler.create_curriculum_key_stage_directory(
|
||||
curriculum_structure.path,
|
||||
str(key_stage)
|
||||
)
|
||||
|
||||
key_stage_node = curriculum_schemas.KeyStageNode(
|
||||
unique_id=f"KeyStage_{curriculum_structure.unique_id}_KStg{key_stage}",
|
||||
key_stage_name=f"Key Stage {key_stage}",
|
||||
key_stage=str(key_stage),
|
||||
path=key_stage_path
|
||||
)
|
||||
neo.create_or_merge_node(key_stage_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(key_stage_node.path, key_stage_node.to_dict())
|
||||
node_library[f'key_stage_{key_stage}'] = key_stage_node
|
||||
|
||||
# Create sequential relationship
|
||||
if last_key_stage_node:
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.KeyStageFollowsKeyStage(
|
||||
source=last_key_stage_node,
|
||||
target=key_stage_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
last_key_stage_node = key_stage_node
|
||||
|
||||
# Link to curriculum structure
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.CurriculumStructureIncludesKeyStage(
|
||||
source=curriculum_structure,
|
||||
target=key_stage_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Detailed structure created successfully",
|
||||
"node_library": node_library
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating detailed structure: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def sort_year_groups(self, df):
|
||||
df = df.copy()
|
||||
df['YearGroupNumeric'] = pd.to_numeric(df['YearGroup'], errors='coerce')
|
||||
return df.sort_values(by='YearGroupNumeric')
|
||||
@ -1,54 +0,0 @@
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
import modules.database.tools.neo4j_driver_tools as driver_tools
|
||||
import modules.database.tools.neo4j_session_tools as session_tools
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
import modules.database.schemas.entities as neo_entity
|
||||
import modules.database.schemas.nodes.schools.curriculum as curriculum_schemas
|
||||
import modules.database.schemas.relationships.curriculum_relationships as curriculum_relationships
|
||||
import modules.database.schemas.relationships.entity_relationships as ent_rels
|
||||
import modules.database.schemas.relationships.entity_curriculum_rels as ent_cur_rels
|
||||
import pandas as pd
|
||||
|
||||
class SchoolSyllabusProvider:
|
||||
def __init__(self):
|
||||
self.driver = driver_tools.get_driver()
|
||||
self.logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
def process_syllabus_data(self, school_node, database_name, dataframes):
|
||||
"""Process syllabus data from Excel file and create nodes in the database"""
|
||||
try:
|
||||
# This method will contain the syllabus-specific processing code from the
|
||||
# original SchoolCurriculumProvider, starting from where the comment
|
||||
# "# Curriculum specific database initialisation begins here" was placed
|
||||
|
||||
# We'll implement this in the next iteration after confirming the basic
|
||||
# structure changes work correctly
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Syllabus data processed successfully"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing syllabus data: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def check_syllabus_status(self, school_node, database_name):
|
||||
"""Check if syllabus data exists in the database"""
|
||||
try:
|
||||
with self.driver.session(database=database_name) as session:
|
||||
result = session.run("""
|
||||
MATCH (s:School {unique_id: $school_id})
|
||||
OPTIONAL MATCH (s)-[:HAS_CURRICULUM_STRUCTURE]->(:CurriculumStructure)-[:INCLUDES_KEY_STAGE]->(:KeyStage)-[:INCLUDES_KEY_STAGE_SYLLABUS]->(ks:KeyStageSyllabus)
|
||||
OPTIONAL MATCH (s)-[:HAS_CURRICULUM_STRUCTURE]->(:CurriculumStructure)-[:INCLUDES_KEY_STAGE]->(:KeyStage)-[:INCLUDES_YEAR_GROUP_SYLLABUS]->(ys:YearGroupSyllabus)
|
||||
RETURN count(ks) > 0 OR count(ys) > 0 as has_syllabus
|
||||
""", school_id=school_node.unique_id)
|
||||
|
||||
has_syllabus = result.single()["has_syllabus"]
|
||||
|
||||
return {"has_syllabus": has_syllabus}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking syllabus status: {str(e)}")
|
||||
raise
|
||||
@ -1,526 +0,0 @@
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
import modules.database.init.init_calendar as init_calendar
|
||||
import modules.database.schemas.nodes.schools.timetable as timetable
|
||||
import modules.database.schemas.relationships.timetables as tt_rels
|
||||
import modules.database.schemas.relationships.entity_timetable_rels as entity_tt_rels
|
||||
import modules.database.schemas.relationships.calendar_timetable_rels as cal_tt_rels
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
||||
from datetime import timedelta, datetime
|
||||
import pandas as pd
|
||||
|
||||
def create_school_timetable_from_dataframes(dataframes, db_name, school_node=None):
|
||||
logger.info(f"Creating school timetable for {db_name}")
|
||||
if dataframes is None:
|
||||
raise ValueError("Data is required to create the calendar and timetable.")
|
||||
|
||||
logger.info("Initialising neo4j connection...")
|
||||
neon.init_neontology_connection()
|
||||
|
||||
# Initialize the filesystem handler
|
||||
fs_handler = ClassroomCopilotFilesystem(db_name, init_run_type="school")
|
||||
|
||||
school_df = dataframes['school']
|
||||
if school_node is None:
|
||||
logger.info("School node is None, using school data from dataframe")
|
||||
school_unique_id = school_df[school_df['Identifier'] == 'SchoolID']['Data'].iloc[0]
|
||||
else:
|
||||
logger.info(f"School node is not None, using school data from school node: {school_node}")
|
||||
school_unique_id = school_node.unique_id
|
||||
|
||||
terms_df = dataframes['terms']
|
||||
weeks_df = dataframes['weeks']
|
||||
days_df = dataframes['days']
|
||||
periods_df = dataframes['periods']
|
||||
|
||||
school_df_year_start = school_df[school_df['Identifier'] == 'AcademicYearStart']['Data'].iloc[0]
|
||||
school_df_year_end = school_df[school_df['Identifier'] == 'AcademicYearEnd']['Data'].iloc[0]
|
||||
if isinstance(school_df_year_start, str):
|
||||
school_year_start_date = datetime.strptime(school_df_year_start, '%Y-%m-%d')
|
||||
else:
|
||||
school_year_start_date = school_df_year_start
|
||||
if isinstance(school_df_year_end, str):
|
||||
school_year_end_date = datetime.strptime(school_df_year_end, '%Y-%m-%d')
|
||||
else:
|
||||
school_year_end_date = school_df_year_end
|
||||
|
||||
# Create a dictionary to store the timetable nodes
|
||||
timetable_nodes = {
|
||||
'timetable_node': None,
|
||||
'academic_year_nodes': [],
|
||||
'academic_term_nodes': [],
|
||||
'academic_week_nodes': [],
|
||||
'academic_day_nodes': [],
|
||||
'academic_period_nodes': []
|
||||
}
|
||||
|
||||
if school_node:
|
||||
# Create the root timetable directory
|
||||
_, timetable_path = fs_handler.create_school_timetable_directory(school_node.path)
|
||||
else:
|
||||
# Create the root timetable directory
|
||||
_, timetable_path = fs_handler.create_school_timetable_directory()
|
||||
|
||||
# Create AcademicTimetable Node
|
||||
school_timetable_unique_id = f"SchoolTimetable_{school_unique_id}_{school_year_start_date.year}_{school_year_end_date.year}"
|
||||
school_timetable_node = timetable.SchoolTimetableNode(
|
||||
unique_id=school_timetable_unique_id,
|
||||
start_date=school_year_start_date,
|
||||
end_date=school_year_end_date,
|
||||
path=timetable_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(school_timetable_node, database=db_name, operation='merge')
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(school_timetable_node.path, school_timetable_node.to_dict())
|
||||
timetable_nodes['timetable_node'] = school_timetable_node
|
||||
|
||||
if school_node:
|
||||
logger.info(f"Creating calendar for {school_unique_id} from Neo4j SchoolNode: {school_node.unique_id}")
|
||||
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=True, entity_node=school_node)
|
||||
# Link the school node to the timetable node
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
entity_tt_rels.SchoolHasTimetable(source=school_node, target=school_timetable_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
timetable_nodes['calendar_nodes'] = calendar_nodes
|
||||
else:
|
||||
logger.info(f"Creating calendar for {school_unique_id} from dataframe SchoolID: {school_unique_id}")
|
||||
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=False, entity_node=None)
|
||||
|
||||
# Create AcademicYear nodes for each year within the range
|
||||
for year in range(school_year_start_date.year, school_year_end_date.year + 1):
|
||||
_, timetable_year_path = fs_handler.create_school_timetable_year_directory(timetable_path, year)
|
||||
year_str = str(year)
|
||||
academic_year_unique_id = f"AcademicYear_{school_timetable_unique_id}_{year}"
|
||||
academic_year_node = timetable.AcademicYearNode(
|
||||
unique_id=academic_year_unique_id,
|
||||
year=year_str,
|
||||
path=timetable_year_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(academic_year_node, database=db_name, operation='merge')
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(academic_year_node.path, academic_year_node.to_dict())
|
||||
timetable_nodes['academic_year_nodes'].append(academic_year_node)
|
||||
logger.info(f'Created academic year node: {academic_year_node.unique_id}')
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
tt_rels.AcademicTimetableHasAcademicYear(source=school_timetable_node, target=academic_year_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {school_timetable_node.unique_id} to {academic_year_node.unique_id}")
|
||||
|
||||
# Link the academic year with the corresponding calendar year node
|
||||
for year_node in calendar_nodes['calendar_year_nodes']:
|
||||
if year_node.year == year:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_tt_rels.AcademicYearIsCalendarYear(source=academic_year_node, target=year_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.unique_id} to {year_node.unique_id}")
|
||||
break
|
||||
|
||||
# Create Term and TermBreak nodes linked to AcademicYear
|
||||
term_number = 1
|
||||
academic_term_number = 1
|
||||
for _, term_row in terms_df.iterrows():
|
||||
term_node_class = timetable.AcademicTermNode if term_row['TermType'] == 'Term' else timetable.AcademicTermBreakNode
|
||||
term_name = term_row['TermName']
|
||||
term_name_no_spaces = term_name.replace(' ', '')
|
||||
term_start_date = term_row['StartDate']
|
||||
if isinstance(term_start_date, pd.Timestamp):
|
||||
term_start_date = term_start_date.strftime('%Y-%m-%d')
|
||||
|
||||
term_end_date = term_row['EndDate']
|
||||
if isinstance(term_end_date, pd.Timestamp):
|
||||
term_end_date = term_end_date.strftime('%Y-%m-%d')
|
||||
|
||||
if term_row['TermType'] == 'Term':
|
||||
_, timetable_term_path = fs_handler.create_school_timetable_academic_term_directory(
|
||||
timetable_path=timetable_path,
|
||||
term_name=term_name,
|
||||
term_number=academic_term_number
|
||||
)
|
||||
term_node_unique_id = f"AcademicTerm_{school_timetable_unique_id}_{academic_term_number}_{term_name_no_spaces}"
|
||||
academic_term_number_str = str(academic_term_number)
|
||||
term_node = term_node_class(
|
||||
unique_id=term_node_unique_id,
|
||||
term_name=term_name,
|
||||
term_number=academic_term_number_str,
|
||||
start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
|
||||
end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
|
||||
path=timetable_term_path
|
||||
)
|
||||
academic_term_number += 1
|
||||
else:
|
||||
term_break_node_unique_id = f"AcademicTermBreak_{school_timetable_unique_id}_{term_name_no_spaces}"
|
||||
term_node = term_node_class(
|
||||
unique_id=term_break_node_unique_id,
|
||||
term_break_name=term_name,
|
||||
start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
|
||||
end_date=datetime.strptime(term_end_date, '%Y-%m-%d')
|
||||
)
|
||||
neon.create_or_merge_neontology_node(term_node, database=db_name, operation='merge')
|
||||
if isinstance(term_node, timetable.AcademicTermNode):
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(term_node.path, term_node.to_dict())
|
||||
logger.info(f'Created academic term break node: {term_node.unique_id}')
|
||||
timetable_nodes['academic_term_nodes'].append(term_node)
|
||||
term_number += 1 # We don't use this but we could
|
||||
|
||||
# Link term node to the correct academic year
|
||||
term_years = set()
|
||||
term_years.update([term_node.start_date.year, term_node.end_date.year])
|
||||
|
||||
for academic_year_node in timetable_nodes['academic_year_nodes']:
|
||||
if int(academic_year_node.year) in term_years:
|
||||
relationship_class = tt_rels.AcademicYearHasAcademicTerm if term_row['TermType'] == 'Term' else tt_rels.AcademicYearHasAcademicTermBreak
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=academic_year_node, target=term_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.unique_id} to {term_node.unique_id}")
|
||||
|
||||
# Create Week nodes
|
||||
academic_week_number = 1
|
||||
for _, week_row in weeks_df.iterrows():
|
||||
week_node_class = timetable.HolidayWeekNode if week_row['WeekType'] == 'Holiday' else timetable.AcademicWeekNode
|
||||
week_start_date = week_row['WeekStart']
|
||||
if isinstance(week_start_date, pd.Timestamp):
|
||||
week_start_date = week_start_date.strftime('%Y-%m-%d')
|
||||
|
||||
if week_row['WeekType'] == 'Holiday':
|
||||
week_node_unique_id = f"{week_row['WeekType']}Week_{school_timetable_unique_id}_Week_{week_row['WeekNumber']}"
|
||||
week_node = week_node_class(
|
||||
unique_id=week_node_unique_id,
|
||||
start_date=datetime.strptime(week_start_date, '%Y-%m-%d')
|
||||
)
|
||||
else:
|
||||
_, timetable_week_path = fs_handler.create_school_timetable_academic_week_directory(
|
||||
timetable_path=timetable_path,
|
||||
week_number=academic_week_number
|
||||
)
|
||||
week_node_unique_id = f"AcademicWeek_{school_timetable_unique_id}_Week_{week_row['WeekNumber']}"
|
||||
academic_week_number_str = str(academic_week_number)
|
||||
week_type = week_row['WeekType']
|
||||
week_node = week_node_class(
|
||||
unique_id=week_node_unique_id,
|
||||
academic_week_number=academic_week_number_str,
|
||||
start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
|
||||
week_type=week_type,
|
||||
path=timetable_week_path
|
||||
)
|
||||
academic_week_number += 1
|
||||
neon.create_or_merge_neontology_node(week_node, database=db_name, operation='merge')
|
||||
timetable_nodes['academic_week_nodes'].append(week_node)
|
||||
logger.info(f"Created week node: {week_node.unique_id}")
|
||||
if isinstance(week_node, timetable.AcademicWeekNode):
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(week_node.path, week_node.to_dict())
|
||||
for calendar_node in calendar_nodes['calendar_week_nodes']:
|
||||
if calendar_node.start_date == week_node.start_date:
|
||||
if isinstance(week_node, timetable.AcademicWeekNode):
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_tt_rels.AcademicWeekIsCalendarWeek(source=week_node, target=calendar_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {calendar_node.unique_id} to {week_node.unique_id}")
|
||||
elif isinstance(week_node, timetable.HolidayWeekNode):
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_tt_rels.HolidayWeekIsCalendarWeek(source=week_node, target=calendar_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {calendar_node.unique_id} to {week_node.unique_id}")
|
||||
break
|
||||
|
||||
# Link week node to the correct academic term
|
||||
for term_node in timetable_nodes['academic_term_nodes']:
|
||||
if term_node.start_date <= week_node.start_date <= term_node.end_date:
|
||||
relationship_class = tt_rels.AcademicTermHasAcademicWeek if week_row['WeekType'] != 'Holiday' else tt_rels.AcademicTermBreakHasHolidayWeek
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=term_node, target=week_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {term_node.unique_id} to {week_node.unique_id}")
|
||||
break
|
||||
|
||||
# Link week node to the correct academic year
|
||||
for academic_year_node in timetable_nodes['academic_year_nodes']:
|
||||
if int(academic_year_node.year) == week_node.start_date.year:
|
||||
relationship_class = tt_rels.AcademicYearHasAcademicWeek if week_row['WeekType'] != 'Holiday' else tt_rels.AcademicYearHasHolidayWeek
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=academic_year_node, target=week_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.unique_id} to {week_node.unique_id}")
|
||||
break
|
||||
|
||||
# Create Day nodes
|
||||
day_number = 1
|
||||
academic_day_number = 1
|
||||
for _, day_row in days_df.iterrows():
|
||||
date_str = day_row['Date']
|
||||
if isinstance(date_str, pd.Timestamp):
|
||||
date_str = date_str.strftime('%Y-%m-%d')
|
||||
|
||||
day_node_class = {
|
||||
'Academic': timetable.AcademicDayNode,
|
||||
'Holiday': timetable.HolidayDayNode,
|
||||
'OffTimetable': timetable.OffTimetableDayNode,
|
||||
'StaffDay': timetable.StaffDayNode
|
||||
}[day_row['DayType']]
|
||||
|
||||
# Format the unique ID as {day_node_class.__name__}Day
|
||||
day_node_data = {
|
||||
'unique_id': f"{day_node_class.__name__}Day_{school_timetable_unique_id}_{day_number}",
|
||||
'date': datetime.strptime(date_str, '%Y-%m-%d'),
|
||||
'day_of_week': datetime.strptime(date_str, '%Y-%m-%d').strftime('%A')
|
||||
}
|
||||
|
||||
if day_row['DayType'] == 'Academic':
|
||||
day_node_data['academic_day'] = str(academic_day_number)
|
||||
day_node_data['day_type'] = day_row['WeekType']
|
||||
_, timetable_day_path = fs_handler.create_school_timetable_academic_day_directory(
|
||||
timetable_path=timetable_path,
|
||||
academic_day=academic_day_number
|
||||
)
|
||||
day_node_data['path'] = timetable_day_path
|
||||
|
||||
day_node = day_node_class(**day_node_data)
|
||||
|
||||
for calendar_node in calendar_nodes['calendar_day_nodes']:
|
||||
if calendar_node.date == day_node.date:
|
||||
neon.create_or_merge_neontology_node(day_node, database=db_name, operation='merge')
|
||||
timetable_nodes['academic_day_nodes'].append(day_node)
|
||||
logger.info(f"Created day node: {day_node.unique_id}")
|
||||
|
||||
if isinstance(day_node, timetable.AcademicDayNode):
|
||||
fs_handler.create_default_tldraw_file(day_node.path, day_node.to_dict())
|
||||
relationship_class = cal_tt_rels.AcademicDayIsCalendarDay
|
||||
elif isinstance(day_node, timetable.HolidayDayNode):
|
||||
relationship_class = cal_tt_rels.HolidayDayIsCalendarDay
|
||||
elif isinstance(day_node, timetable.OffTimetableDayNode):
|
||||
relationship_class = cal_tt_rels.OffTimetableDayIsCalendarDay
|
||||
elif isinstance(day_node, timetable.StaffDayNode):
|
||||
relationship_class = cal_tt_rels.StaffDayIsCalendarDay
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=day_node, target=calendar_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f'Created relationship from {calendar_node.unique_id} to {day_node.unique_id}')
|
||||
break
|
||||
|
||||
# Link day node to the correct academic week
|
||||
for academic_week_node in timetable_nodes['academic_week_nodes']:
|
||||
if academic_week_node.start_date <= day_node.date <= (academic_week_node.start_date + timedelta(days=6)):
|
||||
if day_row['DayType'] == 'Academic':
|
||||
relationship_class = tt_rels.AcademicWeekHasAcademicDay
|
||||
elif day_row['DayType'] == 'Holiday':
|
||||
if hasattr(academic_week_node, 'week_type') and academic_week_node.week_type in ['A', 'B']:
|
||||
relationship_class = tt_rels.AcademicWeekHasHolidayDay
|
||||
else:
|
||||
relationship_class = tt_rels.HolidayWeekHasHolidayDay
|
||||
elif day_row['DayType'] == 'OffTimetable':
|
||||
relationship_class = tt_rels.AcademicWeekHasOffTimetableDay
|
||||
elif day_row['DayType'] == 'Staff':
|
||||
relationship_class = tt_rels.AcademicWeekHasStaffDay
|
||||
else:
|
||||
continue # Skip linking for other day types
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=academic_week_node, target=day_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {academic_week_node.unique_id} to {day_node.unique_id}")
|
||||
break
|
||||
|
||||
# Link day node to the correct academic term
|
||||
for term_node in timetable_nodes['academic_term_nodes']:
|
||||
if term_node.start_date <= day_node.date <= term_node.end_date:
|
||||
if day_row['DayType'] == 'Academic':
|
||||
relationship_class = tt_rels.AcademicTermHasAcademicDay
|
||||
elif day_row['DayType'] == 'Holiday':
|
||||
if isinstance(term_node, timetable.AcademicTermNode):
|
||||
relationship_class = tt_rels.AcademicTermHasHolidayDay
|
||||
else:
|
||||
relationship_class = tt_rels.AcademicTermBreakHasHolidayDay
|
||||
elif day_row['DayType'] == 'OffTimetable':
|
||||
relationship_class = tt_rels.AcademicTermHasOffTimetableDay
|
||||
elif day_row['DayType'] == 'Staff':
|
||||
relationship_class = tt_rels.AcademicTermHasStaffDay
|
||||
else:
|
||||
continue # Skip linking for other day types
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=term_node, target=day_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {term_node.unique_id} to {day_node.unique_id}")
|
||||
break
|
||||
|
||||
# Create Period nodes for each academic day
|
||||
if day_row['DayType'] == 'Academic':
|
||||
logger.info(f"Creating periods for {day_node.unique_id}")
|
||||
period_of_day = 1
|
||||
academic_or_registration_period_of_day = 1
|
||||
for _, period_row in periods_df.iterrows():
|
||||
period_node_class = {
|
||||
'Academic': timetable.AcademicPeriodNode,
|
||||
'Registration': timetable.RegistrationPeriodNode,
|
||||
'Break': timetable.BreakPeriodNode,
|
||||
'OffTimetable': timetable.OffTimetablePeriodNode
|
||||
}[period_row['PeriodType']]
|
||||
|
||||
logger.info(f"Creating period node for {period_node_class.__name__} Period: {period_of_day}")
|
||||
period_node_unique_id = f"{period_node_class.__name__}_{school_timetable_unique_id}_Day_{academic_day_number}_Period_{period_of_day}"
|
||||
logger.debug(f"Period node unique id: {period_node_unique_id}")
|
||||
period_node_data = {
|
||||
'unique_id': period_node_unique_id,
|
||||
'name': period_row['PeriodName'],
|
||||
'date': day_node.date,
|
||||
'start_time': datetime.combine(day_node.date, period_row['StartTime']),
|
||||
'end_time': datetime.combine(day_node.date, period_row['EndTime'])
|
||||
}
|
||||
logger.debug(f"Period node data: {period_node_data}")
|
||||
if period_row['PeriodType'] in ['Academic', 'Registration']:
|
||||
_, timetable_period_path = fs_handler.create_school_timetable_period_directory(
|
||||
timetable_path=timetable_path,
|
||||
academic_day=academic_day_number,
|
||||
period_dir=f"{academic_or_registration_period_of_day}_{period_row['PeriodName'].replace(' ', '_')}"
|
||||
)
|
||||
week_type = day_row['WeekType']
|
||||
day_name_short = day_node.day_of_week[:3]
|
||||
period_code = period_row['PeriodCode']
|
||||
period_code_formatted = f"{week_type}{day_name_short}{period_code}"
|
||||
period_node_data['period_code'] = period_code_formatted
|
||||
period_node_data['path'] = timetable_period_path
|
||||
|
||||
academic_or_registration_period_of_day += 1
|
||||
|
||||
period_node = period_node_class(**period_node_data)
|
||||
neon.create_or_merge_neontology_node(period_node, database=db_name, operation='merge')
|
||||
if isinstance(period_node, timetable.AcademicPeriodNode) or isinstance(period_node, timetable.RegistrationPeriodNode):
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(period_node.path, period_node.to_dict())
|
||||
timetable_nodes['academic_period_nodes'].append(period_node)
|
||||
logger.info(f'Created period node: {period_node.unique_id}')
|
||||
|
||||
relationship_class = {
|
||||
'Academic': tt_rels.AcademicDayHasAcademicPeriod,
|
||||
'Registration': tt_rels.AcademicDayHasRegistrationPeriod,
|
||||
'Break': tt_rels.AcademicDayHasBreakPeriod,
|
||||
'OffTimetable': tt_rels.AcademicDayHasOffTimetablePeriod
|
||||
}[period_row['PeriodType']]
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(source=day_node, target=period_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {day_node.unique_id} to {period_node.unique_id}")
|
||||
period_of_day += 1 # We don't use this but we could
|
||||
academic_day_number += 1 # This is a bit of a hack but it works to keep the directories aligned (reorganise)
|
||||
day_number += 1 # We don't use this but we could
|
||||
|
||||
def create_school_timetable_node_sequence_rels(timetable_nodes):
|
||||
def sort_and_create_relationships(nodes, relationship_map, sort_key):
|
||||
sorted_nodes = sorted(nodes, key=sort_key)
|
||||
for i in range(len(sorted_nodes) - 1):
|
||||
source_node = sorted_nodes[i]
|
||||
target_node = sorted_nodes[i + 1]
|
||||
node_type_pair = (type(source_node), type(target_node))
|
||||
relationship_class = relationship_map.get(node_type_pair)
|
||||
if relationship_class:
|
||||
# Avoid self-referential relationships
|
||||
if source_node.unique_id != target_node.unique_id:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(
|
||||
source=source_node,
|
||||
target=target_node
|
||||
),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {source_node.unique_id} to {target_node.unique_id}")
|
||||
else:
|
||||
logger.warning(f"Skipped self-referential relationship for node {source_node.unique_id}")
|
||||
|
||||
# Relationship maps for different node types
|
||||
academic_year_relationship_map = {
|
||||
(timetable.AcademicYearNode, timetable.AcademicYearNode): tt_rels.AcademicYearFollowsAcademicYear
|
||||
}
|
||||
|
||||
academic_term_relationship_map = {
|
||||
(timetable.AcademicTermNode, timetable.AcademicTermBreakNode): tt_rels.AcademicTermBreakFollowsAcademicTerm,
|
||||
(timetable.AcademicTermBreakNode, timetable.AcademicTermNode): tt_rels.AcademicTermFollowsAcademicTermBreak
|
||||
}
|
||||
|
||||
academic_week_relationship_map = {
|
||||
(timetable.AcademicWeekNode, timetable.AcademicWeekNode): tt_rels.AcademicWeekFollowsAcademicWeek,
|
||||
(timetable.HolidayWeekNode, timetable.HolidayWeekNode): tt_rels.HolidayWeekFollowsHolidayWeek,
|
||||
(timetable.AcademicWeekNode, timetable.HolidayWeekNode): tt_rels.HolidayWeekFollowsAcademicWeek,
|
||||
(timetable.HolidayWeekNode, timetable.AcademicWeekNode): tt_rels.AcademicWeekFollowsHolidayWeek
|
||||
}
|
||||
|
||||
academic_day_relationship_map = {
|
||||
(timetable.AcademicDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsAcademicDay,
|
||||
(timetable.HolidayDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsHolidayDay,
|
||||
(timetable.OffTimetableDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsOffTimetableDay,
|
||||
(timetable.StaffDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsStaffDay,
|
||||
|
||||
(timetable.AcademicDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsAcademicDay,
|
||||
(timetable.AcademicDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsAcademicDay,
|
||||
(timetable.AcademicDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsAcademicDay,
|
||||
|
||||
(timetable.HolidayDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsHolidayDay,
|
||||
(timetable.HolidayDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsHolidayDay,
|
||||
(timetable.HolidayDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsHolidayDay,
|
||||
|
||||
(timetable.OffTimetableDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsOffTimetableDay,
|
||||
(timetable.OffTimetableDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsOffTimetableDay,
|
||||
(timetable.OffTimetableDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsOffTimetableDay,
|
||||
|
||||
(timetable.StaffDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsStaffDay,
|
||||
(timetable.StaffDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsStaffDay,
|
||||
(timetable.StaffDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsStaffDay,
|
||||
}
|
||||
|
||||
academic_period_relationship_map = {
|
||||
(timetable.AcademicPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsAcademicPeriod,
|
||||
(timetable.AcademicPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsAcademicPeriod,
|
||||
(timetable.AcademicPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsAcademicPeriod,
|
||||
(timetable.AcademicPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsAcademicPeriod,
|
||||
(timetable.BreakPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsBreakPeriod,
|
||||
(timetable.BreakPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsBreakPeriod,
|
||||
(timetable.BreakPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsBreakPeriod,
|
||||
(timetable.BreakPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsBreakPeriod,
|
||||
(timetable.RegistrationPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsRegistrationPeriod,
|
||||
(timetable.RegistrationPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsRegistrationPeriod,
|
||||
(timetable.RegistrationPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsRegistrationPeriod,
|
||||
(timetable.RegistrationPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsRegistrationPeriod,
|
||||
(timetable.OffTimetablePeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsOffTimetablePeriod,
|
||||
(timetable.OffTimetablePeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsOffTimetablePeriod,
|
||||
(timetable.OffTimetablePeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsOffTimetablePeriod,
|
||||
(timetable.OffTimetablePeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsOffTimetablePeriod,
|
||||
}
|
||||
|
||||
|
||||
# Sort and create relationships
|
||||
sort_and_create_relationships(timetable_nodes['academic_year_nodes'], academic_year_relationship_map, lambda x: int(x.year))
|
||||
sort_and_create_relationships(timetable_nodes['academic_term_nodes'], academic_term_relationship_map, lambda x: x.start_date)
|
||||
sort_and_create_relationships(timetable_nodes['academic_week_nodes'], academic_week_relationship_map, lambda x: x.start_date)
|
||||
sort_and_create_relationships(timetable_nodes['academic_day_nodes'], academic_day_relationship_map, lambda x: x.date)
|
||||
sort_and_create_relationships(timetable_nodes['academic_period_nodes'], academic_period_relationship_map, lambda x: (x.start_time, x.end_time))
|
||||
|
||||
# Call the function with the created timetable nodes
|
||||
create_school_timetable_node_sequence_rels(timetable_nodes)
|
||||
|
||||
logger.info(f'Created timetable: {timetable_nodes["timetable_node"].unique_id}')
|
||||
|
||||
# Log the directory structure after creation
|
||||
# root_timetable_directory = fs_handler.root_path # Access the root directory of the filesystem handler
|
||||
# fs_handler.log_directory_structure(root_timetable_directory)
|
||||
|
||||
return {
|
||||
'school_node': school_node,
|
||||
'school_calendar_nodes': calendar_nodes,
|
||||
'school_timetable_nodes': timetable_nodes
|
||||
}
|
||||
@ -4,12 +4,25 @@ logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH
|
||||
import modules.database.schemas.nodes.calendars as calendar_schemas
|
||||
import modules.database.schemas.relationships.calendars as calendar_relationships
|
||||
import modules.database.schemas.relationships.calendar_sequence as calendar_sequence_relationships
|
||||
import modules.database.schemas.relationships.owner_relationships as owner_relationships
|
||||
import modules.database.tools.supabase_storage_tools as storage_tools
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False, owner_node=None, time_chunk_node_length: int = None):
|
||||
logger.info(f"Creating calendar for {start_date} to {end_date}")
|
||||
def create_calendar(db_name, start_date, end_date, time_chunk_node_length: int = None, storage_tools=None):
|
||||
"""
|
||||
Create calendar structure with years, months, weeks, and days
|
||||
|
||||
Args:
|
||||
db_name: Database name to create calendar in
|
||||
start_date: Start date for calendar
|
||||
end_date: End date for calendar
|
||||
time_chunk_node_length: Optional time chunk length in minutes
|
||||
storage_tools: Optional Supabase storage tools for generating storage paths
|
||||
|
||||
Returns:
|
||||
dict: Dictionary containing created calendar nodes
|
||||
"""
|
||||
logger.info(f"Creating calendar structure for {start_date} to {end_date} in database: {db_name}")
|
||||
|
||||
logger.info(f"Initializing Neontology connection")
|
||||
neon.init_neontology_connection()
|
||||
@ -25,52 +38,13 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
last_day_node = None
|
||||
|
||||
calendar_nodes = {
|
||||
'calendar_node': None,
|
||||
'calendar_year_nodes': [],
|
||||
'calendar_month_nodes': [],
|
||||
'calendar_week_nodes': [],
|
||||
'calendar_day_nodes': []
|
||||
'calendar_day_nodes': [],
|
||||
'calendar_time_chunk_nodes': []
|
||||
}
|
||||
|
||||
if attach_to_calendar_node and owner_node:
|
||||
logger.info(f"Attaching calendar to owner's node {owner_node.unique_id} in database: {db_name}")
|
||||
owner_unique_id = owner_node.unique_id
|
||||
calendar_unique_id = f"{start_date.strftime('%Y-%m-%d')}_{end_date.strftime('%Y-%m-%d')}"
|
||||
calendar_node = calendar_schemas.CalendarNode(
|
||||
unique_id=calendar_unique_id,
|
||||
name=f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}",
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
neon.create_or_merge_neontology_node(calendar_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_node'] = calendar_node
|
||||
logger.info(f"Calendar node created: {calendar_node.unique_id}")
|
||||
|
||||
import modules.database.schemas.relationships.owner_relationships as owner_relationships
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
owner_relationships.OwnerHasCalendar(source=owner_node, target=calendar_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {owner_node.unique_id} to {calendar_node.unique_id}")
|
||||
elif attach_to_calendar_node and not owner_node:
|
||||
logger.info(f"Creating calendar for {start_date} to {end_date} in database: {db_name}")
|
||||
calendar_node = calendar_schemas.CalendarNode(
|
||||
unique_id=f"{start_date.strftime('%Y-%m-%d')}_{end_date.strftime('%Y-%m-%d')}",
|
||||
name=f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}",
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
neon.create_or_merge_neontology_node(calendar_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_node'] = calendar_node
|
||||
logger.info(f"Calendar node created: {calendar_node.unique_id}")
|
||||
else:
|
||||
logger.error("Invalid combination of parameters for calendar creation.")
|
||||
raise ValueError("Invalid combination of parameters for calendar creation.")
|
||||
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
year = current_date.year
|
||||
@ -78,50 +52,55 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
day = current_date.day
|
||||
iso_year, iso_week, iso_weekday = current_date.isocalendar()
|
||||
|
||||
calendar_year_unique_id = f"{year}"
|
||||
calendar_year_uuid_string = f"{year}"
|
||||
|
||||
if year not in created_years:
|
||||
# Generate storage path for year node using Supabase Storage
|
||||
if storage_tools:
|
||||
year_dir_created, node_storage_path = storage_tools.create_calendar_year_storage_path(year)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
year_node = calendar_schemas.CalendarYearNode(
|
||||
unique_id=calendar_year_unique_id,
|
||||
uuid_string=calendar_year_uuid_string,
|
||||
year=str(year),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(year_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_year_nodes'].append(year_node)
|
||||
created_years[year] = year_node
|
||||
logger.info(f"Year node created: {year_node.unique_id}")
|
||||
logger.info(f"Year node created: {year_node.uuid_string}")
|
||||
|
||||
if attach_to_calendar_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
calendar_relationships.CalendarIncludesYear(source=calendar_node, target=year_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {calendar_node.unique_id} to {year_node.unique_id}")
|
||||
if last_year_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
calendar_sequence_relationships.YearFollowsYear(source=last_year_node, target=year_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_year_node.unique_id} to {year_node.unique_id}")
|
||||
logger.info(f"Relationship created from {last_year_node.uuid_string} to {year_node.uuid_string}")
|
||||
last_year_node = year_node
|
||||
|
||||
calendar_month_unique_id = f"{year}_{month}"
|
||||
calendar_month_uuid_string = f"{year}_{month}"
|
||||
|
||||
month_key = f"{year}-{month}"
|
||||
if month_key not in created_months:
|
||||
# Generate storage path for month node using Supabase Storage
|
||||
if storage_tools:
|
||||
month_dir_created, node_storage_path = storage_tools.create_calendar_month_storage_path(year, month)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
month_node = calendar_schemas.CalendarMonthNode(
|
||||
unique_id=calendar_month_unique_id,
|
||||
uuid_string=calendar_month_uuid_string,
|
||||
year=str(year),
|
||||
month=str(month),
|
||||
month_name=datetime(year, month, 1).strftime('%B'),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(month_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_month_nodes'].append(month_node)
|
||||
created_months[month_key] = month_node
|
||||
logger.info(f"Month node created: {month_node.unique_id}")
|
||||
logger.info(f"Month node created: {month_node.uuid_string}")
|
||||
|
||||
# Check for the end of year transition for months
|
||||
if last_month_node:
|
||||
@ -131,14 +110,14 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_month_node.unique_id} to {month_node.unique_id}")
|
||||
logger.info(f"Relationship created from {last_month_node.uuid_string} to {month_node.uuid_string}")
|
||||
elif int(month) == int(last_month_node.month) + 1:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
calendar_sequence_relationships.MonthFollowsMonth(source=last_month_node, target=month_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_month_node.unique_id} to {month_node.unique_id}")
|
||||
logger.info(f"Relationship created from {last_month_node.uuid_string} to {month_node.uuid_string}")
|
||||
last_month_node = month_node
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -146,25 +125,31 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {year_node.unique_id} to {month_node.unique_id}")
|
||||
logger.info(f"Relationship created from {year_node.uuid_string} to {month_node.uuid_string}")
|
||||
|
||||
calendar_week_unique_id = f"{iso_year}_{iso_week}"
|
||||
calendar_week_uuid_string = f"{iso_year}_{iso_week}"
|
||||
|
||||
week_key = f"{iso_year}-W{iso_week}"
|
||||
if week_key not in created_weeks:
|
||||
# Get the date of the first monday of the week
|
||||
week_start_date = current_date - timedelta(days=current_date.weekday())
|
||||
# Generate storage path for week node using Supabase Storage
|
||||
if storage_tools:
|
||||
week_dir_created, node_storage_path = storage_tools.create_calendar_week_storage_path(iso_year, iso_week)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
week_node = calendar_schemas.CalendarWeekNode(
|
||||
unique_id=calendar_week_unique_id,
|
||||
uuid_string=calendar_week_uuid_string,
|
||||
start_date=week_start_date,
|
||||
week_number=str(iso_week),
|
||||
iso_week=f"{iso_year}-W{iso_week:02}",
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(week_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_week_nodes'].append(week_node)
|
||||
created_weeks[week_key] = week_node
|
||||
logger.info(f"Week node created: {week_node.unique_id}")
|
||||
logger.info(f"Week node created: {week_node.uuid_string}")
|
||||
|
||||
if last_week_node and ((last_week_node.iso_week.split('-')[0] == str(iso_year) and int(last_week_node.week_number) == int(iso_week) - 1) or
|
||||
(last_week_node.iso_week.split('-')[0] != str(iso_year) and int(last_week_node.week_number) == 52 and int(iso_week) == 1)):
|
||||
@ -173,7 +158,7 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_week_node.unique_id} to {week_node.unique_id}")
|
||||
logger.info(f"Relationship created from {last_week_node.uuid_string} to {week_node.uuid_string}")
|
||||
last_week_node = week_node
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -181,23 +166,32 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {year_node.unique_id} to {week_node.unique_id}")
|
||||
logger.info(f"Relationship created from {year_node.uuid_string} to {week_node.uuid_string}")
|
||||
|
||||
# Day node management
|
||||
calendar_day_unique_id = f"{year}_{month}_{day}"
|
||||
calendar_day_uuid_string = f"{year}_{month}_{day}"
|
||||
|
||||
day_key = f"{year}-{month}-{day}"
|
||||
# Generate storage path for day node using Supabase Storage
|
||||
if storage_tools:
|
||||
day_dir_created, node_storage_path = storage_tools.create_calendar_day_storage_path(year, month, day)
|
||||
# Store day path for later use in time chunks
|
||||
created_days[day_key] = {'node': None, 'path': node_storage_path}
|
||||
else:
|
||||
node_storage_path = ""
|
||||
created_days[day_key] = {'node': None, 'path': None}
|
||||
|
||||
day_node = calendar_schemas.CalendarDayNode(
|
||||
unique_id=calendar_day_unique_id,
|
||||
uuid_string=calendar_day_uuid_string,
|
||||
date=current_date,
|
||||
day_of_week=current_date.strftime('%A'),
|
||||
iso_day=f"{year}-{month:02}-{day:02}",
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(day_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_day_nodes'].append(day_node)
|
||||
created_days[day_key] = day_node
|
||||
logger.info(f"Day node created: {day_node.unique_id}")
|
||||
created_days[day_key]['node'] = day_node
|
||||
logger.info(f"Day node created: {day_node.uuid_string}")
|
||||
|
||||
if last_day_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -205,7 +199,7 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {last_day_node.unique_id} to {day_node.unique_id}")
|
||||
logger.info(f"Relationship created from {last_day_node.uuid_string} to {day_node.uuid_string}")
|
||||
last_day_node = day_node
|
||||
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -213,13 +207,13 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {month_node.unique_id} to {day_node.unique_id}")
|
||||
logger.info(f"Relationship created from {month_node.uuid_string} to {day_node.uuid_string}")
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
calendar_relationships.WeekIncludesDay(source=week_node, target=day_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {week_node.unique_id} to {day_node.unique_id}")
|
||||
logger.info(f"Relationship created from {week_node.uuid_string} to {day_node.uuid_string}")
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
if time_chunk_node_length:
|
||||
@ -228,25 +222,32 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
for day_node in calendar_nodes['calendar_day_nodes']:
|
||||
total_time_chunks_in_day = (24 * 60) / time_chunk_interval
|
||||
for i in range(total_time_chunks_in_day):
|
||||
time_chunk_unique_id = f"{day_node.unique_id}_{i}"
|
||||
time_chunk_uuid_string = f"{day_node.uuid_string}_{i}"
|
||||
time_chunk_start_time = day_node.date.time() + timedelta(minutes=i * time_chunk_interval)
|
||||
time_chunk_end_time = time_chunk_start_time + timedelta(minutes=time_chunk_interval)
|
||||
# Generate storage path for time chunk node using Supabase Storage
|
||||
if storage_tools:
|
||||
chunk_id = f"{day_node.uuid_string}_{i:02d}"
|
||||
chunk_dir_created, node_storage_path = storage_tools.create_calendar_time_chunk_storage_path(day_node.uuid_string, i)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
time_chunk_node = calendar_schemas.CalendarTimeChunkNode(
|
||||
unique_id=time_chunk_unique_id,
|
||||
uuid_string=time_chunk_uuid_string,
|
||||
start_time=time_chunk_start_time,
|
||||
end_time=time_chunk_end_time,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(time_chunk_node, database=db_name, operation='merge')
|
||||
calendar_nodes['calendar_time_chunk_nodes'].append(time_chunk_node)
|
||||
logger.info(f"Time chunk node created: {time_chunk_node.unique_id}")
|
||||
logger.info(f"Time chunk node created: {time_chunk_node.uuid_string}")
|
||||
# Create a relationship between the time chunk node and the day node
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
calendar_relationships.DayIncludesTimeChunk(source=day_node, target=time_chunk_node),
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {day_node.unique_id} to {time_chunk_node.unique_id}")
|
||||
logger.info(f"Relationship created from {day_node.uuid_string} to {time_chunk_node.uuid_string}")
|
||||
# Create sequential relationship between the time chunk nodes
|
||||
if i > 0:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -254,7 +255,7 @@ def create_calendar(db_name, start_date, end_date, attach_to_calendar_node=False
|
||||
database=db_name,
|
||||
operation='merge'
|
||||
)
|
||||
logger.info(f"Relationship created from {calendar_nodes['calendar_time_chunk_nodes'][i-1].unique_id} to {time_chunk_node.unique_id}")
|
||||
logger.info(f"Relationship created from {calendar_nodes['calendar_time_chunk_nodes'][i-1].uuid_string} to {time_chunk_node.uuid_string}")
|
||||
|
||||
logger.info(f'Created calendar: {calendar_nodes["calendar_node"].unique_id}')
|
||||
logger.info(f'Calendar structure created successfully for {start_date} to {end_date}')
|
||||
return calendar_nodes
|
||||
@ -2,115 +2,31 @@ import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
from modules.database.schemas.nodes.schools.schools import SchoolNode
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient, CreateBucketOptions
|
||||
import modules.database.init.init_school_timetable as init_school_timetable
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
import modules.database.tools.supabase_storage_tools as storage_tools
|
||||
|
||||
def create_school_buckets(school_id: str, school_type: str, school_name: str, admin_access_token: str) -> dict:
|
||||
"""Create storage buckets for a school
|
||||
Args:
|
||||
school_id: The unique identifier for the school
|
||||
school_type: The type of school (e.g., 'development')
|
||||
school_name: The display name of the school
|
||||
admin_access_token: The admin access token for Supabase operations
|
||||
Returns:
|
||||
Dictionary containing results of bucket creation operations
|
||||
"""
|
||||
logger.info(f"Creating storage buckets for school {school_name} ({school_type}/{school_id})")
|
||||
def create_school(db_name: str, uuid_string: str, name: str, website: str, school_type: str, is_public: bool = True, school_node: SchoolNode | None = None, dataframes=None):
|
||||
if not name or not uuid_string or not website or not school_type:
|
||||
logger.error("School name, uuid_string, website and school_type are required to create a school.")
|
||||
raise ValueError("School name, uuid_string, website and school_type are required to create a school.")
|
||||
|
||||
storage_client = SupabaseServiceRoleClient.for_admin(admin_access_token)
|
||||
base_path = f"cc.institutes.{school_type}.{school_id}"
|
||||
|
||||
buckets = [
|
||||
# Main school buckets
|
||||
{
|
||||
"id": f"{base_path}.public",
|
||||
"options": CreateBucketOptions(
|
||||
name=f"{school_type.title()} School Files - {school_name} - Public Files",
|
||||
public=True,
|
||||
file_size_limit=50 * 1024 * 1024,
|
||||
allowed_mime_types=[
|
||||
'image/*', 'video/*', 'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain', 'text/csv', 'application/json'
|
||||
]
|
||||
)
|
||||
},
|
||||
{
|
||||
"id": f"{base_path}.private",
|
||||
"options": CreateBucketOptions(
|
||||
name=f"{school_type.title()} School Files - {school_name} - Private Files",
|
||||
public=False,
|
||||
file_size_limit=50 * 1024 * 1024,
|
||||
allowed_mime_types=[
|
||||
'image/*', 'video/*', 'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain', 'text/csv', 'application/json'
|
||||
]
|
||||
)
|
||||
},
|
||||
# Curriculum buckets
|
||||
{
|
||||
"id": f"{base_path}.curriculum.public",
|
||||
"options": CreateBucketOptions(
|
||||
name=f"{school_type.title()} School Files - {school_name} - Curriculum Public Files",
|
||||
public=True,
|
||||
file_size_limit=50 * 1024 * 1024,
|
||||
allowed_mime_types=[
|
||||
'image/*', 'video/*', 'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain', 'text/csv', 'application/json'
|
||||
]
|
||||
)
|
||||
},
|
||||
{
|
||||
"id": f"{base_path}.curriculum.private",
|
||||
"options": CreateBucketOptions(
|
||||
name=f"{school_type.title()} School Files - {school_name} - Curriculum Private Files",
|
||||
public=False,
|
||||
file_size_limit=50 * 1024 * 1024,
|
||||
allowed_mime_types=[
|
||||
'image/*', 'video/*', 'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain', 'text/csv', 'application/json'
|
||||
]
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
results = {}
|
||||
for bucket in buckets:
|
||||
try:
|
||||
result = storage_client.create_bucket(bucket["id"], bucket["options"])
|
||||
results[bucket["id"]] = {
|
||||
"status": "success",
|
||||
"result": result
|
||||
}
|
||||
logger.info(f"Successfully created bucket {bucket['id']}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating school bucket {bucket['id']}: {str(e)}")
|
||||
results[bucket["id"]] = {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
def create_school(db_name: str, id: str, name: str, website: str, school_type: str, is_public: bool = True, school_node: SchoolNode | None = None, dataframes=None):
|
||||
if not name or not id or not website or not school_type:
|
||||
logger.error("School name, id, website and school_type are required to create a school.")
|
||||
raise ValueError("School name, id, website and school_type are required to create a school.")
|
||||
|
||||
logger.info(f"Initialising neo4j connection...")
|
||||
logger.info(f"Initialising Neontology connection...")
|
||||
neon.init_neontology_connection()
|
||||
|
||||
# Initialize storage tools for school
|
||||
storage_tools_instance = storage_tools.SupabaseStorageTools(db_name, init_run_type="school")
|
||||
|
||||
# Generate the storage path for the school node
|
||||
school_dir_created, node_storage_path = storage_tools_instance.create_school_storage_path(uuid_string)
|
||||
logger.info(f"Generated school storage path: {node_storage_path}")
|
||||
|
||||
# Create School Node if not provided
|
||||
if not school_node:
|
||||
if is_public:
|
||||
school_node = SchoolNode(
|
||||
unique_id=f'School_{id}',
|
||||
tldraw_snapshot="",
|
||||
id=id,
|
||||
uuid_string=uuid_string,
|
||||
node_storage_path=node_storage_path,
|
||||
name=name,
|
||||
website=website,
|
||||
school_type=school_type
|
||||
@ -118,9 +34,8 @@ def create_school(db_name: str, id: str, name: str, website: str, school_type: s
|
||||
else:
|
||||
# Create private school node with default values
|
||||
school_node = SchoolNode(
|
||||
unique_id=f'School_{id}',
|
||||
tldraw_snapshot="",
|
||||
id=id,
|
||||
uuid_string=uuid_string,
|
||||
node_storage_path=node_storage_path,
|
||||
name=name,
|
||||
website=website,
|
||||
school_type=school_type,
|
||||
@ -133,6 +48,9 @@ def create_school(db_name: str, id: str, name: str, website: str, school_type: s
|
||||
statutory_high_age=18,
|
||||
school_capacity=1000
|
||||
)
|
||||
else:
|
||||
# Update existing school node with the storage path
|
||||
school_node.node_storage_path = node_storage_path
|
||||
|
||||
# First create/merge the school node in the main cc.institutes database
|
||||
logger.info(f"Creating school node in main cc.institutes database...")
|
||||
@ -144,15 +62,16 @@ def create_school(db_name: str, id: str, name: str, website: str, school_type: s
|
||||
|
||||
school_nodes = {
|
||||
'school_node': school_node,
|
||||
'db_name': db_name
|
||||
'db_name': db_name,
|
||||
'storage_path': node_storage_path
|
||||
}
|
||||
|
||||
if dataframes is not None:
|
||||
logger.info(f"Creating school timetable for {name} with {len(dataframes)} dataframes...")
|
||||
school_timetable_nodes = init_school_timetable.create_school_timetable(dataframes, db_name, school_node)
|
||||
school_timetable_nodes = init_school_timetable.create_school_timetable(dataframes, db_name, school_node, storage_tools_instance)
|
||||
school_nodes['school_timetable_nodes'] = school_timetable_nodes
|
||||
else:
|
||||
logger.warning(f"No dataframes provided for {name}, skipping school timetable...")
|
||||
|
||||
logger.info(f"School {name} created successfully...")
|
||||
logger.info(f"School {name} created successfully with storage path: {node_storage_path}")
|
||||
return school_nodes
|
||||
|
||||
@ -4,6 +4,7 @@ logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH
|
||||
import pandas as pd
|
||||
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
import modules.database.tools.supabase_storage_tools as storage_tools
|
||||
import modules.database.schemas.nodes.schools.schools as school_nodes
|
||||
import modules.database.schemas.nodes.schools.curriculum as curriculum_nodes
|
||||
import modules.database.schemas.nodes.schools.pastoral as pastoral_nodes
|
||||
@ -40,7 +41,7 @@ def sort_year_groups(df):
|
||||
df['YearGroupNumeric'] = pd.to_numeric(df['YearGroup'], errors='coerce')
|
||||
return df.sort_values(by='YearGroupNumeric')
|
||||
|
||||
def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_node: school_nodes.SchoolNode):
|
||||
def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_node: school_nodes.SchoolNode, storage_tools=None):
|
||||
|
||||
logger.info(f"Initialising neo4j connection...")
|
||||
neon.init_neontology_connection()
|
||||
@ -70,10 +71,20 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
last_key_stage_node = None
|
||||
|
||||
# Create Department Structure node
|
||||
department_structure_node_unique_id = f"DepartmentStructure_{school_node.unique_id}"
|
||||
department_structure_node_uuid_string = f"DepartmentStructure_{school_node.uuid_string}"
|
||||
|
||||
# For structure nodes, we can use a simple path or leave empty for consistency
|
||||
# Since this is just an organizational node, we'll use a simple path
|
||||
if storage_tools:
|
||||
# Use the school's base department directory as the structure node path
|
||||
dept_structure_path = f"cc.public.snapshots/DepartmentStructure/{school_node.uuid_string}"
|
||||
node_storage_path = dept_structure_path
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
department_structure_node = school_structures.DepartmentStructureNode(
|
||||
unique_id=department_structure_node_unique_id,
|
||||
tldraw_snapshot=""
|
||||
uuid_string=department_structure_node_uuid_string,
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create in school database only
|
||||
neon.create_or_merge_neontology_node(department_structure_node, database=db_name, operation='merge')
|
||||
@ -86,10 +97,17 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
)
|
||||
logger.info(f"Created department structure node and linked to school")
|
||||
|
||||
curriculum_structure_node_unique_id = f"CurriculumStructure_{school_node.unique_id}"
|
||||
curriculum_structure_node_uuid_string = f"CurriculumStructure_{school_node.uuid_string}"
|
||||
|
||||
# Generate storage path for curriculum structure node
|
||||
if storage_tools:
|
||||
curriculum_dir_created, node_storage_path = storage_tools.create_curriculum_storage_path(curriculum_structure_node_uuid_string)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
curriculum_node = school_structures.CurriculumStructureNode(
|
||||
unique_id=curriculum_structure_node_unique_id,
|
||||
tldraw_snapshot=""
|
||||
uuid_string=curriculum_structure_node_uuid_string,
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create in school database only
|
||||
neon.create_or_merge_neontology_node(curriculum_node, database=db_name, operation='merge')
|
||||
@ -102,10 +120,17 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
)
|
||||
logger.info(f"Created curriculum node and relationship with school")
|
||||
|
||||
pastoral_structure_node_unique_id = f"PastoralStructure_{school_node.unique_id}"
|
||||
pastoral_structure_node_uuid_string = f"PastoralStructure_{school_node.uuid_string}"
|
||||
|
||||
# Generate storage path for pastoral structure node
|
||||
if storage_tools:
|
||||
pastoral_dir_created, node_storage_path = storage_tools.create_pastoral_storage_path(pastoral_structure_node_uuid_string)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
pastoral_node = school_structures.PastoralStructureNode(
|
||||
unique_id=pastoral_structure_node_unique_id,
|
||||
tldraw_snapshot=""
|
||||
uuid_string=pastoral_structure_node_uuid_string,
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(pastoral_node, database=db_name, operation='merge')
|
||||
node_library['pastoral_node'] = pastoral_node
|
||||
@ -120,11 +145,20 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
unique_departments = keystagesyllabus_df['Department'].dropna().unique()
|
||||
|
||||
for department_name in unique_departments:
|
||||
department_unique_id = f"Department_{school_node.unique_id}_{department_name.replace(' ', '_')}"
|
||||
department_uuid_string = f"Department_{school_node.uuid_string}_{department_name.replace(' ', '_')}"
|
||||
|
||||
# Generate storage path for department node using the storage tools
|
||||
if storage_tools:
|
||||
# Create department directory under the school's department structure
|
||||
dept_path = f"cc.public.snapshots/Department/{department_uuid_string}"
|
||||
node_storage_path = dept_path
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
department_node = school_nodes.DepartmentNode(
|
||||
unique_id=department_unique_id,
|
||||
uuid_string=department_uuid_string,
|
||||
name=department_name,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create department in school database only
|
||||
neon.create_or_merge_neontology_node(department_node, database=db_name, operation='merge')
|
||||
@ -147,17 +181,25 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
|
||||
# Process subjects from key stage syllabuses first (these have department info)
|
||||
for _, subject_row in unique_subjects.iterrows():
|
||||
subject_unique_id = f"Subject_{school_node.unique_id}_{subject_row['SubjectCode']}"
|
||||
subject_uuid_string = f"Subject_{school_node.uuid_string}_{subject_row['SubjectCode']}"
|
||||
department_node = node_library['department_nodes'].get(subject_row['Department'])
|
||||
if not department_node:
|
||||
logger.warning(f"No department found for subject {subject_row['Subject']} with code {subject_row['SubjectCode']}")
|
||||
continue
|
||||
|
||||
# Generate storage path for subject node
|
||||
if storage_tools:
|
||||
# Create subject directory under the specific department
|
||||
subject_path = f"cc.public.snapshots/Subject/{subject_uuid_string}"
|
||||
node_storage_path = subject_path
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
subject_node = curriculum_nodes.SubjectNode(
|
||||
unique_id=subject_unique_id,
|
||||
uuid_string=subject_uuid_string,
|
||||
id=subject_row['SubjectCode'],
|
||||
name=subject_row['Subject'],
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create subject in both databases
|
||||
neon.create_or_merge_neontology_node(subject_node, database=db_name, operation='merge')
|
||||
@ -173,14 +215,23 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
|
||||
# Process any additional subjects from year group syllabuses (these won't have department info)
|
||||
for _, subject_row in additional_subjects.iterrows():
|
||||
subject_unique_id = f"Subject_{school_node.unique_id}_{subject_row['SubjectCode']}"
|
||||
subject_uuid_string = f"Subject_{school_node.uuid_string}_{subject_row['SubjectCode']}"
|
||||
# Create in a special "Unassigned" department
|
||||
unassigned_dept_name = "Unassigned Department"
|
||||
if unassigned_dept_name not in node_library['department_nodes']:
|
||||
# Generate storage path for unassigned department node
|
||||
if filesystem:
|
||||
# Create unassigned department directory under the school's department structure
|
||||
unassigned_dept_path = os.path.join(filesystem.root_path, "departments", "Unassigned")
|
||||
filesystem.create_directory(unassigned_dept_path)
|
||||
node_storage_path = os.path.relpath(unassigned_dept_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
department_node = school_nodes.DepartmentNode(
|
||||
unique_id=f"Department_{school_node.unique_id}_Unassigned",
|
||||
uuid_string=f"Department_{school_node.uuid_string}_Unassigned",
|
||||
name=unassigned_dept_name,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(department_node, database=db_name, operation='merge')
|
||||
node_library['department_nodes'][unassigned_dept_name] = department_node
|
||||
@ -192,11 +243,20 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
)
|
||||
logger.info(f"Created unassigned department node and linked to department structure")
|
||||
|
||||
# Generate storage path for subject node
|
||||
if filesystem:
|
||||
# Create subject directory under the unassigned department
|
||||
subject_path = os.path.join(filesystem.root_path, "departments", "Unassigned", subject_row['Subject'].replace(' ', '_'))
|
||||
filesystem.create_directory(subject_path)
|
||||
node_storage_path = os.path.relpath(subject_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
subject_node = curriculum_nodes.SubjectNode(
|
||||
unique_id=subject_unique_id,
|
||||
uuid_string=subject_uuid_string,
|
||||
id=subject_row['SubjectCode'],
|
||||
name=subject_row['Subject'],
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create subject in both databases
|
||||
neon.create_or_merge_neontology_node(subject_node, database=db_name, operation='merge')
|
||||
@ -226,20 +286,29 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
# First create all key stage nodes and key stage syllabus nodes
|
||||
for index, ks_row in keystagesyllabus_df.sort_values('KeyStage').iterrows():
|
||||
key_stage = str(ks_row['KeyStage'])
|
||||
logger.debug(f"Processing key stage syllabus row - Subject: {ks_row['Subject']}, Key Stage: {key_stage}")
|
||||
subject = str(ks_row['Subject'])
|
||||
syllabus_id = str(ks_row['ID'])
|
||||
logger.debug(f"Processing key stage syllabus row - Subject: {subject}, Key Stage: {key_stage}, Syllabus ID: {syllabus_id}")
|
||||
|
||||
subject_node = node_library['subject_nodes'].get(ks_row['Subject'])
|
||||
subject_node = node_library['subject_nodes'].get(subject)
|
||||
if not subject_node:
|
||||
logger.warning(f"No subject node found for subject {ks_row['Subject']}")
|
||||
logger.warning(f"No subject node found for subject {subject}")
|
||||
continue
|
||||
|
||||
if key_stage not in key_stage_nodes_created:
|
||||
key_stage_node_unique_id = f"KeyStage_{curriculum_node.unique_id}_KStg{key_stage}"
|
||||
key_stage_node_uuid_string = f"KeyStage_{curriculum_node.uuid_string}_KStg{key_stage}"
|
||||
# Generate storage path for key stage node
|
||||
if filesystem:
|
||||
key_stage_dir_created, key_stage_path = filesystem.create_curriculum_key_stage_syllabus_directory(curriculum_path, key_stage, subject, syllabus_id)
|
||||
node_storage_path = os.path.relpath(key_stage_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
key_stage_node = curriculum_nodes.KeyStageNode(
|
||||
unique_id=key_stage_node_unique_id,
|
||||
uuid_string=key_stage_node_uuid_string,
|
||||
name=f"Key Stage {key_stage}",
|
||||
key_stage=str(key_stage),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create key stage node in both databases
|
||||
neon.create_or_merge_neontology_node(key_stage_node, database=db_name, operation='merge')
|
||||
@ -252,7 +321,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.CurriculumStructureIncludesKeyStage(source=curriculum_node, target=key_stage_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created key stage node {key_stage_node_unique_id} and relationship with curriculum structure")
|
||||
logger.info(f"Created key stage node {key_stage_node_uuid_string} and relationship with curriculum structure")
|
||||
|
||||
# Create sequential relationship between key stages in both databases
|
||||
if last_key_stage_node:
|
||||
@ -264,27 +333,34 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.KeyStageFollowsKeyStage(source=last_key_stage_node, target=key_stage_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between key stages {last_key_stage_node.unique_id} and {key_stage_node.unique_id}")
|
||||
logger.info(f"Created sequential relationship between key stages {last_key_stage_node.uuid_string} and {key_stage_node.uuid_string}")
|
||||
last_key_stage_node = key_stage_node
|
||||
|
||||
# Create key stage syllabus under the subject's curriculum directory
|
||||
key_stage_syllabus_node_unique_id = f"KeyStageSyllabus_{curriculum_node.unique_id}_{ks_row['Title'].replace(' ', '')}"
|
||||
key_stage_syllabus_node_uuid_string = f"KeyStageSyllabus_{curriculum_node.uuid_string}_{ks_row['Title'].replace(' ', '')}"
|
||||
logger.debug(f"Creating key stage syllabus node for {ks_row['Subject']} KS{key_stage} with ID {ks_row['ID']}")
|
||||
|
||||
key_stage_syllabus_node_unique_id = f"KeyStageSyllabus_{curriculum_node.unique_id}_{ks_row['Title'].replace(' ', '')}"
|
||||
key_stage_syllabus_node_uuid_string = f"KeyStageSyllabus_{curriculum_node.uuid_string}_{ks_row['Title'].replace(' ', '')}"
|
||||
# Generate storage path for key stage syllabus node
|
||||
if filesystem:
|
||||
syllabus_dir_created, syllabus_path = filesystem.create_curriculum_key_stage_syllabus_directory(curriculum_path, key_stage, ks_row['Subject'], ks_row['ID'])
|
||||
node_storage_path = os.path.relpath(syllabus_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
key_stage_syllabus_node = curriculum_nodes.KeyStageSyllabusNode(
|
||||
unique_id=key_stage_syllabus_node_unique_id,
|
||||
uuid_string=key_stage_syllabus_node_uuid_string,
|
||||
id=ks_row['ID'],
|
||||
name=ks_row['Title'],
|
||||
key_stage=str(ks_row['KeyStage']),
|
||||
subject_name=ks_row['Subject'],
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create key stage syllabus node in both databases
|
||||
neon.create_or_merge_neontology_node(key_stage_syllabus_node, database=db_name, operation='merge')
|
||||
neon.create_or_merge_neontology_node(key_stage_syllabus_node, database=curriculum_db_name, operation='merge')
|
||||
node_library['key_stage_syllabus_nodes'][ks_row['ID']] = key_stage_syllabus_node
|
||||
logger.debug(f"Created key stage syllabus node {key_stage_syllabus_node_unique_id} for {ks_row['Subject']} KS{key_stage}")
|
||||
logger.debug(f"Created key stage syllabus node {key_stage_syllabus_node_uuid_string} for {ks_row['Subject']} KS{key_stage}")
|
||||
|
||||
# Link key stage syllabus to its subject in both databases
|
||||
if subject_node:
|
||||
@ -296,7 +372,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.SubjectHasKeyStageSyllabus(source=subject_node, target=key_stage_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between subject {subject_node.unique_id} and key stage syllabus {key_stage_syllabus_node.unique_id}")
|
||||
logger.info(f"Created relationship between subject {subject_node.uuid_string} and key stage syllabus {key_stage_syllabus_node.uuid_string}")
|
||||
|
||||
# Link key stage syllabus to its key stage in both databases
|
||||
key_stage_node = key_stage_nodes_created.get(key_stage)
|
||||
@ -309,7 +385,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.KeyStageIncludesKeyStageSyllabus(source=key_stage_node, target=key_stage_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between key stage {key_stage_node.unique_id} and key stage syllabus {key_stage_syllabus_node.unique_id}")
|
||||
logger.info(f"Created relationship between key stage {key_stage_node.uuid_string} and key stage syllabus {key_stage_syllabus_node.uuid_string}")
|
||||
|
||||
# Create sequential relationship between key stage syllabuses in both databases
|
||||
last_key_stage_syllabus_node = last_key_stage_syllabus_nodes.get(ks_row['Subject'])
|
||||
@ -322,7 +398,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.KeyStageSyllabusFollowsKeyStageSyllabus(source=last_key_stage_syllabus_node, target=key_stage_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between key stage syllabuses {last_key_stage_syllabus_node.unique_id} and {key_stage_syllabus_node.unique_id}")
|
||||
logger.info(f"Created sequential relationship between key stage syllabuses {last_key_stage_syllabus_node.uuid_string} and {key_stage_syllabus_node.uuid_string}")
|
||||
last_key_stage_syllabus_nodes[ks_row['Subject']] = key_stage_syllabus_node
|
||||
|
||||
# Now process year groups and their syllabuses
|
||||
@ -339,12 +415,19 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
if pd.notna(numeric_year_group):
|
||||
numeric_year_group = int(numeric_year_group)
|
||||
if numeric_year_group not in year_group_nodes_created:
|
||||
year_group_node_unique_id = f"YearGroup_{school_node.unique_id}_YGrp{numeric_year_group}"
|
||||
year_group_node_uuid_string = f"YearGroup_{school_node.uuid_string}_YGrp{numeric_year_group}"
|
||||
# Generate storage path for year group node
|
||||
if filesystem:
|
||||
year_group_dir_created, year_group_path = filesystem.create_pastoral_year_group_directory(pastoral_path, numeric_year_group)
|
||||
node_storage_path = os.path.relpath(year_group_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
year_group_node = pastoral_nodes.YearGroupNode(
|
||||
unique_id=year_group_node_unique_id,
|
||||
uuid_string=year_group_node_uuid_string,
|
||||
year_group=str(numeric_year_group),
|
||||
name=f"Year {numeric_year_group}, {year_group}",
|
||||
tldraw_snapshot=""
|
||||
name=f"Year {numeric_year_group}",
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create year group node in both databases but use same directory
|
||||
neon.create_or_merge_neontology_node(year_group_node, database=db_name, operation='merge')
|
||||
@ -360,7 +443,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.YearGroupFollowsYearGroup(source=last_year_group_node, target=year_group_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between year groups {last_year_group_node.unique_id} and {year_group_node.unique_id} across key stages")
|
||||
logger.info(f"Created sequential relationship between year groups {last_year_group_node.uuid_string} and {year_group_node.uuid_string} across key stages")
|
||||
last_year_group_node = year_group_node
|
||||
|
||||
# Create relationship with Pastoral Structure in school database only
|
||||
@ -368,7 +451,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.PastoralStructureIncludesYearGroup(source=pastoral_node, target=year_group_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created year group node {year_group_node_unique_id} and relationship with pastoral structure")
|
||||
logger.info(f"Created year group node {year_group_node_uuid_string} and relationship with pastoral structure")
|
||||
|
||||
year_group_nodes_created[numeric_year_group] = year_group_node
|
||||
node_library['year_group_nodes'][str(numeric_year_group)] = year_group_node
|
||||
@ -376,14 +459,21 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
# Create year group syllabus nodes in both databases
|
||||
year_group_node = year_group_nodes_created.get(numeric_year_group)
|
||||
if year_group_node:
|
||||
year_group_syllabus_node_unique_id = f"YearGroupSyllabus_{school_node.unique_id}_{yg_row['ID']}"
|
||||
year_group_syllabus_node_uuid_string = f"YearGroupSyllabus_{school_node.uuid_string}_{yg_row['ID']}"
|
||||
# Generate storage path for year group syllabus node
|
||||
if filesystem:
|
||||
yg_syllabus_dir_created, yg_syllabus_path = filesystem.create_curriculum_year_group_syllabus_directory(curriculum_path, yg_row['Subject'], numeric_year_group, yg_row['ID'])
|
||||
node_storage_path = os.path.relpath(yg_syllabus_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
year_group_syllabus_node = pastoral_nodes.YearGroupSyllabusNode(
|
||||
unique_id=year_group_syllabus_node_unique_id,
|
||||
uuid_string=year_group_syllabus_node_uuid_string,
|
||||
id=yg_row['ID'],
|
||||
name=yg_row['Title'],
|
||||
year_group=str(yg_row['YearGroup']),
|
||||
subject_name=yg_row['Subject'],
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
|
||||
# Create year group syllabus node in both databases but use same directory
|
||||
@ -406,7 +496,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.YearGroupSyllabusFollowsYearGroupSyllabus(source=last_year_group_syllabus_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between year group syllabuses {last_year_group_syllabus_node.unique_id} and {year_group_syllabus_node.unique_id}")
|
||||
logger.info(f"Created sequential relationship between year group syllabuses {last_year_group_syllabus_node.uuid_string} and {year_group_syllabus_node.uuid_string}")
|
||||
last_year_group_syllabus_nodes[yg_row['Subject']] = year_group_syllabus_node
|
||||
|
||||
# Create relationships in both databases using MATCH to avoid cartesian products
|
||||
@ -421,7 +511,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.SubjectHasYearGroupSyllabus(source=subject_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between subject {subject_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
logger.info(f"Created relationship between subject {subject_node.uuid_string} and year group syllabus {year_group_syllabus_node_uuid_string}")
|
||||
|
||||
# Link to year group
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -432,7 +522,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.YearGroupHasYearGroupSyllabus(source=year_group_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between year group {year_group_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
logger.info(f"Created relationship between year group {year_group_node.uuid_string} and year group syllabus {year_group_syllabus_node_uuid_string}")
|
||||
|
||||
# Link to key stage syllabus if it exists for the same subject
|
||||
key_stage_syllabus_node = node_library['key_stage_syllabus_nodes'].get(ks_row['ID'])
|
||||
@ -445,7 +535,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.KeyStageSyllabusIncludesYearGroupSyllabus(source=key_stage_syllabus_node, target=year_group_syllabus_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between key stage syllabus {key_stage_syllabus_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
logger.info(f"Created relationship between key stage syllabus {key_stage_syllabus_node.uuid_string} and year group syllabus {year_group_syllabus_node_uuid_string}")
|
||||
|
||||
# Process topics for this year group syllabus only if not already processed
|
||||
topics_for_syllabus = topic_df[topic_df['SyllabusYearID'] == yg_row['ID']]
|
||||
@ -472,22 +562,29 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
if (syllabus_node.subject_name == topic_subject and
|
||||
syllabus_node.key_stage == str(topic_key_stage)):
|
||||
matching_syllabus_node = syllabus_node
|
||||
logger.debug(f"Found matching syllabus node: {syllabus_node.unique_id}")
|
||||
logger.debug(f"Found matching syllabus node: {syllabus_node.uuid_string}")
|
||||
break
|
||||
|
||||
if not matching_syllabus_node:
|
||||
logger.warning(f"No key stage syllabus node found for subject {topic_subject} and key stage {topic_key_stage}, skipping topic creation")
|
||||
continue
|
||||
|
||||
topic_node_unique_id = f"Topic_{matching_syllabus_node.unique_id}_{topic_row['TopicID']}"
|
||||
topic_node_uuid_string = f"Topic_{matching_syllabus_node.uuid_string}_{topic_row['TopicID']}"
|
||||
# Generate storage path for topic node
|
||||
if filesystem:
|
||||
topic_dir_created, topic_path = filesystem.create_curriculum_topic_directory(yg_syllabus_path, topic_row['TopicID'])
|
||||
node_storage_path = os.path.relpath(topic_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
topic_node = curriculum_nodes.TopicNode(
|
||||
unique_id=topic_node_unique_id,
|
||||
uuid_string=topic_node_uuid_string,
|
||||
id=topic_row['TopicID'],
|
||||
name=topic_row.get('TopicTitle', default_topic_values['topic_title']),
|
||||
total_number_of_lessons_for_topic=str(topic_row.get('TotalNumberOfLessonsForTopic', default_topic_values['total_number_of_lessons_for_topic'])),
|
||||
type=topic_row.get('TopicType', default_topic_values['topic_type']),
|
||||
assessment_type=topic_row.get('TopicAssessmentType', default_topic_values['topic_assessment_type']),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create topic node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(topic_node, database=curriculum_db_name, operation='merge')
|
||||
@ -502,7 +599,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.YearGroupSyllabusIncludesTopic(source=year_group_syllabus_node, target=topic_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationships between topic {topic_node_unique_id} and key stage syllabus {matching_syllabus_node.unique_id} and year group syllabus {year_group_syllabus_node_unique_id}")
|
||||
logger.info(f"Created relationships between topic {topic_node_uuid_string} and key stage syllabus {matching_syllabus_node.uuid_string} and year group syllabus {year_group_syllabus_node_uuid_string}")
|
||||
|
||||
# Process lessons for this topic only if not already processed
|
||||
lessons_for_topic = lesson_df[
|
||||
@ -518,8 +615,15 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
continue
|
||||
lessons_processed.add(lesson_row['LessonID'])
|
||||
|
||||
# Generate storage path for lesson node
|
||||
if filesystem:
|
||||
lesson_dir_created, lesson_path = filesystem.create_curriculum_lesson_directory(topic_path, lesson_row['LessonID'])
|
||||
node_storage_path = os.path.relpath(lesson_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
lesson_node = curriculum_nodes.TopicLessonNode(
|
||||
unique_id=f"TopicLesson_{topic_node_unique_id}_{lesson_row['LessonID']}",
|
||||
uuid_string=f"TopicLesson_{topic_node_uuid_string}_{lesson_row['LessonID']}",
|
||||
id=lesson_row['LessonID'],
|
||||
name=lesson_row.get('LessonTitle', default_topic_lesson_values['topic_lesson_title']),
|
||||
type=lesson_row.get('LessonType', default_topic_lesson_values['topic_lesson_type']),
|
||||
@ -527,7 +631,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
suggested_activities=str(lesson_row.get('SuggestedActivities', default_topic_lesson_values['topic_lesson_suggested_activities'])),
|
||||
skills_learned=str(lesson_row.get('SkillsLearned', default_topic_lesson_values['topic_lesson_skills_learned'])),
|
||||
weblinks=str(lesson_row.get('WebLinks', default_topic_lesson_values['topic_lesson_weblinks'])),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create lesson node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(lesson_node, database=curriculum_db_name, operation='merge')
|
||||
@ -538,7 +642,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.TopicIncludesTopicLesson(source=topic_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created lesson node {lesson_node.unique_id} and relationship with topic {topic_node.unique_id}")
|
||||
logger.info(f"Created lesson node {lesson_node.uuid_string} and relationship with topic {topic_node.uuid_string}")
|
||||
|
||||
# Create sequential relationships between lessons
|
||||
if lesson_row['Lesson'].isdigit() and previous_lesson_node:
|
||||
@ -546,7 +650,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.TopicLessonFollowsTopicLesson(source=previous_lesson_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between lessons {previous_lesson_node.unique_id} and {lesson_node.unique_id}")
|
||||
logger.info(f"Created sequential relationship between lessons {previous_lesson_node.uuid_string} and {lesson_node.uuid_string}")
|
||||
previous_lesson_node = lesson_node
|
||||
|
||||
# Process learning statements for this lesson only if not already processed
|
||||
@ -558,12 +662,19 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
if statement_row['StatementID'] in statements_processed:
|
||||
continue
|
||||
statements_processed.add(statement_row['StatementID'])
|
||||
# Generate storage path for learning statement node
|
||||
if filesystem:
|
||||
statement_dir_created, statement_path = filesystem.create_curriculum_learning_statement_directory(lesson_path, statement_row['StatementID'])
|
||||
node_storage_path = os.path.relpath(statement_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
statement_node = curriculum_nodes.LearningStatementNode(
|
||||
unique_id=f"LearningStatement_{lesson_node.unique_id}_{statement_row['StatementID']}",
|
||||
uuid_string=f"LearningStatement_{lesson_node.uuid_string}_{statement_row['StatementID']}",
|
||||
id=statement_row['StatementID'],
|
||||
name=statement_row.get('LearningStatement', default_learning_statement_values['lesson_learning_statement']),
|
||||
type=statement_row.get('StatementType', default_learning_statement_values['lesson_learning_statement_type']),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create statement node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(statement_node, database=curriculum_db_name, operation='merge')
|
||||
@ -574,7 +685,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.LessonIncludesLearningStatement(source=lesson_node, target=statement_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created learning statement node {statement_node.unique_id} and relationship with lesson {lesson_node.unique_id}")
|
||||
logger.info(f"Created learning statement node {statement_node.uuid_string} and relationship with lesson {lesson_node.uuid_string}")
|
||||
else:
|
||||
logger.warning(f"No year group node found for year group {year_group}, skipping syllabus creation")
|
||||
|
||||
@ -601,15 +712,23 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
logger.warning(f"No key stage syllabus node found for subject {topic_subject} and key stage {topic_key_stage}, skipping topic creation")
|
||||
continue
|
||||
|
||||
topic_node_unique_id = f"Topic_{matching_syllabus_node.unique_id}_{topic_row['TopicID']}"
|
||||
topic_node_uuid_string = f"Topic_{matching_syllabus_node.uuid_string}_{topic_row['TopicID']}"
|
||||
# Generate storage path for topic node
|
||||
if filesystem:
|
||||
syllabus_path = os.path.join(curriculum_path, "subjects", topic_subject, "key_stage_syllabuses", f"KS{topic_key_stage}", f"KS{topic_key_stage}.{topic_subject}")
|
||||
topic_dir_created, keystage_topic_path = filesystem.create_curriculum_keystage_topic_directory(syllabus_path, topic_row['TopicID'])
|
||||
node_storage_path = os.path.relpath(keystage_topic_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
topic_node = curriculum_nodes.TopicNode(
|
||||
unique_id=topic_node_unique_id,
|
||||
uuid_string=topic_node_uuid_string,
|
||||
id=topic_row['TopicID'],
|
||||
name=topic_row.get('TopicTitle', default_topic_values['topic_title']),
|
||||
total_number_of_lessons_for_topic=str(topic_row.get('TotalNumberOfLessonsForTopic', default_topic_values['total_number_of_lessons_for_topic'])),
|
||||
type=topic_row.get('TopicType', default_topic_values['topic_type']),
|
||||
assessment_type=topic_row.get('TopicAssessmentType', default_topic_values['topic_assessment_type']),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create topic node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(topic_node, database=curriculum_db_name, operation='merge')
|
||||
@ -621,7 +740,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.KeyStageSyllabusIncludesTopic(source=matching_syllabus_node, target=topic_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship between topic {topic_node_unique_id} and key stage syllabus {matching_syllabus_node.unique_id}")
|
||||
logger.info(f"Created relationship between topic {topic_node_uuid_string} and key stage syllabus {matching_syllabus_node.uuid_string}")
|
||||
|
||||
# Process lessons for this topic
|
||||
lessons_for_topic = lesson_df[
|
||||
@ -636,8 +755,15 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
if lesson_row['LessonID'] in lessons_processed:
|
||||
continue
|
||||
lessons_processed.add(lesson_row['LessonID'])
|
||||
# Generate storage path for lesson node
|
||||
if filesystem:
|
||||
lesson_dir_created, lesson_path = filesystem.create_curriculum_lesson_directory(topic_path, lesson_row['LessonID'])
|
||||
node_storage_path = os.path.relpath(lesson_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
lesson_node = curriculum_nodes.TopicLessonNode(
|
||||
unique_id=f"TopicLesson_{topic_node_unique_id}_{lesson_row['LessonID']}",
|
||||
uuid_string=f"TopicLesson_{topic_node_uuid_string}_{lesson_row['LessonID']}",
|
||||
id=lesson_row['LessonID'],
|
||||
name=lesson_row.get('LessonTitle', default_topic_lesson_values['topic_lesson_title']),
|
||||
type=lesson_row.get('LessonType', default_topic_lesson_values['topic_lesson_type']),
|
||||
@ -645,7 +771,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
suggested_activities=str(lesson_row.get('SuggestedActivities', default_topic_lesson_values['topic_lesson_suggested_activities'])),
|
||||
skills_learned=str(lesson_row.get('SkillsLearned', default_topic_lesson_values['topic_lesson_skills_learned'])),
|
||||
weblinks=str(lesson_row.get('WebLinks', default_topic_lesson_values['topic_lesson_weblinks'])),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create lesson node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(lesson_node, database=curriculum_db_name, operation='merge')
|
||||
@ -656,7 +782,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.TopicIncludesTopicLesson(source=topic_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created lesson node {lesson_node.unique_id} and relationship with topic {topic_node.unique_id}")
|
||||
logger.info(f"Created lesson node {lesson_node.uuid_string} and relationship with topic {topic_node.uuid_string}")
|
||||
|
||||
# Create sequential relationships between lessons
|
||||
if lesson_row['Lesson'].isdigit() and previous_lesson_node:
|
||||
@ -664,7 +790,7 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.TopicLessonFollowsTopicLesson(source=previous_lesson_node, target=lesson_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created sequential relationship between lessons {previous_lesson_node.unique_id} and {lesson_node.unique_id}")
|
||||
logger.info(f"Created sequential relationship between lessons {previous_lesson_node.uuid_string} and {lesson_node.uuid_string}")
|
||||
previous_lesson_node = lesson_node
|
||||
|
||||
# Process learning statements for this lesson
|
||||
@ -676,12 +802,19 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
if statement_row['StatementID'] in statements_processed:
|
||||
continue
|
||||
statements_processed.add(statement_row['StatementID'])
|
||||
# Generate storage path for learning statement node
|
||||
if filesystem:
|
||||
statement_dir_created, statement_path = filesystem.create_curriculum_learning_statement_directory(lesson_path, statement_row['StatementID'])
|
||||
node_storage_path = os.path.relpath(statement_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
statement_node = curriculum_nodes.LearningStatementNode(
|
||||
unique_id=f"LearningStatement_{lesson_node.unique_id}_{statement_row['StatementID']}",
|
||||
uuid_string=f"LearningStatement_{lesson_node.uuid_string}_{statement_row['StatementID']}",
|
||||
id=statement_row['StatementID'],
|
||||
name=statement_row.get('LearningStatement', default_learning_statement_values['lesson_learning_statement']),
|
||||
type=statement_row.get('StatementType', default_learning_statement_values['lesson_learning_statement_type']),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Create statement node in curriculum database only
|
||||
neon.create_or_merge_neontology_node(statement_node, database=curriculum_db_name, operation='merge')
|
||||
@ -692,6 +825,6 @@ def create_curriculum(dataframes, db_name: str, curriculum_db_name: str, school_
|
||||
curriculum_relationships.LessonIncludesLearningStatement(source=lesson_node, target=statement_node),
|
||||
database=curriculum_db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created learning statement node {statement_node.unique_id} and relationship with lesson {lesson_node.unique_id}")
|
||||
logger.info(f"Created learning statement node {statement_node.uuid_string} and relationship with lesson {lesson_node.uuid_string}")
|
||||
|
||||
return node_library
|
||||
@ -11,7 +11,7 @@ import modules.database.schemas.relationships.calendar_timetable_rels as cal_tt_
|
||||
import modules.database.init.init_calendar as init_calendar
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
|
||||
def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
def create_school_timetable(dataframes, db_name, school_node=None, filesystem=None):
|
||||
logger.info(f"Creating school timetable for {db_name}")
|
||||
if dataframes is None:
|
||||
raise ValueError("Data is required to create the calendar and timetable.")
|
||||
@ -22,10 +22,10 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
school_df = dataframes['school']
|
||||
if school_node is None:
|
||||
logger.info(f"School node is None, using school data from dataframe")
|
||||
school_unique_id = school_df[school_df['Identifier'] == 'SchoolID']['Data'].iloc[0]
|
||||
school_uuid_string = school_df[school_df['Identifier'] == 'SchoolID']['Data'].iloc[0]
|
||||
else:
|
||||
logger.info(f"School node is not None, using school data from school node: {school_node}")
|
||||
school_unique_id = school_node.unique_id
|
||||
school_uuid_string = school_node.uuid_string
|
||||
|
||||
terms_df = dataframes['terms']
|
||||
weeks_df = dataframes['weeks']
|
||||
@ -54,20 +54,30 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
}
|
||||
|
||||
# Create AcademicTimetable Node
|
||||
school_timetable_unique_id = f"{school_unique_id}_{school_year_start_date.year}_{school_year_end_date.year}"
|
||||
school_timetable_uuid_string = f"{school_uuid_string}_{school_year_start_date.year}_{school_year_end_date.year}"
|
||||
|
||||
# Generate storage path for timetable node
|
||||
if filesystem:
|
||||
timetable_dir_created, timetable_path = filesystem.create_school_timetable_directory()
|
||||
node_storage_path = os.path.relpath(timetable_path, filesystem.base_path)
|
||||
logger.info(f"Generated timetable node_storage_path: {node_storage_path}")
|
||||
else:
|
||||
node_storage_path = ""
|
||||
logger.warning("No filesystem provided, using empty storage path")
|
||||
|
||||
school_timetable_node = timetable.SchoolTimetableNode(
|
||||
school_timetable_id=school_timetable_unique_id,
|
||||
unique_id=school_timetable_unique_id,
|
||||
school_timetable_id=school_timetable_uuid_string,
|
||||
uuid_string=school_timetable_uuid_string,
|
||||
start_date=school_year_start_date,
|
||||
end_date=school_year_end_date,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(school_timetable_node, database=db_name, operation='merge')
|
||||
timetable_nodes['timetable_node'] = school_timetable_node
|
||||
|
||||
if school_node:
|
||||
logger.info(f"Creating calendar for {school_unique_id} from Neo4j SchoolNode: {school_node.unique_id}")
|
||||
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=True, owner_node=school_node)
|
||||
logger.info(f"Creating calendar for {school_uuid_string} from Neo4j SchoolNode: {school_node.uuid_string}")
|
||||
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=True, owner_node=school_node, filesystem=filesystem)
|
||||
# Link the school node to the timetable node
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
entity_tt_rels.SchoolHasTimetable(source=school_node, target=school_timetable_node),
|
||||
@ -75,26 +85,34 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
)
|
||||
timetable_nodes['calendar_nodes'] = calendar_nodes
|
||||
else:
|
||||
logger.info(f"Creating calendar for {school_unique_id} from dataframe SchoolID: {school_unique_id}")
|
||||
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=False, owner_node=None)
|
||||
logger.info(f"Creating calendar for {school_uuid_string} from dataframe SchoolID: {school_uuid_string}")
|
||||
calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=False, owner_node=None, filesystem=filesystem)
|
||||
|
||||
# Create AcademicYear nodes for each year within the range
|
||||
for year in range(school_year_start_date.year, school_year_end_date.year + 1):
|
||||
year_str = str(year)
|
||||
academic_year_unique_id = f"{school_timetable_unique_id}_{year}"
|
||||
academic_year_uuid_string = f"{school_timetable_uuid_string}_{year}"
|
||||
|
||||
# Generate storage path for academic year node
|
||||
if filesystem:
|
||||
year_dir_created, year_path = filesystem.create_school_timetable_year_directory(timetable_path, year)
|
||||
node_storage_path = os.path.relpath(year_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
academic_year_node = timetable.AcademicYearNode(
|
||||
unique_id=academic_year_unique_id,
|
||||
uuid_string=academic_year_uuid_string,
|
||||
year=year_str,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(academic_year_node, database=db_name, operation='merge')
|
||||
timetable_nodes['academic_year_nodes'].append(academic_year_node)
|
||||
logger.info(f'Created academic year node: {academic_year_node.unique_id}')
|
||||
logger.info(f'Created academic year node: {academic_year_node.uuid_string}')
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
tt_rels.AcademicTimetableHasAcademicYear(source=school_timetable_node, target=academic_year_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {school_timetable_node.unique_id} to {academic_year_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {school_timetable_node.uuid_string} to {academic_year_node.uuid_string}")
|
||||
|
||||
# Link the academic year with the corresponding calendar year node
|
||||
for year_node in calendar_nodes['calendar_year_nodes']:
|
||||
@ -103,7 +121,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
cal_tt_rels.AcademicYearIsCalendarYear(source=academic_year_node, target=year_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.unique_id} to {year_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {year_node.uuid_string}")
|
||||
break
|
||||
|
||||
# Create Term and TermBreak nodes linked to AcademicYear
|
||||
@ -121,29 +139,39 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
if isinstance(term_end_date, pd.Timestamp):
|
||||
term_end_date = term_end_date.strftime('%Y-%m-%d')
|
||||
|
||||
# Generate storage path for term node
|
||||
if filesystem:
|
||||
if term_row['TermType'] == 'Term':
|
||||
term_dir_created, term_path = filesystem.create_school_timetable_academic_term_directory(timetable_path, term_name, academic_term_number)
|
||||
else:
|
||||
term_dir_created, term_path = filesystem.create_school_timetable_academic_term_break_directory(timetable_path, term_name)
|
||||
node_storage_path = os.path.relpath(term_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
if term_row['TermType'] == 'Term':
|
||||
term_node_unique_id = f"{school_timetable_unique_id}_{academic_term_number}_{term_name_no_spaces}"
|
||||
term_node_uuid_string = f"{school_timetable_uuid_string}_{academic_term_number}_{term_name_no_spaces}"
|
||||
academic_term_number_str = str(academic_term_number)
|
||||
term_node = term_node_class(
|
||||
unique_id=term_node_unique_id,
|
||||
uuid_string=term_node_uuid_string,
|
||||
term_name=term_name,
|
||||
term_number=academic_term_number_str,
|
||||
start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
|
||||
end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
academic_term_number += 1
|
||||
else:
|
||||
term_break_node_unique_id = f"{school_timetable_unique_id}_{term_name_no_spaces}"
|
||||
term_break_node_uuid_string = f"{school_timetable_uuid_string}_{term_name_no_spaces}"
|
||||
term_node = term_node_class(
|
||||
unique_id=term_break_node_unique_id,
|
||||
uuid_string=term_break_node_uuid_string,
|
||||
term_break_name=term_name,
|
||||
start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
|
||||
end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(term_node, database=db_name, operation='merge')
|
||||
logger.info(f'Created academic term break node: {term_node.unique_id}')
|
||||
logger.info(f'Created academic term break node: {term_node.uuid_string}')
|
||||
timetable_nodes['academic_term_nodes'].append(term_node)
|
||||
term_number += 1 # We don't use this but we could
|
||||
|
||||
@ -158,7 +186,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=academic_year_node, target=term_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.unique_id} to {term_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {term_node.uuid_string}")
|
||||
|
||||
# Create Week nodes
|
||||
academic_week_number = 1
|
||||
@ -168,28 +196,35 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
if isinstance(week_start_date, pd.Timestamp):
|
||||
week_start_date = week_start_date.strftime('%Y-%m-%d')
|
||||
|
||||
week_node_unique_id = f"{school_timetable_unique_id}_{week_row['WeekNumber']}_{week_row['WeekType']}Week"
|
||||
week_node_uuid_string = f"{school_timetable_uuid_string}_{week_row['WeekNumber']}_{week_row['WeekType']}Week"
|
||||
|
||||
# Generate storage path for week node
|
||||
if filesystem:
|
||||
week_dir_created, week_path = filesystem.create_school_timetable_academic_week_directory(timetable_path, week_row['WeekNumber'])
|
||||
node_storage_path = os.path.relpath(week_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
if week_row['WeekType'] == 'Holiday':
|
||||
week_node = week_node_class(
|
||||
unique_id=week_node_unique_id,
|
||||
uuid_string=week_node_uuid_string,
|
||||
start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
else:
|
||||
academic_week_number_str = str(academic_week_number)
|
||||
week_type = week_row['WeekType']
|
||||
week_node = week_node_class(
|
||||
unique_id=week_node_unique_id,
|
||||
uuid_string=week_node_uuid_string,
|
||||
academic_week_number=academic_week_number_str,
|
||||
start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
|
||||
week_type=week_type,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
academic_week_number += 1
|
||||
neon.create_or_merge_neontology_node(week_node, database=db_name, operation='merge')
|
||||
timetable_nodes['academic_week_nodes'].append(week_node)
|
||||
logger.info(f"Created week node: {week_node.unique_id}")
|
||||
logger.info(f"Created week node: {week_node.uuid_string}")
|
||||
for calendar_node in calendar_nodes['calendar_week_nodes']:
|
||||
if calendar_node.start_date == week_node.start_date:
|
||||
if isinstance(week_node, timetable.AcademicWeekNode):
|
||||
@ -197,13 +232,13 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
cal_tt_rels.AcademicWeekIsCalendarWeek(source=week_node, target=calendar_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {calendar_node.unique_id} to {week_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {calendar_node.uuid_string} to {week_node.uuid_string}")
|
||||
elif isinstance(week_node, timetable.HolidayWeekNode):
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
cal_tt_rels.HolidayWeekIsCalendarWeek(source=week_node, target=calendar_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {calendar_node.unique_id} to {week_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {calendar_node.uuid_string} to {week_node.uuid_string}")
|
||||
break
|
||||
|
||||
# Link week node to the correct academic term
|
||||
@ -214,7 +249,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=term_node, target=week_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {term_node.unique_id} to {week_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {term_node.uuid_string} to {week_node.uuid_string}")
|
||||
break
|
||||
|
||||
# Link week node to the correct academic year
|
||||
@ -225,7 +260,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=academic_year_node, target=week_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.unique_id} to {week_node.unique_id}")
|
||||
logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {week_node.uuid_string}")
|
||||
break
|
||||
|
||||
# Create Day nodes
|
||||
@ -243,18 +278,24 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
'StaffDay': timetable.StaffDayNode
|
||||
}[day_row['DayType']]
|
||||
|
||||
# Generate storage path for day node
|
||||
if filesystem:
|
||||
day_dir_created, day_path = filesystem.create_school_timetable_academic_day_directory(timetable_path, academic_day_number)
|
||||
node_storage_path = os.path.relpath(day_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
# Format the unique ID as {day_node_class.__name__}Day
|
||||
day_node_data = {
|
||||
'unique_id': f"{school_timetable_unique_id}_{day_number}_{day_node_class.__name__}Day",
|
||||
'uuid_string': f"{school_timetable_uuid_string}_{day_number}_{day_node_class.__name__}Day",
|
||||
'date': datetime.strptime(date_str, '%Y-%m-%d'),
|
||||
'day_of_week': datetime.strptime(date_str, '%Y-%m-%d').strftime('%A'),
|
||||
'tldraw_snapshot': ""
|
||||
'node_storage_path': node_storage_path
|
||||
}
|
||||
|
||||
if day_row['DayType'] == 'Academic':
|
||||
day_node_data['academic_day'] = str(academic_day_number)
|
||||
day_node_data['day_type'] = day_row['WeekType']
|
||||
day_node_data['tldraw_snapshot'] = ""
|
||||
|
||||
day_node = day_node_class(**day_node_data)
|
||||
|
||||
@ -262,7 +303,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
if calendar_node.date == day_node.date:
|
||||
neon.create_or_merge_neontology_node(day_node, database=db_name, operation='merge')
|
||||
timetable_nodes['academic_day_nodes'].append(day_node)
|
||||
logger.info(f"Created day node: {day_node.unique_id}")
|
||||
logger.info(f"Created day node: {day_node.uuid_string}")
|
||||
|
||||
if isinstance(day_node, timetable.AcademicDayNode):
|
||||
relationship_class = cal_tt_rels.AcademicDayIsCalendarDay
|
||||
@ -277,7 +318,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=day_node, target=calendar_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f'Created relationship from {calendar_node.unique_id} to {day_node.unique_id}')
|
||||
logger.info(f'Created relationship from {calendar_node.uuid_string} to {day_node.uuid_string}')
|
||||
break
|
||||
|
||||
# Link day node to the correct academic week
|
||||
@ -300,7 +341,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=academic_week_node, target=day_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {academic_week_node.unique_id} to {day_node.unique_id}")
|
||||
logger.info(f"Created relationship from {academic_week_node.uuid_string} to {day_node.uuid_string}")
|
||||
break
|
||||
|
||||
# Link day node to the correct academic term
|
||||
@ -323,12 +364,12 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=term_node, target=day_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {term_node.unique_id} to {day_node.unique_id}")
|
||||
logger.info(f"Created relationship from {term_node.uuid_string} to {day_node.uuid_string}")
|
||||
break
|
||||
|
||||
# Create Period nodes for each academic day
|
||||
if day_row['DayType'] == 'Academic':
|
||||
logger.info(f"Creating periods for {day_node.unique_id}")
|
||||
logger.info(f"Creating periods for {day_node.uuid_string}")
|
||||
period_of_day = 1
|
||||
academic_or_registration_period_of_day = 1
|
||||
for _, period_row in periods_df.iterrows():
|
||||
@ -340,15 +381,22 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
}[period_row['PeriodType']]
|
||||
|
||||
logger.info(f"Creating period node for {period_node_class.__name__} Period: {period_of_day}")
|
||||
period_node_unique_id = f"{school_timetable_unique_id}_{academic_day_number}_{period_of_day}_{period_node_class.__name__}Period"
|
||||
logger.debug(f"Period node unique id: {period_node_unique_id}")
|
||||
period_node_uuid_string = f"{school_timetable_uuid_string}_{academic_day_number}_{period_of_day}_{period_node_class.__name__}Period"
|
||||
logger.debug(f"Period node unique id: {period_node_uuid_string}")
|
||||
# Generate storage path for period node
|
||||
if filesystem:
|
||||
period_dir_created, period_path = filesystem.create_school_timetable_period_directory(timetable_path, academic_day_number, period_row['PeriodCode'])
|
||||
node_storage_path = os.path.relpath(period_path, filesystem.base_path)
|
||||
else:
|
||||
node_storage_path = ""
|
||||
|
||||
period_node_data = {
|
||||
'unique_id': period_node_unique_id,
|
||||
'uuid_string': period_node_uuid_string,
|
||||
'name': period_row['PeriodName'],
|
||||
'date': day_node.date,
|
||||
'start_time': datetime.combine(day_node.date, period_row['StartTime']),
|
||||
'end_time': datetime.combine(day_node.date, period_row['EndTime']),
|
||||
'tldraw_snapshot': ""
|
||||
'node_storage_path': node_storage_path
|
||||
}
|
||||
logger.debug(f"Period node data: {period_node_data}")
|
||||
if period_row['PeriodType'] in ['Academic', 'Registration']:
|
||||
@ -357,14 +405,13 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
period_code = period_row['PeriodCode']
|
||||
period_code_formatted = f"{week_type}{day_name_short}{period_code}"
|
||||
period_node_data['period_code'] = period_code_formatted
|
||||
period_node_data['tldraw_snapshot'] = ""
|
||||
|
||||
academic_or_registration_period_of_day += 1
|
||||
|
||||
period_node = period_node_class(**period_node_data)
|
||||
neon.create_or_merge_neontology_node(period_node, database=db_name, operation='merge')
|
||||
timetable_nodes['academic_period_nodes'].append(period_node)
|
||||
logger.info(f'Created period node: {period_node.unique_id}')
|
||||
logger.info(f'Created period node: {period_node.uuid_string}')
|
||||
|
||||
relationship_class = {
|
||||
'Academic': tt_rels.AcademicDayHasAcademicPeriod,
|
||||
@ -377,7 +424,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class(source=day_node, target=period_node),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {day_node.unique_id} to {period_node.unique_id}")
|
||||
logger.info(f"Created relationship from {day_node.uuid_string} to {period_node.uuid_string}")
|
||||
period_of_day += 1 # We don't use this but we could
|
||||
academic_day_number += 1 # This is a bit of a hack but it works to keep the directories aligned (reorganise)
|
||||
day_number += 1 # We don't use this but we could
|
||||
@ -392,7 +439,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
relationship_class = relationship_map.get(node_type_pair)
|
||||
if relationship_class:
|
||||
# Avoid self-referential relationships
|
||||
if source_node.unique_id != target_node.unique_id:
|
||||
if source_node.uuid_string != target_node.uuid_string:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
relationship_class(
|
||||
source=source_node,
|
||||
@ -400,9 +447,9 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
),
|
||||
database=db_name, operation='merge'
|
||||
)
|
||||
logger.info(f"Created relationship from {source_node.unique_id} to {target_node.unique_id}")
|
||||
logger.info(f"Created relationship from {source_node.uuid_string} to {target_node.uuid_string}")
|
||||
else:
|
||||
logger.warning(f"Skipped self-referential relationship for node {source_node.unique_id}")
|
||||
logger.warning(f"Skipped self-referential relationship for node {source_node.uuid_string}")
|
||||
|
||||
# Relationship maps for different node types
|
||||
academic_year_relationship_map = {
|
||||
@ -474,7 +521,7 @@ def create_school_timetable(dataframes, db_name, school_node=None):
|
||||
# Call the function with the created timetable nodes
|
||||
create_school_timetable_node_sequence_rels(timetable_nodes)
|
||||
|
||||
logger.info(f'Created timetable: {timetable_nodes["timetable_node"].unique_id}')
|
||||
logger.info(f'Created timetable: {timetable_nodes["timetable_node"].uuid_string}')
|
||||
|
||||
# Log the directory structure after creation
|
||||
# root_timetable_directory = fs_handler.root_path # Access the root directory of the filesystem handler
|
||||
|
||||
@ -9,6 +9,7 @@ import modules.database.schemas.nodes.workers.workers as worker_nodes
|
||||
import modules.database.init.init_calendar as init_calendar
|
||||
import modules.database.schemas.relationships.entity_relationships as entity_relationships
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
import modules.database.tools.supabase_storage_tools as storage_tools
|
||||
from modules.logger_tool import initialise_logger
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
@ -21,11 +22,27 @@ def create_and_check_db(db_name):
|
||||
return database_status
|
||||
|
||||
class UserCreator(ABC):
|
||||
def __init__(self, user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date):
|
||||
cc_schools_db_name = "cc.institutes" # Fix the TODO
|
||||
def __init__(
|
||||
self,
|
||||
user_id,
|
||||
cc_users_db_name,
|
||||
user_type,
|
||||
worker_type,
|
||||
user_email,
|
||||
worker_email,
|
||||
cc_username,
|
||||
user_name,
|
||||
worker_name,
|
||||
calendar_start_date,
|
||||
calendar_end_date,
|
||||
storage_tools=None,
|
||||
user_db_name: Optional[str] = None,
|
||||
worker_db_name: Optional[str] = None,
|
||||
cc_schools_db_name: str = "cc.institutes",
|
||||
):
|
||||
self.cc_users_db_name = cc_users_db_name
|
||||
self.user_db_name = f"{cc_users_db_name}.{user_type}.{cc_username}"
|
||||
self.worker_db_name = f"{cc_schools_db_name}.{user_type}.{cc_username}"
|
||||
self.user_db_name = user_db_name or f"{cc_users_db_name}.{user_type}.{cc_username}"
|
||||
self.worker_db_name = worker_db_name or f"{cc_schools_db_name}.{worker_type}.{cc_username}"
|
||||
self.user_type = user_type
|
||||
self.worker_type = worker_type
|
||||
self.cc_username = cc_username
|
||||
@ -34,6 +51,7 @@ class UserCreator(ABC):
|
||||
self.user_name = user_name
|
||||
self.worker_name = worker_name
|
||||
self.user_id = user_id
|
||||
self.storage_tools = storage_tools # Store the storage tools instance
|
||||
self.user_nodes: Dict[str, Optional[Any]] = {
|
||||
'default_user_node': None,
|
||||
'private_user_node': None,
|
||||
@ -48,6 +66,25 @@ class UserCreator(ABC):
|
||||
self.calendar_start_date = datetime.now().date()
|
||||
self.calendar_end_date = (datetime.now() + timedelta(days=5)).date()
|
||||
|
||||
def _derive_user_storage_path(self) -> str:
|
||||
return os.path.join(
|
||||
"users",
|
||||
self.user_id,
|
||||
"databases",
|
||||
self.user_db_name,
|
||||
self.user_id,
|
||||
).replace('\\', '/')
|
||||
|
||||
def _derive_internal_worker_path(self, worker_kind: str) -> str:
|
||||
return os.path.join(
|
||||
"users",
|
||||
self.user_id,
|
||||
"databases",
|
||||
self.user_db_name,
|
||||
worker_kind,
|
||||
self.user_id,
|
||||
).replace('\\', '/')
|
||||
|
||||
@abstractmethod
|
||||
def create_user(self):
|
||||
pass
|
||||
@ -66,9 +103,17 @@ class UserCreator(ABC):
|
||||
# Ensure Neontology is initialized
|
||||
neon.init_neontology_connection()
|
||||
|
||||
# Generate storage path for user node using Supabase Storage
|
||||
if self.storage_tools:
|
||||
user_dir_created, node_storage_path = self.storage_tools.create_user_storage_path(self.user_id)
|
||||
self.user_path = node_storage_path # Store for later use
|
||||
else:
|
||||
node_storage_path = self._derive_user_storage_path()
|
||||
self.user_path = None
|
||||
|
||||
user_node = user_nodes.UserNode(
|
||||
unique_id=f"{self.user_id}",
|
||||
tldraw_snapshot="",
|
||||
uuid_string=f"{self.user_id}",
|
||||
node_storage_path=node_storage_path,
|
||||
cc_username=f"{self.cc_username}",
|
||||
user_email=f"{self.user_email}",
|
||||
user_name=f"{self.user_name}",
|
||||
@ -76,81 +121,79 @@ class UserCreator(ABC):
|
||||
user_type=f"{self.user_type}",
|
||||
)
|
||||
logger.debug(f"User node template created: {user_node.to_dict()}. Writing to database {db_name}")
|
||||
neon.create_or_merge_neontology_node(node=user_node, database=db_name, operation='merge')
|
||||
logger.info(f"User node created: {user_node.to_dict()}")
|
||||
logger.debug(f"About to call create_or_merge_neontology_node with node class: {user_node.__class__.__name__}")
|
||||
logger.debug(f"Node primary label: {user_node.__primarylabel__}")
|
||||
logger.debug(f"Node primary property: {user_node.__primaryproperty__}")
|
||||
|
||||
try:
|
||||
neon.create_or_merge_neontology_node(node=user_node, database=db_name, operation='merge')
|
||||
logger.info(f"User node created successfully: {user_node.to_dict()}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create user node: {e}")
|
||||
raise
|
||||
return user_node
|
||||
|
||||
def create_storage_bucket(self, bucket_id: str, bucket_name: str, access_token: Optional[str] = None) -> bool:
|
||||
"""Create public and private storage buckets for the user using their access token or service role during initialization"""
|
||||
logger.info(f"Creating storage buckets for user {self.cc_username}")
|
||||
|
||||
try:
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient, SupabaseAnonClient, CreateBucketOptions
|
||||
|
||||
# During initialization (no access token provided), use service role
|
||||
if not access_token:
|
||||
logger.info("Using service role client for bucket creation during initialization")
|
||||
supabase = SupabaseServiceRoleClient()
|
||||
else:
|
||||
# For regular operations, use the user's access token
|
||||
logger.info("Using user token for bucket creation")
|
||||
supabase = SupabaseAnonClient.for_user(access_token)
|
||||
|
||||
# Create both public and private buckets
|
||||
buckets = [
|
||||
{
|
||||
"id": f"{bucket_id}.public",
|
||||
"options": CreateBucketOptions(
|
||||
name=f"{bucket_name} - Public Files",
|
||||
public=True,
|
||||
file_size_limit=50 * 1024 * 1024, # 50MB
|
||||
allowed_mime_types=[
|
||||
'image/*', 'video/*', 'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain', 'text/csv', 'application/json'
|
||||
]
|
||||
)
|
||||
},
|
||||
{
|
||||
"id": f"{bucket_id}.private",
|
||||
"options": CreateBucketOptions(
|
||||
name=f"{bucket_name} - Private Files",
|
||||
public=False,
|
||||
file_size_limit=50 * 1024 * 1024, # 50MB
|
||||
allowed_mime_types=[
|
||||
'image/*', 'video/*', 'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain', 'text/csv', 'application/json'
|
||||
]
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
success = True
|
||||
for bucket in buckets:
|
||||
try:
|
||||
result = supabase.create_bucket(bucket["id"], bucket["options"])
|
||||
if not result:
|
||||
logger.error(f"Failed to create bucket {bucket['id']}")
|
||||
success = False
|
||||
else:
|
||||
logger.info(f"Successfully created bucket {bucket['id']}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating bucket {bucket['id']}: {str(e)}")
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating storage buckets: {str(e)}")
|
||||
return False
|
||||
"""Create storage buckets for the user - DEPRECATED: Use centralized bucket initialization instead"""
|
||||
logger.warning(f"Individual user bucket creation is deprecated. Use centralized bucket initialization instead.")
|
||||
logger.info(f"User {self.cc_username} will use centralized storage buckets.")
|
||||
return True # Return success to avoid breaking existing code
|
||||
|
||||
class SchoolUserCreator(UserCreator):
|
||||
def __init__(self, user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date, school_node, worker_node=None):
|
||||
super().__init__(user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date)
|
||||
def __init__(
|
||||
self,
|
||||
user_id,
|
||||
cc_users_db_name,
|
||||
user_type,
|
||||
worker_type,
|
||||
user_email,
|
||||
worker_email,
|
||||
cc_username,
|
||||
user_name,
|
||||
worker_name,
|
||||
calendar_start_date,
|
||||
calendar_end_date,
|
||||
school_node,
|
||||
worker_node=None,
|
||||
storage_tools=None,
|
||||
user_db_name: Optional[str] = None,
|
||||
worker_db_name: Optional[str] = None,
|
||||
):
|
||||
super().__init__(
|
||||
user_id,
|
||||
cc_users_db_name,
|
||||
user_type,
|
||||
worker_type,
|
||||
user_email,
|
||||
worker_email,
|
||||
cc_username,
|
||||
user_name,
|
||||
worker_name,
|
||||
calendar_start_date,
|
||||
calendar_end_date,
|
||||
storage_tools,
|
||||
user_db_name=user_db_name,
|
||||
worker_db_name=worker_db_name or (school_node.private_database_name if hasattr(school_node, "private_database_name") else None),
|
||||
cc_schools_db_name="cc.institutes",
|
||||
)
|
||||
self.school_node = school_node
|
||||
self.worker_node = worker_node
|
||||
|
||||
|
||||
def _derive_school_worker_path(self, worker_kind: str) -> str:
|
||||
school_identifier = getattr(self.school_node, 'uuid_string', None) if self.school_node else None
|
||||
if not school_identifier and self.worker_db_name:
|
||||
school_identifier = self.worker_db_name.split('.')[-1]
|
||||
school_identifier = school_identifier or self.user_id
|
||||
db_name_segment = self.worker_db_name or f"cc.institutes.{school_identifier}"
|
||||
return os.path.join(
|
||||
"schools",
|
||||
school_identifier,
|
||||
"databases",
|
||||
db_name_segment,
|
||||
worker_kind,
|
||||
self.user_id,
|
||||
).replace('\\', '/')
|
||||
|
||||
def create_user(self):
|
||||
# Ensure Neontology is initialized
|
||||
logger.debug(f"Initializing Neontology connection. Closing any existing connection")
|
||||
@ -167,7 +210,7 @@ class SchoolUserCreator(UserCreator):
|
||||
|
||||
self.user_nodes[f'worker_node'] = worker_node
|
||||
|
||||
user_node = self.create_user_node(self.cc_users_db_name)
|
||||
user_node = self.create_user_node(self.user_db_name)
|
||||
|
||||
logger.info(f"User node created: {user_node}")
|
||||
|
||||
@ -190,16 +233,25 @@ class SchoolUserCreator(UserCreator):
|
||||
raise ValueError(f"Error creating teacher node: {e}") from e
|
||||
|
||||
def _create_teacher_node(self):
|
||||
# Generate storage path for teacher node using Supabase Storage
|
||||
if self.storage_tools:
|
||||
teacher_dir_created, node_storage_path = self.storage_tools.create_teacher_storage_path(self.user_id)
|
||||
else:
|
||||
node_storage_path = self._derive_school_worker_path(self.worker_type)
|
||||
|
||||
teacher_node = worker_nodes.TeacherNode(
|
||||
unique_id=f"{self.user_id}",
|
||||
tldraw_snapshot="",
|
||||
uuid_string=f"{self.user_id}",
|
||||
node_storage_path=node_storage_path,
|
||||
worker_name=self.worker_name,
|
||||
worker_email=self.worker_email,
|
||||
worker_db_name=self.worker_db_name,
|
||||
worker_type=self.worker_type
|
||||
)
|
||||
# Use the school's private database name if available
|
||||
school_db = self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name') else f"cc.institutes.{self.school_node.school_type}.{self.school_node.id}"
|
||||
school_db = self.worker_db_name or (
|
||||
self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name')
|
||||
else f"cc.institutes.{self.school_node.school_type}.{getattr(self.school_node, 'id', self.school_node.uuid_string)}"
|
||||
)
|
||||
logger.info(f"Teacher node template created: {teacher_node}... setting school db to {school_db}")
|
||||
|
||||
neon.create_or_merge_neontology_node(node=teacher_node, database=school_db, operation='merge')
|
||||
@ -208,16 +260,25 @@ class SchoolUserCreator(UserCreator):
|
||||
return teacher_node
|
||||
|
||||
def create_student_node(self):
|
||||
# Generate storage path for student node using Supabase Storage
|
||||
if self.storage_tools:
|
||||
student_dir_created, node_storage_path = self.storage_tools.create_student_storage_path(f"Student_{self.user_id}")
|
||||
else:
|
||||
node_storage_path = self._derive_school_worker_path(self.worker_type)
|
||||
|
||||
student_node = worker_nodes.StudentNode(
|
||||
unique_id=f"Student_{self.user_id}",
|
||||
uuid_string=f"Student_{self.user_id}",
|
||||
worker_name=self.worker_name,
|
||||
worker_email=self.worker_email,
|
||||
worker_db_name=self.worker_db_name,
|
||||
worker_type=self.worker_type,
|
||||
tldraw_snapshot=""
|
||||
node_storage_path=node_storage_path
|
||||
)
|
||||
# Use the school's private database name if available
|
||||
school_db = self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name') else f"cc.institutes.{self.school_node.school_type}.{self.school_node.id}"
|
||||
school_db = self.worker_db_name or (
|
||||
self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name')
|
||||
else f"cc.institutes.{self.school_node.school_type}.{getattr(self.school_node, 'id', self.school_node.uuid_string)}"
|
||||
)
|
||||
logger.info(f"Student node template created: {student_node}... setting school db to {school_db}")
|
||||
|
||||
neon.create_or_merge_neontology_node(node=student_node, database=school_db, operation='merge')
|
||||
@ -228,20 +289,58 @@ class SchoolUserCreator(UserCreator):
|
||||
def create_user_worker_relationship(self, user_node, worker_node):
|
||||
user_role_rel = entity_relationships.UserIsSchoolWorker(source=user_node, target=worker_node)
|
||||
# Use the school's private database name if available
|
||||
school_db = self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name') else f"cc.institutes.{self.school_node.school_type}.{self.school_node.id}"
|
||||
school_db = self.worker_db_name or (
|
||||
self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name')
|
||||
else f"cc.institutes.{self.school_node.school_type}.{getattr(self.school_node, 'id', self.school_node.uuid_string)}"
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(user_role_rel, database=school_db, operation='merge')
|
||||
logger.info(f"Relationship created between user and worker in database {school_db}")
|
||||
|
||||
def create_worker_school_relationship(self, worker_node, school_node):
|
||||
worker_school_rel = entity_relationships.EntityBelongsToSchool(source=worker_node, target=school_node)
|
||||
# Use the school's private database name if available
|
||||
school_db = school_node.private_database_name if hasattr(school_node, 'private_database_name') else f"cc.institutes.{school_node.school_type}.{school_node.id}"
|
||||
school_db = self.worker_db_name or (
|
||||
school_node.private_database_name if hasattr(school_node, 'private_database_name')
|
||||
else f"cc.institutes.{school_node.school_type}.{getattr(school_node, 'id', school_node.uuid_string)}"
|
||||
)
|
||||
neon.create_or_merge_neontology_relationship(worker_school_rel, database=school_db, operation='merge')
|
||||
logger.info(f"Relationship created between worker and school in database {school_db}")
|
||||
|
||||
class NonSchoolUserCreator(UserCreator):
|
||||
def __init__(self, user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date, developer_role: str = "developer"):
|
||||
super().__init__(user_id, cc_users_db_name, user_type, worker_type, user_email, worker_email, cc_username, user_name, worker_name, calendar_start_date, calendar_end_date)
|
||||
def __init__(
|
||||
self,
|
||||
user_id,
|
||||
cc_users_db_name,
|
||||
user_type,
|
||||
worker_type,
|
||||
user_email,
|
||||
worker_email,
|
||||
cc_username,
|
||||
user_name,
|
||||
worker_name,
|
||||
calendar_start_date,
|
||||
calendar_end_date,
|
||||
developer_role: str = "developer",
|
||||
storage_tools=None,
|
||||
user_db_name: Optional[str] = None,
|
||||
worker_db_name: Optional[str] = None,
|
||||
):
|
||||
super().__init__(
|
||||
user_id,
|
||||
cc_users_db_name,
|
||||
user_type,
|
||||
worker_type,
|
||||
user_email,
|
||||
worker_email,
|
||||
cc_username,
|
||||
user_name,
|
||||
worker_name,
|
||||
calendar_start_date,
|
||||
calendar_end_date,
|
||||
storage_tools,
|
||||
user_db_name=user_db_name,
|
||||
worker_db_name=worker_db_name,
|
||||
)
|
||||
self.developer_role = developer_role
|
||||
|
||||
def create_user(self, access_token: Optional[str] = None):
|
||||
@ -273,7 +372,8 @@ class NonSchoolUserCreator(UserCreator):
|
||||
logger.debug(f"Creating developer db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
||||
self.create_developer_db()
|
||||
else:
|
||||
raise ValueError(f"User type {self.user_type} not supported")
|
||||
logger.warning(f"User type {self.user_type} not explicitly supported; defaulting to developer workspace")
|
||||
self.create_developer_db()
|
||||
|
||||
logger.debug(f"User nodes after creation: {self.user_nodes}")
|
||||
return self.user_nodes
|
||||
@ -290,10 +390,16 @@ class NonSchoolUserCreator(UserCreator):
|
||||
logger.debug(f"Creating super admin user node for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
|
||||
private_user_node = self.create_user_node(self.user_db_name)
|
||||
|
||||
# Generate storage path for super admin node using Supabase Storage
|
||||
if self.storage_tools:
|
||||
admin_dir_created, node_storage_path = self.storage_tools.create_super_admin_storage_path(self.user_id)
|
||||
else:
|
||||
node_storage_path = self._derive_internal_worker_path(self.worker_type)
|
||||
|
||||
super_admin_node = worker_nodes.SuperAdminNode(
|
||||
unique_id=f"SuperAdmin_{self.user_id}",
|
||||
uuid_string=self.user_id,
|
||||
worker_email=self.worker_email,
|
||||
tldraw_snapshot="",
|
||||
node_storage_path=node_storage_path,
|
||||
worker_name=self.worker_name,
|
||||
worker_db_name=self.worker_db_name,
|
||||
worker_type=self.worker_type
|
||||
@ -329,11 +435,17 @@ class NonSchoolUserCreator(UserCreator):
|
||||
# Create the user node again for the user db
|
||||
private_user_node = self.create_user_node(self.user_db_name)
|
||||
|
||||
# Generate storage path for developer node using Supabase Storage
|
||||
if self.storage_tools:
|
||||
dev_dir_created, node_storage_path = self.storage_tools.create_developer_storage_path(self.user_id)
|
||||
else:
|
||||
node_storage_path = self._derive_internal_worker_path(self.worker_type)
|
||||
|
||||
developer_node = worker_nodes.DeveloperNode(
|
||||
unique_id=f"Developer_{self.user_id}",
|
||||
uuid_string=self.user_id,
|
||||
worker_name=self.worker_name,
|
||||
worker_email=self.worker_email,
|
||||
tldraw_snapshot="",
|
||||
node_storage_path=node_storage_path,
|
||||
worker_db_name=self.worker_db_name,
|
||||
worker_type=self.worker_type,
|
||||
developer_role=self.developer_role
|
||||
@ -372,8 +484,101 @@ class NonSchoolUserCreator(UserCreator):
|
||||
neon.create_or_merge_neontology_relationship(specific_user_rel, database=self.user_db_name, operation='merge')
|
||||
logger.info("Relationship created between user and specific node")
|
||||
|
||||
def create_calendar(self, user_node: user_nodes.UserNode):
|
||||
calendar_nodes = init_calendar.create_calendar(self.user_db_name, self.calendar_start_date, self.calendar_end_date, attach_to_calendar_node=True, owner_node=user_node)
|
||||
def create_calendar(self, user_node: user_nodes.UserNode):
|
||||
calendar_nodes = init_calendar.create_calendar(self.user_db_name, self.calendar_start_date, self.calendar_end_date, attach_to_calendar_node=True, owner_node=user_node, filesystem=self.filesystem)
|
||||
|
||||
logger.info(f"Calendar nodes created.")
|
||||
return calendar_nodes
|
||||
|
||||
|
||||
def _default_date_range():
|
||||
today = datetime.now().date()
|
||||
return today, (datetime.now() + timedelta(days=365)).date()
|
||||
|
||||
|
||||
def create_user(
|
||||
*,
|
||||
user_id: str,
|
||||
user_type: str,
|
||||
username: str,
|
||||
user_email: str,
|
||||
user_name: Optional[str] = None,
|
||||
worker_name: Optional[str] = None,
|
||||
worker_type: Optional[str] = None,
|
||||
worker_email: Optional[str] = None,
|
||||
cc_users_db_name: str = "cc.users",
|
||||
user_db_name: Optional[str] = None,
|
||||
worker_db_name: Optional[str] = None,
|
||||
calendar_start_date: Optional[datetime.date] = None,
|
||||
calendar_end_date: Optional[datetime.date] = None,
|
||||
school_node: Optional[Any] = None,
|
||||
storage_tools=None,
|
||||
) -> Dict[str, Optional[Any]]:
|
||||
"""Create a user graph structure in Neo4j.
|
||||
|
||||
Args:
|
||||
user_id: Identifier used as UUID for graph nodes.
|
||||
user_type: Application-level user type (e.g. email_teacher, developer).
|
||||
username: Canonical username/slug.
|
||||
user_email: Contact email for the user node.
|
||||
user_name: Friendly display name (defaults to username).
|
||||
worker_name: Friendly name for worker node (defaults to user_name).
|
||||
worker_type: Worker role (teacher, student, developer, etc.).
|
||||
worker_email: Email for worker node (defaults to user_email).
|
||||
cc_users_db_name: Root namespace for user databases (defaults to cc.users).
|
||||
user_db_name: Fully-qualified target database for the user graph.
|
||||
worker_db_name: Database for worker entities (usually school private DB).
|
||||
calendar_start_date/calendar_end_date: Date range for initial calendar seeding.
|
||||
school_node: Optional school context; if provided a SchoolUserCreator is used.
|
||||
storage_tools: Optional Supabase storage tools for generating storage paths.
|
||||
|
||||
Returns:
|
||||
Dict describing created nodes keyed by semantic role.
|
||||
"""
|
||||
|
||||
start_date, end_date = calendar_start_date, calendar_end_date
|
||||
if not start_date or not end_date:
|
||||
start_date, end_date = _default_date_range()
|
||||
|
||||
worker_email = worker_email or user_email
|
||||
user_name = user_name or username
|
||||
worker_name = worker_name or user_name
|
||||
|
||||
if school_node is not None:
|
||||
creator = SchoolUserCreator(
|
||||
user_id=user_id,
|
||||
cc_users_db_name=cc_users_db_name,
|
||||
user_type=user_type,
|
||||
worker_type=worker_type or "teacher",
|
||||
user_email=user_email,
|
||||
worker_email=worker_email,
|
||||
cc_username=username,
|
||||
user_name=user_name,
|
||||
worker_name=worker_name,
|
||||
calendar_start_date=start_date,
|
||||
calendar_end_date=end_date,
|
||||
school_node=school_node,
|
||||
worker_node=None,
|
||||
storage_tools=storage_tools,
|
||||
user_db_name=user_db_name,
|
||||
worker_db_name=worker_db_name,
|
||||
)
|
||||
else:
|
||||
creator = NonSchoolUserCreator(
|
||||
user_id=user_id,
|
||||
cc_users_db_name=cc_users_db_name,
|
||||
user_type=user_type,
|
||||
worker_type=worker_type or user_type,
|
||||
user_email=user_email,
|
||||
worker_email=worker_email,
|
||||
cc_username=username,
|
||||
user_name=user_name,
|
||||
worker_name=worker_name,
|
||||
calendar_start_date=start_date,
|
||||
calendar_end_date=end_date,
|
||||
storage_tools=storage_tools,
|
||||
user_db_name=user_db_name,
|
||||
worker_db_name=worker_db_name,
|
||||
)
|
||||
|
||||
return creator.create_user()
|
||||
|
||||
@ -7,10 +7,10 @@ import modules.database.tools.neontology_tools as neon
|
||||
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
||||
from modules.database.schemas.nodes.users import UserNode
|
||||
from modules.database.schemas.nodes.schools.schools import SubjectClassNode
|
||||
from modules.database.schemas.nodes.workers.workers import TeacherNode
|
||||
from modules.database.schemas.nodes.workers.workers import TeacherNode,
|
||||
from modules.database.schemas.nodes.calendars import CalendarDayNode
|
||||
from modules.database.schemas.nodes.workers.timetable import (
|
||||
UserTeacherTimetableNode
|
||||
UserTeacherTimetableNode, TimetableLessonNode
|
||||
)
|
||||
from modules.database.schemas.relationships.entity_timetable_rels import (
|
||||
EntityHasTimetable
|
||||
@ -23,35 +23,35 @@ from modules.database.schemas.relationships.calendar_timetable_rels import (
|
||||
CalendarDayHasPlannedLesson, PlannedLessonBelongsToCalendarDay
|
||||
)
|
||||
|
||||
def get_school_worker_classes(school_db_name: str, user_unique_id: str, worker_unique_id: str) -> list:
|
||||
def get_school_worker_classes(school_db_name: str, user_uuid_string: str, worker_uuid_string: str) -> list:
|
||||
"""
|
||||
Retrieve all classes for a worker from the school database.
|
||||
"""
|
||||
query = """
|
||||
MATCH (w:Teacher {unique_id: $worker_id})-[:TEACHER_HAS_TIMETABLE]->(tt:TeacherTimetable)
|
||||
MATCH (w:Teacher {uuid_string: $worker_id})-[:TEACHER_HAS_TIMETABLE]->(tt:TeacherTimetable)
|
||||
-[:TIMETABLE_HAS_CLASS]->(c:SubjectClass)
|
||||
RETURN c
|
||||
"""
|
||||
with driver.get_driver(db_name=school_db_name).session(database=school_db_name) as session:
|
||||
result = session.run(query, worker_id=worker_unique_id)
|
||||
result = session.run(query, worker_id=worker_uuid_string)
|
||||
classes = [record['c'] for record in result]
|
||||
if not classes:
|
||||
logger.warning(f"No classes found for teacher {worker_unique_id} in school database")
|
||||
logger.warning(f"No classes found for teacher {worker_uuid_string} in school database")
|
||||
return classes
|
||||
|
||||
def get_school_class_periods(school_db_name: str, class_unique_id: str) -> list:
|
||||
def get_school_class_periods(school_db_name: str, class_uuid_string: str) -> list:
|
||||
"""
|
||||
Retrieve all periods for a class from the school database.
|
||||
"""
|
||||
query = """
|
||||
MATCH (c:SubjectClass {unique_id: $class_id})-[:CLASS_HAS_LESSON]->(l:TimetableLesson)
|
||||
MATCH (c:SubjectClass {uuid_string: $class_id})-[:CLASS_HAS_LESSON]->(l:TimetableLesson)
|
||||
RETURN l
|
||||
"""
|
||||
with driver.get_driver(db_name=school_db_name).session(database=school_db_name) as session:
|
||||
result = session.run(query, class_id=class_unique_id)
|
||||
result = session.run(query, class_id=class_uuid_string)
|
||||
periods = [record['l'] for record in result]
|
||||
if not periods:
|
||||
logger.warning(f"No periods found for class {class_unique_id} in school database")
|
||||
logger.warning(f"No periods found for class {class_uuid_string} in school database")
|
||||
return periods
|
||||
|
||||
def get_user_calendar_nodes(user_db_name: str, user_node: UserNode) -> list:
|
||||
@ -60,12 +60,12 @@ def get_user_calendar_nodes(user_db_name: str, user_node: UserNode) -> list:
|
||||
"""
|
||||
# First try to find any calendar days to verify the structure
|
||||
verify_query = """
|
||||
MATCH (w:User {unique_id: $user_id})
|
||||
MATCH (w:User {uuid_string: $user_id})
|
||||
OPTIONAL MATCH (w)-[:HAS_CALENDAR]->(c:Calendar)
|
||||
OPTIONAL MATCH (c)-[:CALENDAR_INCLUDES_YEAR]->(y:CalendarYear)
|
||||
OPTIONAL MATCH (y)-[:YEAR_INCLUDES_MONTH]->(m:CalendarMonth)
|
||||
OPTIONAL MATCH (m)-[:MONTH_INCLUDES_DAY]->(d:CalendarDay)
|
||||
RETURN w.unique_id as user_id,
|
||||
RETURN w.uuid_string as user_id,
|
||||
count(c) as calendar_count,
|
||||
count(y) as year_count,
|
||||
count(m) as month_count,
|
||||
@ -76,7 +76,7 @@ def get_user_calendar_nodes(user_db_name: str, user_node: UserNode) -> list:
|
||||
|
||||
with driver.get_driver(db_name=user_db_name).session(database=user_db_name) as session:
|
||||
# First check the calendar structure
|
||||
result = session.run(verify_query, user_id=user_node.unique_id)
|
||||
result = session.run(verify_query, user_id=user_node.uuid_string)
|
||||
if stats := result.single():
|
||||
logger.info(f"Calendar structure for user {stats['user_id']}: "
|
||||
f"calendars={stats['calendar_count']}, "
|
||||
@ -86,50 +86,50 @@ def get_user_calendar_nodes(user_db_name: str, user_node: UserNode) -> list:
|
||||
f"available years={stats['years']}")
|
||||
|
||||
if stats['calendar_count'] == 0:
|
||||
logger.error(f"No calendar found for user {user_node.unique_id}")
|
||||
logger.error(f"No calendar found for user {user_node.uuid_string}")
|
||||
return []
|
||||
if stats['year_count'] == 0:
|
||||
logger.error(f"No calendar years found for user {user_node.unique_id}")
|
||||
logger.error(f"No calendar years found for user {user_node.uuid_string}")
|
||||
return []
|
||||
if stats['month_count'] == 0:
|
||||
logger.error(f"No calendar months found for user {user_node.unique_id}")
|
||||
logger.error(f"No calendar months found for user {user_node.uuid_string}")
|
||||
return []
|
||||
if stats['day_count'] == 0:
|
||||
logger.error(f"No calendar days found for user {user_node.unique_id}")
|
||||
logger.error(f"No calendar days found for user {user_node.uuid_string}")
|
||||
return []
|
||||
|
||||
# Get all calendar days without year filter
|
||||
query = """
|
||||
MATCH (w:User {unique_id: $user_id})-[:HAS_CALENDAR]->(c:Calendar)
|
||||
MATCH (w:User {uuid_string: $user_id})-[:HAS_CALENDAR]->(c:Calendar)
|
||||
-[:CALENDAR_INCLUDES_YEAR]->(y:CalendarYear)
|
||||
-[:YEAR_INCLUDES_MONTH]->(m:CalendarMonth)
|
||||
-[:MONTH_INCLUDES_DAY]->(d:CalendarDay)
|
||||
RETURN d.unique_id as unique_id,
|
||||
RETURN d.uuid_string as uuid_string,
|
||||
d.date as date,
|
||||
d.day_of_week as day_of_week,
|
||||
d.iso_day as iso_day,
|
||||
d.path as path
|
||||
d.node_storage_path as path
|
||||
ORDER BY d.date
|
||||
"""
|
||||
|
||||
result = session.run(query, user_id=user_node.unique_id)
|
||||
result = session.run(query, user_id=user_node.uuid_string)
|
||||
calendar_days = []
|
||||
for record in result:
|
||||
calendar_day = CalendarDayNode(
|
||||
unique_id=record['unique_id'],
|
||||
uuid_string=record['uuid_string'],
|
||||
date=record['date'],
|
||||
day_of_week=record['day_of_week'],
|
||||
iso_day=record['iso_day'],
|
||||
path=record['path']
|
||||
node_storage_path=record['path']
|
||||
)
|
||||
calendar_days.append(calendar_day)
|
||||
|
||||
if not calendar_days:
|
||||
logger.error(f"No calendar days found for user {user_node.unique_id}")
|
||||
logger.error(f"No calendar days found for user {user_node.uuid_string}")
|
||||
else:
|
||||
# Log the date range we have
|
||||
dates = sorted([day.date for day in calendar_days])
|
||||
logger.info(f"Found {len(calendar_days)} calendar days for user {user_node.unique_id}")
|
||||
logger.info(f"Found {len(calendar_days)} calendar days for user {user_node.uuid_string}")
|
||||
logger.info(f"Calendar days range from {dates[0]} to {dates[-1]}")
|
||||
|
||||
return calendar_days
|
||||
@ -149,7 +149,7 @@ def create_user_worker_timetable(
|
||||
fs_handler = ClassroomCopilotFilesystem(db_name=user_db_name, init_run_type="user")
|
||||
|
||||
# Create teacher timetable directory under the worker's directory
|
||||
_, worker_timetable_path = fs_handler.create_teacher_timetable_directory(user_worker_node.path)
|
||||
_, worker_timetable_path = fs_handler.create_teacher_timetable_directory(user_worker_node.node_storage_path)
|
||||
|
||||
# Initialize neontology connection
|
||||
neon.init_neontology_connection()
|
||||
@ -157,7 +157,7 @@ def create_user_worker_timetable(
|
||||
# Get user's calendar nodes
|
||||
calendar_nodes = get_user_calendar_nodes(user_db_name, user_node)
|
||||
if not calendar_nodes:
|
||||
logger.warning(f"No calendar nodes found for user {user_node.unique_id}")
|
||||
logger.warning(f"No calendar nodes found for user {user_node.uuid_string}")
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "No calendar nodes found for user"
|
||||
@ -165,17 +165,17 @@ def create_user_worker_timetable(
|
||||
|
||||
try:
|
||||
# Create UserTeacherTimetableNode
|
||||
timetable_unique_id = f"UserTeacherTimetable_{user_worker_node.teacher_code}"
|
||||
timetable_uuid_string = f"UserTeacherTimetable_{user_worker_node.teacher_code}"
|
||||
worker_timetable = UserTeacherTimetableNode(
|
||||
unique_id=timetable_unique_id,
|
||||
uuid_string=timetable_uuid_string,
|
||||
school_db_name=school_db_name,
|
||||
school_timetable_id=f"TeacherTimetable_{user_worker_node.teacher_code}",
|
||||
path=worker_timetable_path
|
||||
node_storage_path=worker_timetable_path
|
||||
)
|
||||
|
||||
# Create the timetable node and its tldraw file
|
||||
neon.create_or_merge_neontology_node(worker_timetable, database=user_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(worker_timetable.path, worker_timetable.to_dict())
|
||||
fs_handler.create_default_tldraw_file(worker_timetable.node_storage_path, worker_timetable.to_dict())
|
||||
|
||||
# Link timetable to teacher using the correct relationship structure
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -185,9 +185,9 @@ def create_user_worker_timetable(
|
||||
)
|
||||
|
||||
# Get classes from school database
|
||||
school_classes = get_school_worker_classes(school_db_name, user_node.unique_id, user_worker_node.unique_id)
|
||||
school_classes = get_school_worker_classes(school_db_name, user_node.uuid_string, user_worker_node.uuid_string)
|
||||
if not school_classes:
|
||||
logger.warning(f"No classes found for teacher {user_worker_node.unique_id} in school database")
|
||||
logger.warning(f"No classes found for teacher {user_worker_node.uuid_string} in school database")
|
||||
return {
|
||||
"status": "warning",
|
||||
"message": "No classes found in school database"
|
||||
@ -202,15 +202,15 @@ def create_user_worker_timetable(
|
||||
|
||||
# Create SubjectClassNode
|
||||
subject_class_node = SubjectClassNode(
|
||||
unique_id=class_data['unique_id'],
|
||||
uuid_string=class_data['uuid_string'],
|
||||
subject_class_code=class_data['subject_class_code'],
|
||||
year_group=class_data['year_group'],
|
||||
subject=class_data['subject'],
|
||||
subject_code=class_data['subject_code'],
|
||||
path=class_path
|
||||
node_storage_path=class_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(subject_class_node, database=user_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(subject_class_node.path, subject_class_node.to_dict())
|
||||
fs_handler.create_default_tldraw_file(subject_class_node.node_storage_path, subject_class_node.to_dict())
|
||||
|
||||
# Link class to timetable
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -220,27 +220,27 @@ def create_user_worker_timetable(
|
||||
)
|
||||
|
||||
# Initialize empty list for this class's lessons
|
||||
class_lessons[class_data['unique_id']] = []
|
||||
class_lessons[class_data['uuid_string']] = []
|
||||
|
||||
# Get periods from school database
|
||||
periods = get_school_class_periods(school_db_name, class_data['unique_id'])
|
||||
periods = get_school_class_periods(school_db_name, class_data['uuid_string'])
|
||||
if not periods:
|
||||
logger.warning(f"No periods found for class {class_data['unique_id']} in school database")
|
||||
logger.warning(f"No periods found for class {class_data['uuid_string']} in school database")
|
||||
continue
|
||||
|
||||
for period_data in periods:
|
||||
# Create UserTimetableLessonNode
|
||||
lesson_unique_id = f"UserTimetableLesson_{timetable_unique_id}_{class_name_safe}_{period_data['date']}_{period_data['period_code']}"
|
||||
timetable_lesson_node = UserTimetableLessonNode(
|
||||
unique_id=lesson_unique_id,
|
||||
# Create TimetableLessonNode
|
||||
lesson_uuid_string = f"UserTimetableLesson_{timetable_uuid_string}_{class_name_safe}_{period_data['date']}_{period_data['period_code']}"
|
||||
timetable_lesson_node = TimetableLessonNode(
|
||||
uuid_string=lesson_uuid_string,
|
||||
subject_class=class_data['subject_class_code'],
|
||||
date=period_data['date'],
|
||||
start_time=period_data['start_time'],
|
||||
end_time=period_data['end_time'],
|
||||
period_code=period_data['period_code'],
|
||||
school_db_name=school_db_name,
|
||||
school_period_id=period_data['unique_id'],
|
||||
path="Not set" # Will be set after creating directories
|
||||
school_period_id=period_data['uuid_string'],
|
||||
node_storage_path="Not set" # Will be set after creating directories
|
||||
)
|
||||
|
||||
if calendar_day := next(
|
||||
@ -256,11 +256,11 @@ def create_user_worker_timetable(
|
||||
class_path,
|
||||
f"{calendar_day.date}_{period_data['period_code']}"
|
||||
)
|
||||
timetable_lesson_node.path = lesson_path
|
||||
timetable_lesson_node.node_storage_path = lesson_path
|
||||
|
||||
# Create and link nodes
|
||||
neon.create_or_merge_neontology_node(timetable_lesson_node, database=user_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(timetable_lesson_node.path, timetable_lesson_node.to_dict())
|
||||
fs_handler.create_default_tldraw_file(timetable_lesson_node.node_storage_path, timetable_lesson_node.to_dict())
|
||||
|
||||
# Link lesson to class
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
@ -280,7 +280,7 @@ def create_user_worker_timetable(
|
||||
)
|
||||
|
||||
# Store the lesson node
|
||||
class_lessons[class_data['unique_id']].append({
|
||||
class_lessons[class_data['uuid_string']].append({
|
||||
'node': timetable_lesson_node,
|
||||
'date': period_data['date'],
|
||||
'start_time': period_data['start_time']
|
||||
@ -299,7 +299,7 @@ def create_user_worker_timetable(
|
||||
next_lesson = sorted_lessons[i + 1]['node']
|
||||
|
||||
# Skip if current and next lesson are the same node
|
||||
if current_lesson.unique_id != next_lesson.unique_id:
|
||||
if current_lesson.uuid_string != next_lesson.uuid_string:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
TimetableLessonFollowsTimetableLesson(
|
||||
source=current_lesson,
|
||||
|
||||
@ -33,19 +33,19 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
|
||||
logging.info(f"Initialising filesystem handler...")
|
||||
fs_handler = ClassroomCopilotFilesystem(db_name=worker_db_name, init_run_type="user")
|
||||
_, worker_timetable_path = fs_handler.create_teacher_timetable_directory(worker_node.path)
|
||||
_, worker_timetable_path = fs_handler.create_teacher_timetable_directory(worker_node.node_storage_path)
|
||||
|
||||
logging.info(f"Initialising neo4j connection...")
|
||||
neon.init_neontology_connection()
|
||||
|
||||
try:
|
||||
timetable_unique_id = f"TeacherTimetable_{worker_node.teacher_code}"
|
||||
timetable_uuid_string = f"TeacherTimetable_{worker_node.teacher_code}"
|
||||
worker_timetable = TeacherTimetableNode(
|
||||
unique_id=timetable_unique_id,
|
||||
path=worker_timetable_path
|
||||
uuid_string=timetable_uuid_string,
|
||||
node_storage_path=worker_timetable_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(worker_timetable, database=worker_db_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(worker_timetable.path, worker_timetable.to_dict())
|
||||
fs_handler.create_default_tldraw_file(worker_timetable.node_storage_path, worker_timetable.to_dict())
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
TeacherHasTimetable(source=worker_node, target=worker_timetable),
|
||||
database=worker_db_name, operation='merge'
|
||||
@ -57,28 +57,28 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
for class_name, class_df in class_groups:
|
||||
if pd.notna(class_name):
|
||||
class_name_safe = re.sub(r'[^A-Za-z0-9_ ]+', '', class_name)
|
||||
_, class_path = fs_handler.create_teacher_class_directory(worker_timetable.path, class_name_safe)
|
||||
_, class_path = fs_handler.create_teacher_class_directory(worker_timetable.node_storage_path, class_name_safe)
|
||||
|
||||
subject_class_node_unique_id = f"SubjectClass_{class_name}"
|
||||
subject_class_node_uuid_string = f"SubjectClass_{class_name}"
|
||||
subject_class_node = SubjectClassNode(
|
||||
unique_id=subject_class_node_unique_id,
|
||||
uuid_string=subject_class_node_uuid_string,
|
||||
subject_class_code=class_name,
|
||||
year_group=str(int(class_df['YearGroup'].iloc[0])), # TODO: Hacky fix for the year group being a float
|
||||
subject=str(class_df['Subject'].iloc[0]),
|
||||
subject_code=str(class_df['SubjectCode'].iloc[0]),
|
||||
path=class_path
|
||||
node_storage_path=class_path
|
||||
)
|
||||
neon.create_or_merge_neontology_node(subject_class_node, database=worker_db_name, operation='merge')
|
||||
logging.info(f"Class node created: {subject_class_node}")
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(subject_class_node.path, subject_class_node.to_dict())
|
||||
fs_handler.create_default_tldraw_file(subject_class_node.node_storage_path, subject_class_node.to_dict())
|
||||
|
||||
# Link ClassNode to TeacherTimetableNode
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
TimetableHasClass(source=worker_timetable, target=subject_class_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Relationship created from {worker_timetable.unique_id} to {subject_class_node.unique_id}")
|
||||
logging.info(f"Relationship created from {worker_timetable.uuid_string} to {subject_class_node.uuid_string}")
|
||||
|
||||
# Link class to corresponding YearGoupSyllabus
|
||||
|
||||
@ -92,7 +92,7 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
SubjectClassBelongsToYearGroupSyllabus(source=subject_class_node, target=year_group_syllabus_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Relationship created from {subject_class_node.unique_id} to {year_group_syllabus_node.unique_id}")
|
||||
logging.info(f"Relationship created from {subject_class_node.uuid_string} to {year_group_syllabus_node.uuid_string}")
|
||||
else:
|
||||
logging.warning(f"No YearGroupSyllabus found for class {class_name} with year group {subject_class_node.year_group} and subject code {subject_class_node.subject_code}")
|
||||
|
||||
@ -125,16 +125,16 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
date = class_lesson['date']
|
||||
date_safe = date.strftime("%Y-%m-%d")
|
||||
# Clean the class_name to make it directory-safe (catch all for invalid characters)
|
||||
timetable_lesson_unique_id = f"TimetableLesson_{timetable_unique_id}_Class_{class_name}_Lesson_{lesson_number}_{date_safe}_{lesson_period_code}"
|
||||
timetable_lesson_uuid_string = f"TimetableLesson_{timetable_uuid_string}_Class_{class_name}_Lesson_{lesson_number}_{date_safe}_{lesson_period_code}"
|
||||
|
||||
timetable_lesson_node = TimetableLessonNode(
|
||||
unique_id=timetable_lesson_unique_id,
|
||||
uuid_string=timetable_lesson_uuid_string,
|
||||
subject_class=class_name,
|
||||
date=date,
|
||||
start_time=class_lesson['start_time'].time(), # TODO: This is probably how we should format the start and end time properties for all such nodes
|
||||
end_time=class_lesson['end_time'].time(),
|
||||
period_code=lesson_period_code,
|
||||
path="Not set"
|
||||
node_storage_path="Not set"
|
||||
)
|
||||
neon.create_or_merge_neontology_node(timetable_lesson_node, database=worker_db_name, operation='merge')
|
||||
logging.info(f"TimetableLessonNode created: {timetable_lesson_node}")
|
||||
@ -144,19 +144,19 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
TimetableLessonBelongsToPeriod(source=timetable_lesson_node, target=period_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Relationship created from {timetable_lesson_node.unique_id} to {period_node.unique_id}")
|
||||
logging.info(f"Relationship created from {timetable_lesson_node.uuid_string} to {period_node.uuid_string}")
|
||||
|
||||
# Link TimetableLessonNode to ClassNode
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
ClassHasLesson(source=subject_class_node, target=timetable_lesson_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Relationship created from {subject_class_node.unique_id} to {timetable_lesson_node.unique_id}")
|
||||
logging.info(f"Relationship created from {subject_class_node.uuid_string} to {timetable_lesson_node.uuid_string}")
|
||||
|
||||
# Create PlannedLessonNode
|
||||
planned_lesson_unique_id = f"PlannedLesson_{timetable_unique_id}_Class_{class_name}_Lesson_{lesson_number}_{date_safe}_{lesson_period_code}"
|
||||
planned_lesson_uuid_string = f"PlannedLesson_{timetable_uuid_string}_Class_{class_name}_Lesson_{lesson_number}_{date_safe}_{lesson_period_code}"
|
||||
planned_lesson_node = PlannedLessonNode(
|
||||
unique_id=planned_lesson_unique_id,
|
||||
uuid_string=planned_lesson_uuid_string,
|
||||
date=date,
|
||||
start_time=class_lesson['start_time'].time(),
|
||||
end_time=class_lesson['end_time'].time(),
|
||||
@ -174,7 +174,7 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
learning_statements=None,
|
||||
learning_resource_codes=None,
|
||||
learning_resources=None,
|
||||
path="Not set"
|
||||
node_storage_path="Not set"
|
||||
)
|
||||
# Create the PlannedLessonNode
|
||||
neon.create_or_merge_neontology_node(planned_lesson_node, database=worker_db_name, operation='merge')
|
||||
@ -186,7 +186,7 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
TimetableLessonHasPlannedLesson(source=timetable_lesson_node, target=planned_lesson_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Relationship created from {timetable_lesson_node.unique_id} to {planned_lesson_node.unique_id}")
|
||||
logging.info(f"Relationship created from {timetable_lesson_node.uuid_string} to {planned_lesson_node.uuid_string}")
|
||||
lesson_of_same_period += 1
|
||||
lesson_number += 1
|
||||
else:
|
||||
@ -201,17 +201,17 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
current_node = class_lesson_nodes[i]
|
||||
i_safe = f"{i:02d}"
|
||||
_, class_lesson_path = fs_handler.create_teacher_timetable_lesson_directory(class_path, f"{i_safe}_{current_node.date}_{current_node.period_code}")
|
||||
current_node.path = class_lesson_path
|
||||
current_node.node_storage_path = class_lesson_path
|
||||
neon.create_or_merge_neontology_node(current_node, database=worker_db_name, operation='merge')
|
||||
logging.info(f"TimetableLessonNode directory created and node merged into database: {current_node}")
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(current_node.path, current_node.to_dict())
|
||||
fs_handler.create_default_tldraw_file(current_node.node_storage_path, current_node.to_dict())
|
||||
if previous_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
TimetableLessonFollowsTimetableLesson(source=previous_node, target=current_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Sequential relationship created between {previous_node.unique_id} and {current_node.unique_id}")
|
||||
logging.info(f"Sequential relationship created between {previous_node.uuid_string} and {current_node.uuid_string}")
|
||||
|
||||
# Create sequential relationships for PlannedLessonNodes
|
||||
for i in range(1, len(planned_lesson_nodes)):
|
||||
@ -219,17 +219,17 @@ def init_worker_timetable(timetable_df: pd.DataFrame, school_worker_node: Teache
|
||||
current_node = planned_lesson_nodes[i]
|
||||
i_safe = f"{i:02d}"
|
||||
_, planned_lesson_path = fs_handler.create_teacher_planned_lesson_directory(class_path, f"{i_safe}_{current_node.date}_{current_node.period_code}")
|
||||
current_node.path = planned_lesson_path
|
||||
current_node.node_storage_path = planned_lesson_path
|
||||
neon.create_or_merge_neontology_node(current_node, database=worker_db_name, operation='merge')
|
||||
logging.info(f"PlannedLessonNode directory created and node merged into database: {current_node}")
|
||||
# Create the tldraw file for the node
|
||||
fs_handler.create_default_tldraw_file(current_node.path, current_node.to_dict())
|
||||
fs_handler.create_default_tldraw_file(current_node.node_storage_path, current_node.to_dict())
|
||||
if previous_node:
|
||||
neon.create_or_merge_neontology_relationship(
|
||||
PlannedLessonFollowsPlannedLesson(source=previous_node, target=current_node),
|
||||
database=worker_db_name, operation='merge'
|
||||
)
|
||||
logging.info(f"Sequential relationship created between {previous_node.unique_id} and {current_node.unique_id}")
|
||||
logging.info(f"Sequential relationship created between {previous_node.uuid_string} and {current_node.uuid_string}")
|
||||
logging.info(f"Successfully initialized worker timetable for worker {worker_node.teacher_code}")
|
||||
return {"status": "success", "message": "Worker timetable initialized successfully"}
|
||||
|
||||
|
||||
@ -3,15 +3,15 @@ from modules.database.tools.neontology.basenode import BaseNode
|
||||
|
||||
class CCBaseNode(BaseNode):
|
||||
__primarylabel__: ClassVar[str] = ''
|
||||
__primaryproperty__: ClassVar[str] = 'unique_id'
|
||||
unique_id: str
|
||||
tldraw_snapshot: str
|
||||
|
||||
__primaryproperty__: ClassVar[str] = 'uuid_string'
|
||||
uuid_string: str
|
||||
node_storage_path: str
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"__primarylabel__": self.__primarylabel__,
|
||||
"unique_id": self.unique_id,
|
||||
"tldraw_snapshot": self.tldraw_snapshot,
|
||||
"uuid_string": self.uuid_string,
|
||||
"node_storage_path": self.node_storage_path,
|
||||
}
|
||||
|
||||
class UserBaseNode(CCBaseNode):
|
||||
@ -25,8 +25,8 @@ class UserBaseNode(CCBaseNode):
|
||||
def to_dict(self):
|
||||
return {
|
||||
"__primarylabel__": self.__primarylabel__,
|
||||
"unique_id": self.unique_id,
|
||||
"tldraw_snapshot": self.tldraw_snapshot,
|
||||
"uuid_string": self.uuid_string,
|
||||
"node_storage_path": self.node_storage_path,
|
||||
"cc_username": self.cc_username,
|
||||
"user_db_name": self.user_db_name,
|
||||
"user_email": self.user_email,
|
||||
@ -43,8 +43,8 @@ class WorkerBaseNode(CCBaseNode):
|
||||
def to_dict(self):
|
||||
return {
|
||||
"__primarylabel__": self.__primarylabel__,
|
||||
"unique_id": self.unique_id,
|
||||
"tldraw_snapshot": self.tldraw_snapshot,
|
||||
"uuid_string": self.uuid_string,
|
||||
"node_storage_path": self.node_storage_path,
|
||||
"worker_name": self.worker_name,
|
||||
"worker_email": self.worker_email,
|
||||
"worker_db_name": self.worker_db_name,
|
||||
|
||||
@ -5,7 +5,7 @@ class SchoolNode(CCBaseNode):
|
||||
__primarylabel__: ClassVar[str] = 'School'
|
||||
|
||||
# Core identification fields (required for all databases)
|
||||
id: str # School's unique identifier within its type
|
||||
uuid_string: str # School's unique identifier within its type
|
||||
school_type: str # e.g., 'development', 'state', 'private', etc.
|
||||
name: str
|
||||
website: str = 'unknown'
|
||||
|
||||
@ -2,6 +2,7 @@ import os
|
||||
from typing import Dict, List, Optional
|
||||
from supabase import create_client
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.services.provisioning_service import ProvisioningService
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@ -31,6 +32,7 @@ class AdminService:
|
||||
"Authorization": f"Bearer {service_role_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
self.provisioner = ProvisioningService()
|
||||
|
||||
def get_admin_profile(self, admin_id: str) -> Optional[Dict]:
|
||||
"""Get admin profile by ID"""
|
||||
@ -88,6 +90,10 @@ class AdminService:
|
||||
result = (
|
||||
self.supabase.table("admin_profiles").insert(profile_data).execute()
|
||||
)
|
||||
try:
|
||||
self.provisioner.ensure_user(profile_data["id"])
|
||||
except Exception as exc:
|
||||
self.logger.warning(f"Provisioning admin user {profile_data['id']} failed: {exc}")
|
||||
return result.data[0] if result else None
|
||||
|
||||
except Exception as e:
|
||||
@ -186,6 +192,10 @@ class AdminService:
|
||||
result = (
|
||||
self.supabase.table("admin_profiles").insert(profile_data).execute()
|
||||
)
|
||||
try:
|
||||
self.provisioner.ensure_user(profile_data["id"])
|
||||
except Exception as exc:
|
||||
self.logger.warning(f"Provisioning super admin {profile_data['id']} failed: {exc}")
|
||||
return result.data[0] if result else None
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
from modules.logger_tool import initialise_logger
|
||||
import modules.database.tools.neo4j_driver_tools as driver_tools
|
||||
from modules.database.admin.neontology_provider import NeontologyProvider
|
||||
from modules.database.admin.graph_provider import GraphNamingProvider
|
||||
|
||||
class GraphService:
|
||||
def __init__(self):
|
||||
self.logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
self.driver = driver_tools.get_driver()
|
||||
self.neontology = NeontologyProvider()
|
||||
self.graph_naming = GraphNamingProvider()
|
||||
|
||||
def check_schema_status(self, database_name: str = "neo4j") -> Dict[str, Any]:
|
||||
"""Check the status of Neo4j schema including constraints, indexes, and labels"""
|
||||
try:
|
||||
with self.driver.session(database=database_name) as session:
|
||||
# Check constraints
|
||||
constraints_result = session.run("SHOW CONSTRAINTS")
|
||||
constraints = list(constraints_result)
|
||||
|
||||
# Check indexes
|
||||
indexes_result = session.run("SHOW INDEXES")
|
||||
indexes = list(indexes_result)
|
||||
|
||||
# Check labels
|
||||
labels_result = session.run("CALL db.labels()")
|
||||
labels = list(labels_result)
|
||||
|
||||
return {
|
||||
"constraints_count": len(constraints),
|
||||
"indexes_count": len(indexes),
|
||||
"labels_count": len(labels),
|
||||
"constraints": [dict(record) for record in constraints],
|
||||
"indexes": [dict(record) for record in indexes],
|
||||
"labels": [dict(record) for record in labels]
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking schema status: {str(e)}")
|
||||
return {
|
||||
"constraints_count": 0,
|
||||
"indexes_count": 0,
|
||||
"labels_count": 0,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def initialize_schema(self, database_name: str = "neo4j") -> Dict[str, Any]:
|
||||
"""Initialize Neo4j schema with required constraints and indexes"""
|
||||
try:
|
||||
schema_queries = self.graph_naming.get_schema_creation_queries()
|
||||
|
||||
with self.driver.session(database=database_name) as session:
|
||||
for query in schema_queries:
|
||||
session.run(query)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Schema initialized successfully",
|
||||
"details": self.check_schema_status(database_name)
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error initializing schema: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"message": str(e)
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
from datetime import datetime, timedelta
|
||||
import jwt
|
||||
from typing import Dict, List
|
||||
|
||||
class JWTService:
|
||||
"""JWT Service for Neo4j authentication
|
||||
|
||||
TODO: Security Enhancements Needed
|
||||
- Implement token refresh mechanism
|
||||
- Add token revocation capability
|
||||
- Add token validation checks
|
||||
- Implement rate limiting
|
||||
- Add audit logging for token generation/usage
|
||||
- Consider reducing token expiry time and implementing refresh tokens
|
||||
"""
|
||||
|
||||
def __init__(self, secret_key: str, algorithm: str = "HS256"):
|
||||
self.secret_key = secret_key
|
||||
self.algorithm = algorithm
|
||||
|
||||
def generate_neo4j_token(self, user_data: Dict) -> str:
|
||||
"""Generate JWT token for Neo4j database access"""
|
||||
payload = {
|
||||
"sub": user_data["email"],
|
||||
"roles": self._get_neo4j_roles(user_data["user_type"]),
|
||||
"iss": "supabase",
|
||||
"aud": "neo4j",
|
||||
"iat": datetime.utcnow(),
|
||||
"exp": datetime.utcnow() + timedelta(hours=24)
|
||||
}
|
||||
|
||||
if "school_uuid" in user_data:
|
||||
payload["worker_db_name"] = f"cc.institutes.{user_data['school_uuid']}"
|
||||
|
||||
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
||||
|
||||
def _get_neo4j_roles(self, user_type: str) -> List[str]:
|
||||
"""Map user types to Neo4j roles"""
|
||||
role_mapping = {
|
||||
"cc_admin": ["admin", "reader", "writer"],
|
||||
"developer": ["developer", "reader", "writer"],
|
||||
"email_teacher": ["teacher", "reader", "writer"],
|
||||
"email_student": ["student", "reader"]
|
||||
}
|
||||
return role_mapping.get(user_type, ["reader"])
|
||||
@ -86,18 +86,18 @@ class Neo4jService:
|
||||
with self.driver.session(database=database_name) as session:
|
||||
# Create constraints
|
||||
constraints = [
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:School) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Department) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Subject) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:YearGroup) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Class) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Teacher) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Student) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Calendar) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Term) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Week) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Day) REQUIRE n.unique_id IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Period) REQUIRE n.unique_id IS UNIQUE"
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:School) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Department) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Subject) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:YearGroup) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Class) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Teacher) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Student) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Calendar) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Term) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Week) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Day) REQUIRE n.uuid_string IS UNIQUE",
|
||||
"CREATE CONSTRAINT IF NOT EXISTS FOR (n:Period) REQUIRE n.uuid_string IS UNIQUE"
|
||||
]
|
||||
|
||||
# Create indexes
|
||||
|
||||
306
modules/database/services/provisioning_service.py
Normal file
306
modules/database/services/provisioning_service.py
Normal file
@ -0,0 +1,306 @@
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Optional, Tuple, List
|
||||
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.services.neo4j_service import Neo4jService
|
||||
from modules.database.init import init_user
|
||||
from modules.database.tools.supabase_storage_tools import SupabaseStorageTools
|
||||
from modules.database.schemas.nodes.schools.schools import SchoolNode
|
||||
import modules.database.tools.neontology_tools as neon
|
||||
from modules.database.tools.neontology_tools import create_or_merge_neontology_node
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
_CC_USERS_DB = "cc.users"
|
||||
_CC_SCHOOLS_DB = "cc.institutes"
|
||||
_DEFAULT_INSTITUTE_NAME = os.getenv("DEFAULT_INSTITUTE_NAME", "KevlarAI")
|
||||
_DEFAULT_INSTITUTE_ID = os.getenv("DEFAULT_INSTITUTE_ID")
|
||||
|
||||
|
||||
class ProvisioningService:
|
||||
"""Coordinates provisioning of Neo4j resources for schools and users."""
|
||||
|
||||
def __init__(self):
|
||||
self.neo4j_service = Neo4jService()
|
||||
self.supabase = SupabaseServiceRoleClient().supabase
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Naming helpers
|
||||
# ------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def _sanitize_component(value: str) -> str:
|
||||
return "".join(ch for ch in value.lower() if ch.isalnum())
|
||||
|
||||
def _build_user_db_name(self, role: str, user_id: str) -> str:
|
||||
return f"{_CC_USERS_DB}.{self._sanitize_component(role)}.{self._sanitize_component(user_id)}"
|
||||
|
||||
def _build_school_db_name(self, institute_id: str) -> str:
|
||||
return f"{_CC_SCHOOLS_DB}.{self._sanitize_component(institute_id)}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Supabase helpers
|
||||
# ------------------------------------------------------------------
|
||||
def _get_profile(self, user_id: str) -> Dict:
|
||||
response = (
|
||||
self.supabase
|
||||
.table("profiles")
|
||||
.select("*")
|
||||
.eq("id", user_id)
|
||||
.single()
|
||||
.execute()
|
||||
)
|
||||
if not response.data:
|
||||
raise ValueError(f"Profile {user_id} not found")
|
||||
return response.data
|
||||
|
||||
def _get_membership(self, profile_id: str) -> Optional[Dict]:
|
||||
response = (
|
||||
self.supabase
|
||||
.table("institute_memberships")
|
||||
.select("*")
|
||||
.eq("profile_id", profile_id)
|
||||
.limit(1)
|
||||
.execute()
|
||||
)
|
||||
data = response.data or []
|
||||
return data[0] if data else None
|
||||
|
||||
def _get_institute(self, institute_id: str) -> Optional[Dict]:
|
||||
response = (
|
||||
self.supabase
|
||||
.table("institutes")
|
||||
.select("*")
|
||||
.eq("id", institute_id)
|
||||
.single()
|
||||
.execute()
|
||||
)
|
||||
return response.data if response and response.data else None
|
||||
|
||||
def _get_institute_by_name(self, name: str) -> Optional[Dict]:
|
||||
response = (
|
||||
self.supabase
|
||||
.table("institutes")
|
||||
.select("*")
|
||||
.eq("name", name)
|
||||
.limit(1)
|
||||
.execute()
|
||||
)
|
||||
data = response.data or []
|
||||
return data[0] if data else None
|
||||
|
||||
def _determine_membership_role(self, user_type: str) -> str:
|
||||
if "teacher" in user_type:
|
||||
return "teacher"
|
||||
if "student" in user_type:
|
||||
return "student"
|
||||
return "staff"
|
||||
|
||||
def _ensure_membership(self, profile: Dict, user_type: str) -> Optional[Dict]:
|
||||
membership = self._get_membership(profile["id"])
|
||||
if membership:
|
||||
return membership
|
||||
|
||||
institute_id = _DEFAULT_INSTITUTE_ID
|
||||
institute = None
|
||||
if institute_id:
|
||||
institute = self._get_institute(institute_id)
|
||||
if not institute:
|
||||
logger.warning(f"Default institute {_DEFAULT_INSTITUTE_ID} not found; attempting lookup by name")
|
||||
institute_id = None
|
||||
|
||||
if not institute_id:
|
||||
institute = self._get_institute_by_name(_DEFAULT_INSTITUTE_NAME)
|
||||
if not institute:
|
||||
raise ValueError(f"Default institute '{_DEFAULT_INSTITUTE_NAME}' not found; cannot create membership")
|
||||
institute_id = institute["id"]
|
||||
|
||||
role = self._determine_membership_role(user_type)
|
||||
|
||||
try:
|
||||
response = (
|
||||
self.supabase
|
||||
.table("institute_memberships")
|
||||
.insert({
|
||||
"profile_id": profile["id"],
|
||||
"institute_id": institute_id,
|
||||
"role": role
|
||||
})
|
||||
.execute()
|
||||
)
|
||||
data = response.data or []
|
||||
membership = data[0] if isinstance(data, list) and data else data
|
||||
email = profile.get("email") or profile.get("user_email") or profile.get("id")
|
||||
logger.info(f"Created institute membership for {email} -> {institute_id} as {role}")
|
||||
return membership
|
||||
except Exception as exc:
|
||||
logger.warning(f"Failed to create institute membership for {profile['id']}: {exc}")
|
||||
# Try to fetch again in case of race condition
|
||||
return self._get_membership(profile["id"])
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Provisioning actions
|
||||
# ------------------------------------------------------------------
|
||||
def ensure_school(self, institute_id: str) -> Dict[str, str]:
|
||||
"""Ensure the Neo4j databases and root nodes exist for a school."""
|
||||
institute = self._get_institute(institute_id)
|
||||
if not institute:
|
||||
raise ValueError(f"Institute {institute_id} not found in Supabase")
|
||||
|
||||
school_db = self._build_school_db_name(institute_id)
|
||||
curriculum_db = f"{school_db}.curriculum"
|
||||
|
||||
# Ensure root namespaces exist
|
||||
self.neo4j_service.create_database(_CC_SCHOOLS_DB)
|
||||
self.neo4j_service.create_database(school_db)
|
||||
self.neo4j_service.create_database(curriculum_db)
|
||||
|
||||
metadata = institute.get("metadata") or {}
|
||||
if isinstance(metadata, str):
|
||||
try:
|
||||
metadata = json.loads(metadata)
|
||||
except json.JSONDecodeError:
|
||||
metadata = {}
|
||||
|
||||
school_type = metadata.get("school_type") or institute.get("school_type") or "demo"
|
||||
school_node = SchoolNode(
|
||||
uuid_string=self._sanitize_component(institute_id),
|
||||
node_storage_path=f"schools/{self._sanitize_component(institute_id)}/databases/{school_db}/{self._sanitize_component(institute_id)}",
|
||||
school_type=self._sanitize_component(school_type) or "demo",
|
||||
name=institute.get("name", "Unknown School"),
|
||||
website=institute.get("website", "https://example.com"),
|
||||
)
|
||||
|
||||
neon.init_neontology_connection()
|
||||
try:
|
||||
create_or_merge_neontology_node(school_node, database=_CC_SCHOOLS_DB, operation='merge')
|
||||
create_or_merge_neontology_node(school_node, database=school_db, operation='merge')
|
||||
finally:
|
||||
neon.close_neontology_connection()
|
||||
|
||||
# Try to persist database references back to Supabase (best effort)
|
||||
updates = {
|
||||
"neo4j_private_db_name": school_db,
|
||||
"neo4j_private_sync_status": "ready",
|
||||
"neo4j_private_sync_at": datetime.utcnow().isoformat(),
|
||||
}
|
||||
try:
|
||||
(
|
||||
self.supabase
|
||||
.table("institutes")
|
||||
.update(updates)
|
||||
.eq("id", institute_id)
|
||||
.execute()
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - defensive logging only
|
||||
logger.warning(f"Failed to update institute {institute_id} with db info: {exc}")
|
||||
|
||||
return {
|
||||
"db_name": school_db,
|
||||
"curriculum_db_name": curriculum_db,
|
||||
"school_node": school_node,
|
||||
}
|
||||
|
||||
def ensure_user(self, user_id: str) -> Dict[str, Optional[str]]:
|
||||
"""Provision Neo4j resources for a specific user profile."""
|
||||
profile = self._get_profile(user_id)
|
||||
user_type_raw = (profile.get("user_type") or "").lower()
|
||||
|
||||
user_type_map = {
|
||||
"teacher": ("email_teacher", "teacher"),
|
||||
"email_teacher": ("email_teacher", "teacher"),
|
||||
"student": ("email_student", "student"),
|
||||
"email_student": ("email_student", "student"),
|
||||
"developer": ("developer", "developer"),
|
||||
"cc_developer": ("developer", "developer"),
|
||||
"admin": ("superadmin", "superadmin"),
|
||||
"super_admin": ("superadmin", "superadmin"),
|
||||
"superadmin": ("superadmin", "superadmin"),
|
||||
}
|
||||
neo_user_type, worker_type = user_type_map.get(user_type_raw, (user_type_raw or "standard", user_type_raw or "standard"))
|
||||
|
||||
user_db_name = profile.get("user_db_name")
|
||||
if not user_db_name:
|
||||
user_db_name = self._build_user_db_name(worker_type, user_id)
|
||||
|
||||
full_name = profile.get("full_name") or profile.get("display_name") or profile.get("username") or "User"
|
||||
username = profile.get("username") or self._sanitize_component(profile.get("email", "user"))
|
||||
user_email = profile.get("email") or profile.get("user_email") or ""
|
||||
|
||||
school_db_name = profile.get("school_db_name")
|
||||
school_node = None
|
||||
|
||||
membership = None
|
||||
if worker_type in ("teacher", "student"):
|
||||
membership = self._ensure_membership(profile, user_type_raw)
|
||||
if not membership:
|
||||
raise ValueError("Unable to determine institute membership for school-based user")
|
||||
if membership:
|
||||
institute_id = membership.get("institute_id")
|
||||
if institute_id:
|
||||
ensure_school_result = self.ensure_school(institute_id)
|
||||
school_db_name = ensure_school_result["db_name"]
|
||||
school_meta = ensure_school_result.get("school_node")
|
||||
if isinstance(school_meta, SchoolNode):
|
||||
school_node = school_meta
|
||||
else:
|
||||
school_node = SchoolNode(
|
||||
uuid_string=self._sanitize_component(institute_id),
|
||||
node_storage_path="",
|
||||
school_type=getattr(school_meta, "school_type", "demo") if school_meta else "demo",
|
||||
name=(school_meta.get("name") if isinstance(school_meta, dict) else None) or "Unknown School",
|
||||
website=(school_meta.get("website") if isinstance(school_meta, dict) else None) or "https://example.com",
|
||||
)
|
||||
|
||||
# Ensure base namespaces exist before creating user-specific db
|
||||
self.neo4j_service.create_database(_CC_USERS_DB)
|
||||
self.neo4j_service.create_database(user_db_name)
|
||||
|
||||
calendar_start = datetime.utcnow().date()
|
||||
calendar_end = (datetime.utcnow() + timedelta(days=365)).date()
|
||||
|
||||
# Initialize storage tools for user provisioning
|
||||
storage_tools = SupabaseStorageTools(user_db_name, init_run_type="user")
|
||||
|
||||
init_user.create_user(
|
||||
user_id=user_id,
|
||||
user_type=neo_user_type,
|
||||
username=username,
|
||||
user_email=user_email,
|
||||
user_name=full_name,
|
||||
worker_name=full_name,
|
||||
worker_type=worker_type,
|
||||
worker_email=user_email,
|
||||
cc_users_db_name=_CC_USERS_DB,
|
||||
user_db_name=user_db_name,
|
||||
worker_db_name=school_db_name,
|
||||
calendar_start_date=calendar_start,
|
||||
calendar_end_date=calendar_end,
|
||||
school_node=school_node,
|
||||
storage_tools=storage_tools,
|
||||
)
|
||||
|
||||
profile_updates = {
|
||||
"user_db_name": user_db_name,
|
||||
"school_db_name": school_db_name,
|
||||
"neo4j_sync_status": "ready",
|
||||
"neo4j_synced_at": datetime.utcnow().isoformat(),
|
||||
}
|
||||
try:
|
||||
(
|
||||
self.supabase
|
||||
.table("profiles")
|
||||
.update(profile_updates)
|
||||
.eq("id", user_id)
|
||||
.execute()
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - logging only
|
||||
logger.warning(f"Failed to update profile {user_id} with provisioning info: {exc}")
|
||||
|
||||
return {
|
||||
"user_db_name": user_db_name,
|
||||
"worker_db_name": school_db_name,
|
||||
"worker_type": worker_type,
|
||||
}
|
||||
@ -1,412 +0,0 @@
|
||||
import os
|
||||
from typing import Dict, Any, BinaryIO
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
import modules.database.tools.neo4j_driver_tools as driver_tools
|
||||
import modules.database.tools.neo4j_session_tools as session_tools
|
||||
import modules.database.schemas.nodes.schools.schools as school_nodes
|
||||
import modules.database.schemas.nodes.schools.curriculum as curriculum_nodes
|
||||
import modules.database.schemas.nodes.schools.pastoral as pastoral_nodes
|
||||
import modules.database.schemas.nodes.structures.schools as school_structures
|
||||
from modules.database.schemas.entities import entities
|
||||
from modules.database.schemas.relationships import curriculum_relationships, entity_relationships, entity_curriculum_rels
|
||||
from modules.database.admin.neontology_provider import NeontologyProvider
|
||||
from modules.database.admin.graph_provider import GraphNamingProvider
|
||||
from modules.database.supabase.utils.client import SupabaseAnonClient
|
||||
from modules.database.supabase.utils.storage import StorageManager
|
||||
from modules.database.services.neo4j_service import Neo4jService
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
class SchoolAdminService:
|
||||
def __init__(self):
|
||||
self.logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
self.driver = driver_tools.get_driver()
|
||||
self.neontology = NeontologyProvider()
|
||||
self.graph_naming = GraphNamingProvider()
|
||||
self.storage = StorageManager(SupabaseAnonClient)
|
||||
self.neo4j_service = Neo4jService()
|
||||
|
||||
def check_database_exists(self, database_name: str) -> Dict[str, Any]:
|
||||
"""Check if a Neo4j database exists"""
|
||||
return self.neo4j_service.check_database_exists(database_name)
|
||||
|
||||
def create_database(self, db_name: str) -> Dict:
|
||||
"""Creates a Neo4j database with the given name"""
|
||||
return self.neo4j_service.create_database(db_name)
|
||||
|
||||
def create_school_node(self, school_data: Dict) -> Dict:
|
||||
"""Creates a school node in cc.institutes database and stores TLDraw file in Supabase"""
|
||||
try:
|
||||
# Convert school data to SchoolNode
|
||||
school_unique_id = self.graph_naming.get_school_unique_id(school_data['urn'])
|
||||
school_path = self.graph_naming.get_school_path("cc.institutes", school_data['urn'])
|
||||
|
||||
school_node = entities.SchoolNode(
|
||||
unique_id=school_unique_id,
|
||||
path=school_path,
|
||||
urn=school_data['urn'],
|
||||
establishment_number=school_data['establishment_number'],
|
||||
establishment_name=school_data['establishment_name'],
|
||||
establishment_type=school_data['establishment_type'],
|
||||
establishment_status=school_data['establishment_status'],
|
||||
phase_of_education=school_data['phase_of_education'] if school_data['phase_of_education'] not in [None, ''] else None,
|
||||
statutory_low_age=int(school_data['statutory_low_age']) if school_data.get('statutory_low_age') is not None else 0,
|
||||
statutory_high_age=int(school_data['statutory_high_age']) if school_data.get('statutory_high_age') is not None else 0,
|
||||
religious_character=school_data.get('religious_character') if school_data.get('religious_character') not in [None, ''] else None,
|
||||
school_capacity=int(school_data['school_capacity']) if school_data.get('school_capacity') is not None else 0,
|
||||
school_website=school_data.get('school_website', ''),
|
||||
ofsted_rating=school_data.get('ofsted_rating') if school_data.get('ofsted_rating') not in [None, ''] else None
|
||||
)
|
||||
|
||||
# Create default tldraw file data
|
||||
tldraw_data = {
|
||||
"document": {
|
||||
"version": 1,
|
||||
"id": school_data['urn'],
|
||||
"name": school_data['establishment_name'],
|
||||
"meta": {
|
||||
"created_at": "",
|
||||
"updated_at": "",
|
||||
"creator_id": "",
|
||||
"is_template": False,
|
||||
"is_snapshot": False,
|
||||
"is_draft": False,
|
||||
"template_id": None,
|
||||
"snapshot_id": None,
|
||||
"draft_id": None
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"schemaVersion": 1,
|
||||
"storeVersion": 4,
|
||||
"recordVersions": {
|
||||
"asset": {
|
||||
"version": 1,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {}
|
||||
},
|
||||
"camera": {
|
||||
"version": 1
|
||||
},
|
||||
"document": {
|
||||
"version": 2
|
||||
},
|
||||
"instance": {
|
||||
"version": 22
|
||||
},
|
||||
"instance_page_state": {
|
||||
"version": 5
|
||||
},
|
||||
"page": {
|
||||
"version": 1
|
||||
},
|
||||
"shape": {
|
||||
"version": 3,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {
|
||||
"cc-school-node": 1
|
||||
}
|
||||
},
|
||||
"instance_presence": {
|
||||
"version": 5
|
||||
},
|
||||
"pointer": {
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": school_data['establishment_name'],
|
||||
"meta": {},
|
||||
"id": school_data['urn'],
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
},
|
||||
"shape:school-node": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"rotation": 0,
|
||||
"type": "cc-school-node",
|
||||
"id": school_unique_id,
|
||||
"parentId": "page",
|
||||
"index": "a1",
|
||||
"props": school_node.to_dict(),
|
||||
"typeName": "shape"
|
||||
},
|
||||
"instance:instance": {
|
||||
"id": "instance",
|
||||
"currentPageId": "page",
|
||||
"typeName": "instance"
|
||||
},
|
||||
"camera:camera": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 1,
|
||||
"id": "camera",
|
||||
"typeName": "camera"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Store tldraw file in Supabase storage
|
||||
file_path = f"{school_data['urn']}/tldraw.json"
|
||||
file_options = {
|
||||
"content-type": "application/json",
|
||||
"x-upsert": "true",
|
||||
"metadata": {
|
||||
"establishment_urn": school_data['urn'],
|
||||
"establishment_name": school_data['establishment_name']
|
||||
}
|
||||
}
|
||||
|
||||
# Upload file
|
||||
self.storage.upload_file(
|
||||
bucket_id="cc.institutes",
|
||||
file_path=file_path,
|
||||
file_data=json.dumps(tldraw_data).encode(),
|
||||
content_type="application/json",
|
||||
upsert=True
|
||||
)
|
||||
|
||||
# Create node in Neo4j
|
||||
with self.neontology as neo:
|
||||
self.logger.info(f"Creating school node in Neo4j: {school_node.to_dict()}")
|
||||
neo.create_or_merge_node(school_node, database="cc.institutes", operation="merge")
|
||||
return {"status": "success", "node": school_node}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating school node: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_private_database(self, school_data: Dict) -> Dict:
|
||||
"""Creates a private database for a specific school"""
|
||||
try:
|
||||
private_db_name = f"cc.institutes.{school_data['urn']}"
|
||||
with self.driver.session() as session:
|
||||
session_tools.create_database(session, private_db_name)
|
||||
self.logger.info(f"Created private database {private_db_name}")
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Database {private_db_name} created successfully"
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating private database: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_basic_structure(self, school_node: school_nodes.SchoolNode, database_name: str) -> Dict:
|
||||
"""Creates basic structural nodes in the specified database"""
|
||||
try:
|
||||
# Create Department Structure node
|
||||
department_structure_node_unique_id = f"DepartmentStructure_{school_node.unique_id}"
|
||||
department_structure_node = entities.DepartmentStructureNode(
|
||||
unique_id=department_structure_node_unique_id,
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
|
||||
# Create Curriculum Structure node
|
||||
curriculum_node = curriculum_nodes.CurriculumStructureNode(
|
||||
unique_id=f"CurriculumStructure_{school_node.unique_id}",
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
|
||||
# Create Pastoral Structure node
|
||||
pastoral_node = school_structures.PastoralStructureNode(
|
||||
unique_id=f"PastoralStructure_{school_node.unique_id}",
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
|
||||
with self.neontology as neo:
|
||||
# Create nodes
|
||||
neo.create_or_merge_node(department_structure_node, database=str(database_name), operation='merge')
|
||||
neo.create_or_merge_node(curriculum_node, database=str(database_name), operation='merge')
|
||||
neo.create_or_merge_node(pastoral_node, database=str(database_name), operation='merge')
|
||||
|
||||
# Create relationships
|
||||
neo.create_or_merge_relationship(
|
||||
entity_relationships.SchoolHasDepartmentStructure(source=school_node, target=department_structure_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
neo.create_or_merge_relationship(
|
||||
entity_curriculum_rels.SchoolHasCurriculumStructure(source=school_node, target=curriculum_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
neo.create_or_merge_relationship(
|
||||
entity_curriculum_rels.SchoolHasPastoralStructure(source=school_node, target=pastoral_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Basic structure created successfully",
|
||||
"nodes": {
|
||||
"department_structure": department_structure_node,
|
||||
"curriculum_structure": curriculum_node,
|
||||
"pastoral_structure": pastoral_node
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating basic structure: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_detailed_structure(self, school_node: school_nodes.SchoolNode, database_name: str, excel_file: BinaryIO) -> Dict:
|
||||
"""Creates detailed structural nodes from Excel file"""
|
||||
try:
|
||||
# Store Excel file in Supabase
|
||||
file_path = f"{school_node.urn}/structure.xlsx"
|
||||
|
||||
# Upload Excel file
|
||||
self.storage.upload_file(
|
||||
bucket_id="cc.institutes",
|
||||
file_path=file_path,
|
||||
file_data=excel_file.read(),
|
||||
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
upsert=True
|
||||
)
|
||||
|
||||
# Process Excel file
|
||||
dataframes = pd.read_excel(excel_file, sheet_name=None)
|
||||
|
||||
# Get existing basic structure nodes
|
||||
with self.neontology as neo:
|
||||
result = neo.cypher_read("""
|
||||
MATCH (s:School {unique_id: $school_id})
|
||||
OPTIONAL MATCH (s)-[:HAS_DEPARTMENT_STRUCTURE]->(ds:DepartmentStructure)
|
||||
OPTIONAL MATCH (s)-[:HAS_CURRICULUM_STRUCTURE]->(cs:CurriculumStructure)
|
||||
OPTIONAL MATCH (s)-[:HAS_PASTORAL_STRUCTURE]->(ps:PastoralStructure)
|
||||
RETURN ds, cs, ps
|
||||
""", {"school_id": school_node.unique_id}, database=database_name)
|
||||
|
||||
if not result:
|
||||
raise Exception("Basic structure not found")
|
||||
|
||||
department_structure = result['ds']
|
||||
curriculum_structure = result['cs']
|
||||
pastoral_structure = result['ps']
|
||||
|
||||
# Create departments and subjects
|
||||
unique_departments = dataframes['keystagesyllabuses']['Department'].dropna().unique()
|
||||
|
||||
node_library = {}
|
||||
|
||||
with self.neontology as neo:
|
||||
for department_name in unique_departments:
|
||||
|
||||
department_node = entities.DepartmentNode(
|
||||
unique_id=f"Department_{school_node.unique_id}_{department_name.replace(' ', '_')}",
|
||||
department_name=department_name,
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
neo.create_or_merge_node(department_node, database=database_name, operation='merge')
|
||||
node_library[f'department_{department_name}'] = department_node
|
||||
|
||||
# Link to department structure
|
||||
neo.create_or_merge_relationship(
|
||||
entity_relationships.DepartmentStructureHasDepartment(
|
||||
source=department_structure,
|
||||
target=department_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
# Create year groups
|
||||
year_groups = self.sort_year_groups(dataframes['yeargroupsyllabuses'])['YearGroup'].unique()
|
||||
last_year_group_node = None
|
||||
|
||||
for year_group in year_groups:
|
||||
numeric_year_group = pd.to_numeric(year_group, errors='coerce')
|
||||
if pd.notna(numeric_year_group):
|
||||
year_group_node = pastoral_nodes.YearGroupNode(
|
||||
unique_id=f"YearGroup_{school_node.unique_id}_YGrp{int(numeric_year_group)}",
|
||||
year_group=str(int(numeric_year_group)),
|
||||
year_group_name=f"Year {int(numeric_year_group)}",
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
neo.create_or_merge_node(year_group_node, database=database_name, operation='merge')
|
||||
node_library[f'year_group_{int(numeric_year_group)}'] = year_group_node
|
||||
|
||||
# Create sequential relationship
|
||||
if last_year_group_node:
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.YearGroupFollowsYearGroup(
|
||||
source=last_year_group_node,
|
||||
target=year_group_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
last_year_group_node = year_group_node
|
||||
|
||||
# Link to pastoral structure
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.PastoralStructureIncludesYearGroup(
|
||||
source=pastoral_structure,
|
||||
target=year_group_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
# Create key stages
|
||||
key_stages = dataframes['keystagesyllabuses']['KeyStage'].unique()
|
||||
last_key_stage_node = None
|
||||
|
||||
for key_stage in sorted(key_stages):
|
||||
key_stage_node = curriculum_nodes.KeyStageNode(
|
||||
unique_id=f"KeyStage_{curriculum_structure.unique_id}_KStg{key_stage}",
|
||||
key_stage_name=f"Key Stage {key_stage}",
|
||||
key_stage=str(key_stage),
|
||||
tldraw_snapshot=""
|
||||
)
|
||||
neo.create_or_merge_node(key_stage_node, database=database_name, operation='merge')
|
||||
node_library[f'key_stage_{key_stage}'] = key_stage_node
|
||||
|
||||
# Create sequential relationship
|
||||
if last_key_stage_node:
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.KeyStageFollowsKeyStage(
|
||||
source=last_key_stage_node,
|
||||
target=key_stage_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
last_key_stage_node = key_stage_node
|
||||
|
||||
# Link to curriculum structure
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.CurriculumStructureIncludesKeyStage(
|
||||
source=curriculum_structure,
|
||||
target=key_stage_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Detailed structure created successfully",
|
||||
"node_library": node_library
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating detailed structure: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def sort_year_groups(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Helper function to sort year groups numerically"""
|
||||
df = df.copy()
|
||||
df['YearGroupNumeric'] = pd.to_numeric(df['YearGroup'], errors='coerce')
|
||||
return df.sort_values(by='YearGroupNumeric')
|
||||
|
||||
|
||||
@ -1,472 +0,0 @@
|
||||
import os
|
||||
from typing import Dict, List, Optional, BinaryIO
|
||||
import json
|
||||
import pandas as pd
|
||||
from backend.modules.database.schemas import entities
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.tools.filesystem_tools import ClassroomCopilotFilesystem
|
||||
import modules.database.tools.neo4j_driver_tools as driver_tools
|
||||
import modules.database.tools.neo4j_session_tools as session_tools
|
||||
from modules.database.admin.neontology_provider import NeontologyProvider
|
||||
from modules.database.admin.graph_provider import GraphNamingProvider
|
||||
from modules.database.schemas import curriculum_neo
|
||||
from modules.database.schemas.relationships import curriculum_relationships, entity_relationships, entity_curriculum_rels
|
||||
from modules.database.supabase.utils.storage import StorageManager
|
||||
|
||||
class SchoolService:
|
||||
def __init__(self):
|
||||
self.logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
self.driver = driver_tools.get_driver()
|
||||
self.neontology = NeontologyProvider()
|
||||
self.graph_naming = GraphNamingProvider()
|
||||
self.storage = StorageManager()
|
||||
|
||||
def create_schools_database(self) -> Dict:
|
||||
"""Creates the main cc.institutes database in Neo4j"""
|
||||
try:
|
||||
db_name = "cc.institutes"
|
||||
with self.driver.session() as session:
|
||||
session_tools.create_database(session, db_name)
|
||||
self.logger.info(f"Created database {db_name}")
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Database {db_name} created successfully"
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating schools database: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_school_node(self, school_data: Dict) -> Dict:
|
||||
"""Creates a school node in cc.institutes database and stores TLDraw file in Supabase"""
|
||||
try:
|
||||
# Convert school data to SchoolNode
|
||||
school_unique_id = self.graph_naming.get_school_unique_id(school_data['urn'])
|
||||
school_path = self.graph_naming.get_school_path("cc.institutes", school_data['urn'])
|
||||
|
||||
school_node = entities.SchoolNode(
|
||||
unique_id=school_unique_id,
|
||||
path=school_path,
|
||||
urn=school_data['urn'],
|
||||
establishment_number=school_data['establishment_number'],
|
||||
establishment_name=school_data['establishment_name'],
|
||||
establishment_type=school_data['establishment_type'],
|
||||
establishment_status=school_data['establishment_status'],
|
||||
phase_of_education=school_data['phase_of_education'] if school_data['phase_of_education'] not in [None, ''] else None,
|
||||
statutory_low_age=int(school_data['statutory_low_age']) if school_data.get('statutory_low_age') is not None else 0,
|
||||
statutory_high_age=int(school_data['statutory_high_age']) if school_data.get('statutory_high_age') is not None else 0,
|
||||
religious_character=school_data.get('religious_character') if school_data.get('religious_character') not in [None, ''] else None,
|
||||
school_capacity=int(school_data['school_capacity']) if school_data.get('school_capacity') is not None else 0,
|
||||
school_website=school_data.get('school_website', ''),
|
||||
ofsted_rating=school_data.get('ofsted_rating') if school_data.get('ofsted_rating') not in [None, ''] else None
|
||||
)
|
||||
|
||||
# Create default tldraw file data
|
||||
tldraw_data = {
|
||||
"document": {
|
||||
"version": 1,
|
||||
"id": school_data['urn'],
|
||||
"name": school_data['establishment_name'],
|
||||
"meta": {
|
||||
"created_at": "",
|
||||
"updated_at": "",
|
||||
"creator_id": "",
|
||||
"is_template": False,
|
||||
"is_snapshot": False,
|
||||
"is_draft": False,
|
||||
"template_id": None,
|
||||
"snapshot_id": None,
|
||||
"draft_id": None
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"schemaVersion": 1,
|
||||
"storeVersion": 4,
|
||||
"recordVersions": {
|
||||
"asset": {
|
||||
"version": 1,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {}
|
||||
},
|
||||
"camera": {
|
||||
"version": 1
|
||||
},
|
||||
"document": {
|
||||
"version": 2
|
||||
},
|
||||
"instance": {
|
||||
"version": 22
|
||||
},
|
||||
"instance_page_state": {
|
||||
"version": 5
|
||||
},
|
||||
"page": {
|
||||
"version": 1
|
||||
},
|
||||
"shape": {
|
||||
"version": 3,
|
||||
"subTypeKey": "type",
|
||||
"subTypeVersions": {
|
||||
"cc-school-node": 1
|
||||
}
|
||||
},
|
||||
"instance_presence": {
|
||||
"version": 5
|
||||
},
|
||||
"pointer": {
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": school_data['establishment_name'],
|
||||
"meta": {},
|
||||
"id": school_data['urn'],
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
},
|
||||
"shape:school-node": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"rotation": 0,
|
||||
"type": "cc-school-node",
|
||||
"id": school_unique_id,
|
||||
"parentId": "page",
|
||||
"index": "a1",
|
||||
"props": school_node.to_dict(),
|
||||
"typeName": "shape"
|
||||
},
|
||||
"instance:instance": {
|
||||
"id": "instance",
|
||||
"currentPageId": "page",
|
||||
"typeName": "instance"
|
||||
},
|
||||
"camera:camera": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 1,
|
||||
"id": "camera",
|
||||
"typeName": "camera"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Store tldraw file in Supabase storage
|
||||
file_path = f"{school_data['urn']}/tldraw.json"
|
||||
file_options = {
|
||||
"content-type": "application/json",
|
||||
"x-upsert": "true",
|
||||
"metadata": {
|
||||
"establishment_urn": school_data['urn'],
|
||||
"establishment_name": school_data['establishment_name']
|
||||
}
|
||||
}
|
||||
|
||||
# Upload file
|
||||
self.storage.upload_file(
|
||||
bucket_id="cc.institutes",
|
||||
file_path=file_path,
|
||||
file_data=json.dumps(tldraw_data).encode(),
|
||||
content_type="application/json",
|
||||
upsert=True
|
||||
)
|
||||
|
||||
# Create node in Neo4j
|
||||
with self.neontology as neo:
|
||||
self.logger.info(f"Creating school node in Neo4j: {school_node.to_dict()}")
|
||||
neo.create_or_merge_node(school_node, database="cc.institutes", operation="merge")
|
||||
return {"status": "success", "node": school_node}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating school node: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_private_database(self, school_data: Dict) -> Dict:
|
||||
"""Creates a private database for a specific school"""
|
||||
try:
|
||||
private_db_name = f"cc.institutes.{school_data['urn']}"
|
||||
with self.driver.session() as session:
|
||||
session_tools.create_database(session, private_db_name)
|
||||
self.logger.info(f"Created private database {private_db_name}")
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Database {private_db_name} created successfully"
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating private database: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_basic_structure(self, school_node: entities.SchoolNode, database_name: str) -> Dict:
|
||||
"""Creates basic structural nodes in the specified database"""
|
||||
try:
|
||||
# Create filesystem paths
|
||||
fs_handler = ClassroomCopilotFilesystem(database_name, init_run_type="school")
|
||||
|
||||
# Create Department Structure node
|
||||
department_structure_node_unique_id = f"DepartmentStructure_{school_node.unique_id}"
|
||||
_, department_path = fs_handler.create_school_department_directory(school_node.path, "departments")
|
||||
department_structure_node = entities.DepartmentStructureNode(
|
||||
unique_id=department_structure_node_unique_id,
|
||||
path=department_path
|
||||
)
|
||||
|
||||
# Create Curriculum Structure node
|
||||
_, curriculum_path = fs_handler.create_school_curriculum_directory(school_node.path)
|
||||
curriculum_node = curriculum_neo.CurriculumStructureNode(
|
||||
unique_id=f"CurriculumStructure_{school_node.unique_id}",
|
||||
path=curriculum_path
|
||||
)
|
||||
|
||||
# Create Pastoral Structure node
|
||||
_, pastoral_path = fs_handler.create_school_pastoral_directory(school_node.path)
|
||||
pastoral_node = curriculum_neo.PastoralStructureNode(
|
||||
unique_id=f"PastoralStructure_{school_node.unique_id}",
|
||||
path=pastoral_path
|
||||
)
|
||||
|
||||
with self.neontology as neo:
|
||||
# Create nodes
|
||||
neo.create_or_merge_node(department_structure_node, database=str(database_name), operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_structure_node.path, department_structure_node.to_dict())
|
||||
|
||||
neo.create_or_merge_node(curriculum_node, database=str(database_name), operation='merge')
|
||||
fs_handler.create_default_tldraw_file(curriculum_node.path, curriculum_node.to_dict())
|
||||
|
||||
neo.create_or_merge_node(pastoral_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(pastoral_node.path, pastoral_node.to_dict())
|
||||
|
||||
# Create relationships
|
||||
neo.create_or_merge_relationship(
|
||||
entity_relationships.SchoolHasDepartmentStructure(source=school_node, target=department_structure_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
neo.create_or_merge_relationship(
|
||||
entity_curriculum_rels.SchoolHasCurriculumStructure(source=school_node, target=curriculum_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
neo.create_or_merge_relationship(
|
||||
entity_curriculum_rels.SchoolHasPastoralStructure(source=school_node, target=pastoral_node),
|
||||
database=database_name, operation='merge'
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Basic structure created successfully",
|
||||
"nodes": {
|
||||
"department_structure": department_structure_node,
|
||||
"curriculum_structure": curriculum_node,
|
||||
"pastoral_structure": pastoral_node
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating basic structure: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def create_detailed_structure(self, school_node: entities.SchoolNode, database_name: str, excel_file: BinaryIO) -> Dict:
|
||||
"""Creates detailed structural nodes from Excel file"""
|
||||
try:
|
||||
# Store Excel file in Supabase
|
||||
file_path = f"{school_node.urn}/structure.xlsx"
|
||||
|
||||
# Upload Excel file
|
||||
self.storage.upload_file(
|
||||
bucket_id="cc.institutes",
|
||||
file_path=file_path,
|
||||
file_data=excel_file.read(),
|
||||
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
upsert=True
|
||||
)
|
||||
|
||||
# Process Excel file
|
||||
dataframes = pd.read_excel(excel_file, sheet_name=None)
|
||||
|
||||
# Get existing basic structure nodes
|
||||
with self.neontology as neo:
|
||||
result = neo.cypher_read("""
|
||||
MATCH (s:School {unique_id: $school_id})
|
||||
OPTIONAL MATCH (s)-[:HAS_DEPARTMENT_STRUCTURE]->(ds:DepartmentStructure)
|
||||
OPTIONAL MATCH (s)-[:HAS_CURRICULUM_STRUCTURE]->(cs:CurriculumStructure)
|
||||
OPTIONAL MATCH (s)-[:HAS_PASTORAL_STRUCTURE]->(ps:PastoralStructure)
|
||||
RETURN ds, cs, ps
|
||||
""", {"school_id": school_node.unique_id}, database=database_name)
|
||||
|
||||
if not result:
|
||||
raise Exception("Basic structure not found")
|
||||
|
||||
department_structure = result['ds']
|
||||
curriculum_structure = result['cs']
|
||||
pastoral_structure = result['ps']
|
||||
|
||||
# Create departments and subjects
|
||||
unique_departments = dataframes['keystagesyllabuses']['Department'].dropna().unique()
|
||||
|
||||
fs_handler = ClassroomCopilotFilesystem(database_name, init_run_type="school")
|
||||
node_library = {}
|
||||
|
||||
with self.neontology as neo:
|
||||
for department_name in unique_departments:
|
||||
_, department_path = fs_handler.create_school_department_directory(school_node.path, department_name)
|
||||
|
||||
department_node = entities.DepartmentNode(
|
||||
unique_id=f"Department_{school_node.unique_id}_{department_name.replace(' ', '_')}",
|
||||
department_name=department_name,
|
||||
path=department_path
|
||||
)
|
||||
neo.create_or_merge_node(department_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(department_node.path, department_node.to_dict())
|
||||
node_library[f'department_{department_name}'] = department_node
|
||||
|
||||
# Link to department structure
|
||||
neo.create_or_merge_relationship(
|
||||
entity_relationships.DepartmentStructureHasDepartment(
|
||||
source=department_structure,
|
||||
target=department_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
# Create year groups
|
||||
year_groups = self.sort_year_groups(dataframes['yeargroupsyllabuses'])['YearGroup'].unique()
|
||||
last_year_group_node = None
|
||||
|
||||
for year_group in year_groups:
|
||||
numeric_year_group = pd.to_numeric(year_group, errors='coerce')
|
||||
if pd.notna(numeric_year_group):
|
||||
_, year_group_path = fs_handler.create_pastoral_year_group_directory(
|
||||
pastoral_structure.path,
|
||||
str(int(numeric_year_group))
|
||||
)
|
||||
|
||||
year_group_node = curriculum_neo.YearGroupNode(
|
||||
unique_id=f"YearGroup_{school_node.unique_id}_YGrp{int(numeric_year_group)}",
|
||||
year_group=str(int(numeric_year_group)),
|
||||
year_group_name=f"Year {int(numeric_year_group)}",
|
||||
path=year_group_path
|
||||
)
|
||||
neo.create_or_merge_node(year_group_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(year_group_node.path, year_group_node.to_dict())
|
||||
node_library[f'year_group_{int(numeric_year_group)}'] = year_group_node
|
||||
|
||||
# Create sequential relationship
|
||||
if last_year_group_node:
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.YearGroupFollowsYearGroup(
|
||||
source=last_year_group_node,
|
||||
target=year_group_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
last_year_group_node = year_group_node
|
||||
|
||||
# Link to pastoral structure
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.PastoralStructureIncludesYearGroup(
|
||||
source=pastoral_structure,
|
||||
target=year_group_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
# Create key stages
|
||||
key_stages = dataframes['keystagesyllabuses']['KeyStage'].unique()
|
||||
last_key_stage_node = None
|
||||
|
||||
for key_stage in sorted(key_stages):
|
||||
_, key_stage_path = fs_handler.create_curriculum_key_stage_directory(
|
||||
curriculum_structure.path,
|
||||
str(key_stage)
|
||||
)
|
||||
|
||||
key_stage_node = curriculum_neo.KeyStageNode(
|
||||
unique_id=f"KeyStage_{curriculum_structure.unique_id}_KStg{key_stage}",
|
||||
key_stage_name=f"Key Stage {key_stage}",
|
||||
key_stage=str(key_stage),
|
||||
path=key_stage_path
|
||||
)
|
||||
neo.create_or_merge_node(key_stage_node, database=database_name, operation='merge')
|
||||
fs_handler.create_default_tldraw_file(key_stage_node.path, key_stage_node.to_dict())
|
||||
node_library[f'key_stage_{key_stage}'] = key_stage_node
|
||||
|
||||
# Create sequential relationship
|
||||
if last_key_stage_node:
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.KeyStageFollowsKeyStage(
|
||||
source=last_key_stage_node,
|
||||
target=key_stage_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
last_key_stage_node = key_stage_node
|
||||
|
||||
# Link to curriculum structure
|
||||
neo.create_or_merge_relationship(
|
||||
curriculum_relationships.CurriculumStructureIncludesKeyStage(
|
||||
source=curriculum_structure,
|
||||
target=key_stage_node
|
||||
),
|
||||
database=database_name,
|
||||
operation='merge'
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Detailed structure created successfully",
|
||||
"node_library": node_library
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating detailed structure: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def sort_year_groups(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Helper function to sort year groups numerically"""
|
||||
df = df.copy()
|
||||
df['YearGroupNumeric'] = pd.to_numeric(df['YearGroup'], errors='coerce')
|
||||
return df.sort_values(by='YearGroupNumeric')
|
||||
|
||||
def check_schools_database(self) -> Dict:
|
||||
"""Check if the schools database exists and has been initialized"""
|
||||
try:
|
||||
db_name = "cc.institutes"
|
||||
with self.driver.session() as session:
|
||||
# Check if database exists
|
||||
databases = session_tools.list_databases(session)
|
||||
if db_name not in databases:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"Database {db_name} does not exist"
|
||||
}
|
||||
|
||||
# Check if database has any nodes (indicating it's been initialized)
|
||||
session.run("USE " + db_name)
|
||||
result = session.run("MATCH (n) RETURN count(n) as count").single()
|
||||
node_count = result["count"] if result else 0
|
||||
|
||||
if node_count == 0:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"Database {db_name} exists but has no nodes"
|
||||
}
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Database {db_name} exists and has {node_count} nodes"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking schools database: {str(e)}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
@ -14,14 +14,13 @@ class CreateBucketOptions(TypedDict, total=False):
|
||||
allowed_mime_types: List[str]
|
||||
name: str
|
||||
|
||||
def _create_base_client(url: str, key: str, options: Optional[Dict[str, Any]] = None, access_token: Optional[str] = None) -> Client:
|
||||
def _create_base_client(url: str, key: str, options: Optional[Dict[str, Any]] = None) -> Client:
|
||||
"""Create a base Supabase client with given configuration."""
|
||||
client_options = SyncClientOptions(
|
||||
schema="public",
|
||||
storage=SyncMemoryStorage(),
|
||||
headers={
|
||||
"apikey": key,
|
||||
"Authorization": f"Bearer {access_token if access_token else key}"
|
||||
"Authorization": f"Bearer {key}"
|
||||
}
|
||||
)
|
||||
return create_client(url, key, options=client_options)
|
||||
@ -29,16 +28,16 @@ def _create_base_client(url: str, key: str, options: Optional[Dict[str, Any]] =
|
||||
class SupabaseServiceRoleClient:
|
||||
"""Supabase client for making authenticated requests using the service role key"""
|
||||
|
||||
def __init__(self, url: Optional[str] = None, service_role_key: Optional[str] = None, access_token: Optional[str] = None):
|
||||
def __init__(self, url: Optional[str] = None, service_role_key: Optional[str] = None):
|
||||
"""Initialize the Supabase client with URL and service role key"""
|
||||
self.url = url or os.environ.get("SUPABASE_URL", "http://kong:8000")
|
||||
self.url = url or os.environ.get("SUPABASE_URL", "http://localhost:8000")
|
||||
self.service_role_key = service_role_key or os.environ.get("SERVICE_ROLE_KEY")
|
||||
|
||||
if not self.url or not self.service_role_key:
|
||||
raise ValueError("SUPABASE_URL and SERVICE_ROLE_KEY must be provided")
|
||||
|
||||
# Initialize Supabase client with service role key and optional access token
|
||||
self.supabase = _create_base_client(self.url, self.service_role_key, access_token=access_token)
|
||||
self.supabase = _create_base_client(self.url, self.service_role_key)
|
||||
|
||||
def create_bucket(self, id: str, options: Optional[CreateBucketOptions] = None) -> Dict[str, Any]:
|
||||
"""Create a storage bucket with the given ID and options"""
|
||||
@ -48,17 +47,12 @@ class SupabaseServiceRoleClient:
|
||||
options['name'] = id # Use ID as default name if not provided
|
||||
return self.supabase.storage.create_bucket(id, options=options)
|
||||
|
||||
@classmethod
|
||||
def for_admin(cls, access_token: str) -> 'SupabaseServiceRoleClient':
|
||||
"""Create a client instance for the super admin using their access token"""
|
||||
return cls(access_token=access_token)
|
||||
|
||||
class SupabaseAnonClient:
|
||||
"""Supabase client for making authenticated requests using the anon key"""
|
||||
|
||||
def __init__(self, url: Optional[str] = None, anon_key: Optional[str] = None, access_token: Optional[str] = None):
|
||||
"""Initialize the Supabase client with URL and anon key"""
|
||||
self.url = url or os.environ.get("SUPABASE_URL", "http://kong:8000")
|
||||
self.url = url or os.environ.get("SUPABASE_URL", "http://localhost:8000")
|
||||
self.anon_key = anon_key or os.environ.get("ANON_KEY")
|
||||
|
||||
if not self.url or not self.anon_key:
|
||||
|
||||
@ -18,7 +18,6 @@ import re
|
||||
|
||||
class ClassroomCopilotFilesystem:
|
||||
def __init__(self, db_name: str, init_run_type: str = None):
|
||||
logging.info(f"Initializing ClassroomCopilotFilesystem with db_name: {db_name} and init_run_type: {init_run_type}")
|
||||
|
||||
self.db_name = db_name
|
||||
|
||||
@ -27,19 +26,21 @@ class ClassroomCopilotFilesystem:
|
||||
if not self.base_path:
|
||||
raise ValueError("NODE_FILESYSTEM_PATH environment variable not set")
|
||||
|
||||
logging.info(f"Initializing ClassroomCopilotFilesystem with db_name: {db_name} and init_run_type: {init_run_type} with base path: {self.base_path}")
|
||||
|
||||
# Set root path based on init type
|
||||
if init_run_type == "school":
|
||||
self.root_path = os.path.join(self.base_path, "schools", self.db_name)
|
||||
self.root_path = os.path.join(self.base_path, "schools")
|
||||
logging.debug(f"School root path: {self.root_path}")
|
||||
elif init_run_type == "user":
|
||||
self.root_path = os.path.join(self.base_path, "users", self.db_name)
|
||||
self.root_path = os.path.join(self.base_path, "users")
|
||||
logging.debug(f"User root path: {self.root_path}")
|
||||
elif init_run_type == "multiplayer":
|
||||
self.root_path = os.path.join(self.base_path, "multiplayer")
|
||||
logging.debug(f"Multiplayer root path: {self.root_path}")
|
||||
else:
|
||||
self.root_path = os.path.join(self.base_path, self.db_name)
|
||||
logging.debug(f"Default root path: {self.root_path}")
|
||||
self.root_path = self.base_path
|
||||
logging.debug(f"Database root path: {self.root_path}")
|
||||
|
||||
# Ensure root directory exists
|
||||
os.makedirs(self.root_path, exist_ok=True)
|
||||
@ -63,45 +64,33 @@ class ClassroomCopilotFilesystem:
|
||||
return True
|
||||
return False
|
||||
|
||||
def sanitize_username(self, username):
|
||||
return re.sub(r'[^\w\-_\.]', '_', username)
|
||||
|
||||
def create_user_directory(self, username, user_type=None, school_path=None):
|
||||
def create_private_user_directory(self, user_id):
|
||||
"""Create a directory for a specific user."""
|
||||
sanitized_username = self.sanitize_username(username)
|
||||
|
||||
if school_path:
|
||||
# For school database: /schools/[school_db]/users/[user_type]/[username]
|
||||
user_path = os.path.join(self.root_path, "users", user_type, sanitized_username)
|
||||
else:
|
||||
# For user database: /users/[user_db]/[username]
|
||||
user_path = os.path.join(self.root_path, sanitized_username)
|
||||
# For user database: /users/[user_db]/[username]
|
||||
user_path = os.path.join(self.root_path, user_id)
|
||||
|
||||
logging.info(f"Creating user directory at {user_path}")
|
||||
return self.create_directory(user_path), user_path
|
||||
|
||||
def create_user_worker_directory(self, user_path, worker_code):
|
||||
def create_user_worker_directory(self, user_path, worker_id, worker_type):
|
||||
"""Create a worker directory under the user directory."""
|
||||
# Create worker directory: [user_path]/[worker_code]
|
||||
worker_path = os.path.join(user_path, worker_code)
|
||||
worker_path = os.path.join(user_path, worker_type, worker_id)
|
||||
logging.info(f"Creating worker directory at {worker_path}")
|
||||
return self.create_directory(worker_path), worker_path
|
||||
|
||||
def create_school_worker_directory(self, school_path, worker_type):
|
||||
def create_school_worker_directory(self, school_path, worker_type, worker_id):
|
||||
"""Create a worker directory under the school directory."""
|
||||
worker_path = os.path.join(school_path, "workers", worker_type)
|
||||
logging.info(f"Creating school worker directory at {worker_path}")
|
||||
worker_path = os.path.join(school_path, "workers", worker_type, worker_id)
|
||||
logging.info(f"Creating school {worker_type} worker directory at {worker_path}")
|
||||
return self.create_directory(worker_path), worker_path
|
||||
|
||||
def create_school_directory(self, school_uuid=None):
|
||||
def create_school_directory(self, school_uuid_string):
|
||||
"""Create a directory for a specific school."""
|
||||
logging.info(f"Creating school directory with school_uuid: {school_uuid}")
|
||||
if school_uuid is None:
|
||||
logging.debug(f"School UUID is None, creating school directory at {self.root_path}")
|
||||
school_path = self.root_path
|
||||
else:
|
||||
logging.debug(f"School UUID is not None, creating school directory at {os.path.join(self.root_path, school_uuid)}")
|
||||
school_path = os.path.join(self.root_path, school_uuid)
|
||||
logging.info(f"Creating school directory with uuid_string: {school_uuid_string}")
|
||||
logging.debug(f"School UUID is not None, creating school directory at {os.path.join(self.root_path, school_uuid_string)}")
|
||||
school_path = os.path.join(self.root_path, school_uuid_string)
|
||||
return self.create_directory(school_path), school_path
|
||||
|
||||
def create_year_directory(self, year, calendar_path=None):
|
||||
@ -248,7 +237,12 @@ class ClassroomCopilotFilesystem:
|
||||
"""Create a directory for a specific topic under a year group syllabus."""
|
||||
topic_path = os.path.join(year_group_syllabus_path, "topics", f"{topic_id}")
|
||||
return self.create_directory(topic_path), topic_path
|
||||
|
||||
|
||||
def create_curriculum_keystage_topic_directory(self, keystage_syllabus_path, topic_id):
|
||||
"""Create a directory for a specific key stage topic under a key stage group syllabus."""
|
||||
topic_path = os.path.join(keystage_syllabus_path, "core_topics", f"{topic_id}")
|
||||
return self.create_directory(topic_path), topic_path
|
||||
|
||||
def create_curriculum_lesson_directory(self, topic_path, lesson_id):
|
||||
"""Create a directory for a specific lesson under a topic."""
|
||||
lesson_path = os.path.join(topic_path, "lessons", f"{lesson_id}")
|
||||
@ -276,285 +270,4 @@ class ClassroomCopilotFilesystem:
|
||||
|
||||
def create_teacher_planned_lesson_directory(self, class_path, lesson_id):
|
||||
planned_lesson_path = os.path.join(class_path, "planned_lessons", lesson_id)
|
||||
return self.create_directory(planned_lesson_path), planned_lesson_path
|
||||
|
||||
# TLDraw File Creation
|
||||
def create_default_tldraw_file(self, node_path, node_data):
|
||||
"""Create a tldraw file for a node."""
|
||||
logging.info(f"Creating tldraw file for node at {node_path}")
|
||||
|
||||
# Ensure the directory exists
|
||||
os.makedirs(node_path, exist_ok=True)
|
||||
|
||||
tldraw_path = os.path.join(node_path, 'tldraw_file.json')
|
||||
|
||||
# Create default tldraw content
|
||||
tldraw_content = {
|
||||
"document": {
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": "",
|
||||
"meta": {},
|
||||
"id": "document:document",
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page:page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
}
|
||||
},
|
||||
"schema":
|
||||
{"schemaVersion":2,
|
||||
"sequences": {
|
||||
"com.tldraw.store":4,
|
||||
"com.tldraw.asset":1,
|
||||
"com.tldraw.camera":1,
|
||||
"com.tldraw.document":2,
|
||||
"com.tldraw.instance":25,
|
||||
"com.tldraw.instance_page_state":5,
|
||||
"com.tldraw.page":1,
|
||||
"com.tldraw.instance_presence":5,
|
||||
"com.tldraw.pointer":1,
|
||||
"com.tldraw.shape":4,
|
||||
"com.tldraw.asset.bookmark":2,
|
||||
"com.tldraw.asset.image":5,
|
||||
"com.tldraw.asset.video":5,
|
||||
"com.tldraw.shape.arrow":5,
|
||||
"com.tldraw.shape.bookmark":2,
|
||||
"com.tldraw.shape.draw":2,
|
||||
"com.tldraw.shape.embed":4,
|
||||
"com.tldraw.shape.frame":0,
|
||||
"com.tldraw.shape.geo":9,
|
||||
"com.tldraw.shape.group":0,
|
||||
"com.tldraw.shape.highlight":1,
|
||||
"com.tldraw.shape.image":4,
|
||||
"com.tldraw.shape.line":5,
|
||||
"com.tldraw.shape.note":8,
|
||||
"com.tldraw.shape.text":2,
|
||||
"com.tldraw.shape.video":2,
|
||||
"com.tldraw.shape.youtube-embed":0,
|
||||
"com.tldraw.shape.calendar":0,
|
||||
"com.tldraw.shape.microphone":1,
|
||||
"com.tldraw.shape.transcriptionText":0,
|
||||
"com.tldraw.shape.slide":0,"com.tldraw.shape.slideshow":0,
|
||||
"com.tldraw.shape.user_node":1,
|
||||
"com.tldraw.shape.developer_node":1,
|
||||
"com.tldraw.shape.student_node":1,
|
||||
"com.tldraw.shape.teacher_node":1,
|
||||
"com.tldraw.shape.calendar_node":1,
|
||||
"com.tldraw.shape.calendar_year_node":1,
|
||||
"com.tldraw.shape.calendar_month_node":1,
|
||||
"com.tldraw.shape.calendar_week_node":1,
|
||||
"com.tldraw.shape.calendar_day_node":1,
|
||||
"com.tldraw.shape.calendar_time_chunk_node":1,
|
||||
"com.tldraw.shape.teacher_timetable_node":1,
|
||||
"com.tldraw.shape.timetable_lesson_node":1,
|
||||
"com.tldraw.shape.planned_lesson_node":1,
|
||||
"com.tldraw.shape.pastoral_structure_node":1,
|
||||
"com.tldraw.shape.year_group_node":1,
|
||||
"com.tldraw.shape.curriculum_structure_node":1,
|
||||
"com.tldraw.shape.key_stage_node":1,
|
||||
"com.tldraw.shape.key_stage_syllabus_node":1,
|
||||
"com.tldraw.shape.year_group_syllabus_node":1,
|
||||
"com.tldraw.shape.subject_node":1,
|
||||
"com.tldraw.shape.topic_node":1,
|
||||
"com.tldraw.shape.topic_lesson_node":1,
|
||||
"com.tldraw.shape.learning_statement_node":1,
|
||||
"com.tldraw.shape.science_lab_node":1,
|
||||
"com.tldraw.shape.school_timetable_node":1,
|
||||
"com.tldraw.shape.academic_year_node":1,
|
||||
"com.tldraw.shape.academic_term_node":1,
|
||||
"com.tldraw.shape.academic_week_node":1,
|
||||
"com.tldraw.shape.academic_day_node":1,
|
||||
"com.tldraw.shape.academic_period_node":1,
|
||||
"com.tldraw.shape.registration_period_node":1,
|
||||
"com.tldraw.shape.school_node":1,
|
||||
"com.tldraw.shape.department_node":1,
|
||||
"com.tldraw.shape.room_node":1,
|
||||
"com.tldraw.shape.subject_class_node":1,
|
||||
"com.tldraw.shape.general_relationship":1,
|
||||
"com.tldraw.binding.arrow":0,
|
||||
"com.tldraw.binding.slide-layout":0
|
||||
}
|
||||
},
|
||||
"recordVersions": {
|
||||
"asset": { "version": 1, "subTypeKey": "type", "subTypeVersions": {} },
|
||||
"camera": { "version": 1 },
|
||||
"document": { "version": 2 },
|
||||
"instance": { "version": 21 },
|
||||
"instance_page_state": { "version": 5 },
|
||||
"page": { "version": 1 },
|
||||
"shape": { "version": 3, "subTypeKey": "type", "subTypeVersions": {} },
|
||||
"instance_presence": { "version": 5 },
|
||||
"pointer": { "version": 1 }
|
||||
},
|
||||
"rootShapeIds":[],
|
||||
"bindings":[],
|
||||
"assets":[]
|
||||
},
|
||||
"session": {
|
||||
"version": 0,
|
||||
"currentPageId": "page:page",
|
||||
"pageStates": [{
|
||||
"pageId": "page:page",
|
||||
"camera": {"x": 0, "y": 0, "z": 1},
|
||||
"selectedShapeIds": []
|
||||
}]
|
||||
},
|
||||
"node_data": node_data
|
||||
}
|
||||
|
||||
with open(tldraw_path, 'w') as f:
|
||||
json.dump(tldraw_content, f, indent=4)
|
||||
|
||||
logging.info(f"tldraw file created at {tldraw_path}")
|
||||
return tldraw_path
|
||||
|
||||
def create_default_tldraw_file_in_storage(self, admin_supabase, bucket_id, file_path, node_data):
|
||||
"""Create a tldraw file in Supabase storage."""
|
||||
logging.info(f"Creating tldraw file in storage at {file_path}")
|
||||
|
||||
# Create default tldraw content
|
||||
tldraw_content = {
|
||||
"document": {
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": "",
|
||||
"meta": {},
|
||||
"id": "document:document",
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page:page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
}
|
||||
},
|
||||
"schema":
|
||||
{"schemaVersion":2,
|
||||
"sequences": {
|
||||
"com.tldraw.store":4,
|
||||
"com.tldraw.asset":1,
|
||||
"com.tldraw.camera":1,
|
||||
"com.tldraw.document":2,
|
||||
"com.tldraw.instance":25,
|
||||
"com.tldraw.instance_page_state":5,
|
||||
"com.tldraw.page":1,
|
||||
"com.tldraw.instance_presence":5,
|
||||
"com.tldraw.pointer":1,
|
||||
"com.tldraw.shape":4,
|
||||
"com.tldraw.asset.bookmark":2,
|
||||
"com.tldraw.asset.image":5,
|
||||
"com.tldraw.asset.video":5,
|
||||
"com.tldraw.shape.arrow":5,
|
||||
"com.tldraw.shape.bookmark":2,
|
||||
"com.tldraw.shape.draw":2,
|
||||
"com.tldraw.shape.embed":4,
|
||||
"com.tldraw.shape.frame":0,
|
||||
"com.tldraw.shape.geo":9,
|
||||
"com.tldraw.shape.group":0,
|
||||
"com.tldraw.shape.highlight":1,
|
||||
"com.tldraw.shape.image":4,
|
||||
"com.tldraw.shape.line":5,
|
||||
"com.tldraw.shape.note":8,
|
||||
"com.tldraw.shape.text":2,
|
||||
"com.tldraw.shape.video":2,
|
||||
"com.tldraw.shape.youtube-embed":0,
|
||||
"com.tldraw.shape.calendar":0,
|
||||
"com.tldraw.shape.microphone":1,
|
||||
"com.tldraw.shape.transcriptionText":0,
|
||||
"com.tldraw.shape.slide":0,"com.tldraw.shape.slideshow":0,
|
||||
"com.tldraw.shape.user_node":1,
|
||||
"com.tldraw.shape.developer_node":1,
|
||||
"com.tldraw.shape.student_node":1,
|
||||
"com.tldraw.shape.teacher_node":1,
|
||||
"com.tldraw.shape.calendar_node":1,
|
||||
"com.tldraw.shape.calendar_year_node":1,
|
||||
"com.tldraw.shape.calendar_month_node":1,
|
||||
"com.tldraw.shape.calendar_week_node":1,
|
||||
"com.tldraw.shape.calendar_day_node":1,
|
||||
"com.tldraw.shape.calendar_time_chunk_node":1,
|
||||
"com.tldraw.shape.teacher_timetable_node":1,
|
||||
"com.tldraw.shape.timetable_lesson_node":1,
|
||||
"com.tldraw.shape.planned_lesson_node":1,
|
||||
"com.tldraw.shape.pastoral_structure_node":1,
|
||||
"com.tldraw.shape.year_group_node":1,
|
||||
"com.tldraw.shape.curriculum_structure_node":1,
|
||||
"com.tldraw.shape.key_stage_node":1,
|
||||
"com.tldraw.shape.key_stage_syllabus_node":1,
|
||||
"com.tldraw.shape.year_group_syllabus_node":1,
|
||||
"com.tldraw.shape.subject_node":1,
|
||||
"com.tldraw.shape.topic_node":1,
|
||||
"com.tldraw.shape.topic_lesson_node":1,
|
||||
"com.tldraw.shape.learning_statement_node":1,
|
||||
"com.tldraw.shape.science_lab_node":1,
|
||||
"com.tldraw.shape.school_timetable_node":1,
|
||||
"com.tldraw.shape.academic_year_node":1,
|
||||
"com.tldraw.shape.academic_term_node":1,
|
||||
"com.tldraw.shape.academic_week_node":1,
|
||||
"com.tldraw.shape.academic_day_node":1,
|
||||
"com.tldraw.shape.academic_period_node":1,
|
||||
"com.tldraw.shape.registration_period_node":1,
|
||||
"com.tldraw.shape.school_node":1,
|
||||
"com.tldraw.shape.department_node":1,
|
||||
"com.tldraw.shape.room_node":1,
|
||||
"com.tldraw.shape.subject_class_node":1,
|
||||
"com.tldraw.shape.general_relationship":1,
|
||||
"com.tldraw.binding.arrow":0,
|
||||
"com.tldraw.binding.slide-layout":0
|
||||
}
|
||||
},
|
||||
"recordVersions": {
|
||||
"asset": { "version": 1, "subTypeKey": "type", "subTypeVersions": {} },
|
||||
"camera": { "version": 1 },
|
||||
"document": { "version": 2 },
|
||||
"instance": { "version": 21 },
|
||||
"instance_page_state": { "version": 5 },
|
||||
"page": { "version": 1 },
|
||||
"shape": { "version": 3, "subTypeKey": "type", "subTypeVersions": {} },
|
||||
"instance_presence": { "version": 5 },
|
||||
"pointer": { "version": 1 }
|
||||
},
|
||||
"rootShapeIds":[],
|
||||
"bindings":[],
|
||||
"assets":[]
|
||||
},
|
||||
"session": {
|
||||
"version": 0,
|
||||
"currentPageId": "page:page",
|
||||
"pageStates": [{
|
||||
"pageId": "page:page",
|
||||
"camera": {"x": 0, "y": 0, "z": 1},
|
||||
"selectedShapeIds": []
|
||||
}]
|
||||
},
|
||||
"node_data": node_data
|
||||
}
|
||||
|
||||
# Convert the content to JSON string
|
||||
tldraw_json = json.dumps(tldraw_content, indent=4)
|
||||
|
||||
try:
|
||||
# Upload the file to Supabase storage
|
||||
result = admin_supabase.storage.from_(bucket_id).upload(
|
||||
path=file_path,
|
||||
file=tldraw_json,
|
||||
file_options={"content-type": "application/json"}
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
logging.error(f"Error creating tldraw file in storage: {result['error']}")
|
||||
raise Exception(f"Failed to create tldraw file: {result['error']}")
|
||||
|
||||
logging.info(f"tldraw file created in storage at {file_path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating tldraw file in storage: {str(e)}")
|
||||
raise e
|
||||
return self.create_directory(planned_lesson_path), planned_lesson_path
|
||||
@ -13,8 +13,8 @@ def get_static_nodes(context: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
query = """
|
||||
MATCH (t:Teacher)
|
||||
RETURN DISTINCT {
|
||||
id: t.unique_id,
|
||||
path: t.path,
|
||||
id: t.uuid_string,
|
||||
path: t.node_storage_path,
|
||||
label: t.teacher_name_formal,
|
||||
type: 'Teacher',
|
||||
isStatic: true,
|
||||
@ -24,8 +24,8 @@ def get_static_nodes(context: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
UNION ALL
|
||||
MATCH (t:UserTeacherTimetable)
|
||||
RETURN DISTINCT {
|
||||
id: t.unique_id,
|
||||
path: t.path,
|
||||
id: t.uuid_string,
|
||||
path: t.node_storage_path,
|
||||
label: t.name,
|
||||
type: 'UserTeacherTimetable',
|
||||
isStatic: true,
|
||||
@ -35,8 +35,8 @@ def get_static_nodes(context: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
UNION ALL
|
||||
MATCH (t:UserTeacherTimetable)-[:HAS_CLASS]->(c:Class)
|
||||
RETURN DISTINCT {
|
||||
id: c.unique_id,
|
||||
path: c.path,
|
||||
id: c.uuid_string,
|
||||
path: c.node_storage_path,
|
||||
label: c.name,
|
||||
type: 'Class',
|
||||
isStatic: true,
|
||||
@ -49,8 +49,8 @@ def get_static_nodes(context: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
query = """
|
||||
MATCH (u:User)
|
||||
RETURN DISTINCT {
|
||||
id: u.unique_id,
|
||||
path: u.path,
|
||||
id: u.uuid_string,
|
||||
path: u.node_storage_path,
|
||||
label: u.user_name,
|
||||
type: 'User',
|
||||
isStatic: true,
|
||||
@ -70,8 +70,8 @@ def get_static_nodes(context: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
ELSE 1
|
||||
END as nodeOrder
|
||||
RETURN DISTINCT {
|
||||
id: n.unique_id,
|
||||
path: n.path,
|
||||
id: n.uuid_string,
|
||||
path: n.node_storage_path,
|
||||
label: n.name,
|
||||
type: 'Calendar',
|
||||
isStatic: true,
|
||||
@ -98,7 +98,7 @@ def get_today_calendar_node(db_name: str) -> Optional[Dict[str, Any]]:
|
||||
query = """
|
||||
MATCH (n:Calendar)
|
||||
WHERE date($today) >= date(n.start_date) AND date($today) <= date(n.end_date)
|
||||
RETURN n.unique_id as id, n.path as path, n.name as label,
|
||||
RETURN n.uuid_string as id, n.path as path, n.name as label,
|
||||
'Calendar' as type
|
||||
LIMIT 1
|
||||
"""
|
||||
@ -117,7 +117,7 @@ def get_relative_calendar_node(day_offset: int, db_name: str) -> Optional[Dict[s
|
||||
query = """
|
||||
MATCH (n:Calendar)
|
||||
WHERE date($target_date) >= date(n.start_date) AND date($target_date) <= date(n.end_date)
|
||||
RETURN n.unique_id as id, n.path as path, n.name as label,
|
||||
RETURN n.uuid_string as id, n.node_storage_path as path, n.name as label,
|
||||
'Calendar' as type
|
||||
LIMIT 1
|
||||
"""
|
||||
@ -136,7 +136,7 @@ def get_next_month_node(db_name: str) -> Optional[Dict[str, Any]]:
|
||||
query = """
|
||||
MATCH (n:Calendar)
|
||||
WHERE date($next_month_start) >= date(n.start_date) AND date($next_month_start) <= date(n.end_date)
|
||||
RETURN n.unique_id as id, n.path as path, n.name as label,
|
||||
RETURN n.uuid_string as id, n.node_storage_path as path, n.name as label,
|
||||
'Calendar' as type
|
||||
LIMIT 1
|
||||
"""
|
||||
@ -155,7 +155,7 @@ def get_previous_month_node(db_name: str) -> Optional[Dict[str, Any]]:
|
||||
query = """
|
||||
MATCH (n:Calendar)
|
||||
WHERE date($prev_month_start) >= date(n.start_date) AND date($prev_month_start) <= date(n.end_date)
|
||||
RETURN n.unique_id as id, n.path as path, n.name as label,
|
||||
RETURN n.uuid_string as id, n.node_storage_path as path, n.name as label,
|
||||
'Calendar' as type
|
||||
LIMIT 1
|
||||
"""
|
||||
@ -172,7 +172,7 @@ def get_user_timetables(db_name: str) -> List[Dict[str, Any]]:
|
||||
"""Get user's timetables."""
|
||||
query = """
|
||||
MATCH (t:UserTeacherTimetable)
|
||||
RETURN t.unique_id as id, t.path as path, t.name as label,
|
||||
RETURN t.uuid_string as id, t.node_storage_path as path, t.name as label,
|
||||
'UserTeacherTimetable' as type
|
||||
"""
|
||||
try:
|
||||
@ -186,8 +186,8 @@ def get_user_timetables(db_name: str) -> List[Dict[str, Any]]:
|
||||
def get_timetable_classes(timetable_id: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
"""Get classes for a timetable."""
|
||||
query = """
|
||||
MATCH (t:UserTeacherTimetable {unique_id: $timetable_id})-[:HAS_CLASS]->(c:Class)
|
||||
RETURN c.unique_id as id, c.path as path, c.name as label,
|
||||
MATCH (t:UserTeacherTimetable {uuid_string: $timetable_id})-[:HAS_CLASS]->(c:Class)
|
||||
RETURN c.uuid_string as id, c.node_storage_path as path, c.name as label,
|
||||
'Class' as type
|
||||
"""
|
||||
try:
|
||||
@ -202,9 +202,9 @@ def get_next_lesson(class_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get next lesson for a class."""
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
query = """
|
||||
MATCH (c:Class {unique_id: $class_id})-[:HAS_LESSON]->(l:Lesson)
|
||||
MATCH (c:Class {uuid_string: $class_id})-[:HAS_LESSON]->(l:Lesson)
|
||||
WHERE l.start_time > $now
|
||||
RETURN l.unique_id as id, l.path as path, l.name as label,
|
||||
RETURN l.uuid_string as id, l.node_storage_path as path, l.name as label,
|
||||
'Lesson' as type
|
||||
ORDER BY l.start_time ASC
|
||||
LIMIT 1
|
||||
@ -222,9 +222,9 @@ def get_previous_lesson(class_id: str, db_name: str) -> Optional[Dict[str, Any]]
|
||||
"""Get previous lesson for a class."""
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
query = """
|
||||
MATCH (c:Class {unique_id: $class_id})-[:HAS_LESSON]->(l:Lesson)
|
||||
MATCH (c:Class {uuid_string: $class_id})-[:HAS_LESSON]->(l:Lesson)
|
||||
WHERE l.start_time < $now
|
||||
RETURN l.unique_id as id, l.path as path, l.name as label,
|
||||
RETURN l.uuid_string as id, l.path as path, l.name as label,
|
||||
'Lesson' as type
|
||||
ORDER BY l.start_time DESC
|
||||
LIMIT 1
|
||||
@ -251,20 +251,20 @@ def save_shared_snapshot(path: str, room_id: str, snapshot: Dict[str, Any]) -> b
|
||||
def get_connected_nodes_for_workers(node_id: str, db_name: str) -> List[Dict[str, Any]]:
|
||||
"""Get connected nodes specific to the workers context."""
|
||||
query = """
|
||||
MATCH (n {unique_id: $node_id})
|
||||
MATCH (n {uuid_string: $node_id})
|
||||
WITH n
|
||||
CALL {
|
||||
WITH n
|
||||
MATCH (n:UserTeacherTimetable)-[:HAS_CLASS]->(c:Class)
|
||||
RETURN c.unique_id as id, c.path as path, c.name as label,
|
||||
RETURN c.uuid_string as id, c.node_storage_path as path, c.name as label,
|
||||
'Class' as type
|
||||
UNION
|
||||
MATCH (n:Class)<-[:HAS_CLASS]-(t:UserTeacherTimetable)
|
||||
RETURN t.unique_id as id, t.path as path, t.name as label,
|
||||
RETURN t.uuid_string as id, t.node_storage_path as path, t.name as label,
|
||||
'UserTeacherTimetable' as type
|
||||
UNION
|
||||
MATCH (n:Class)-[:HAS_LESSON]->(l:Lesson)
|
||||
RETURN l.unique_id as id, l.path as path, l.name as label,
|
||||
RETURN l.uuid_string as id, l.node_storage_path as path, l.name as label,
|
||||
'Lesson' as type
|
||||
}
|
||||
RETURN DISTINCT id, path, label, type
|
||||
@ -284,8 +284,8 @@ def get_connected_nodes(node_id: str, db_name: str, context: str = None) -> List
|
||||
|
||||
# Default query for other contexts
|
||||
query = """
|
||||
MATCH (n {unique_id: $node_id})-[r]-(connected)
|
||||
RETURN DISTINCT connected.unique_id as id, connected.path as path,
|
||||
MATCH (n {uuid_string: $node_id})-[r]-(connected)
|
||||
RETURN DISTINCT connected.uuid_string as id, connected.path as path,
|
||||
connected.name as label, labels(connected)[0] as type
|
||||
"""
|
||||
try:
|
||||
@ -314,37 +314,37 @@ def get_worker_structure(db_name: str) -> Dict[str, Any]:
|
||||
// Collect all nodes
|
||||
RETURN {
|
||||
schools: collect(DISTINCT {
|
||||
id: s.unique_id,
|
||||
path: s.path,
|
||||
id: s.uuid_string,
|
||||
path: s.node_storage_path,
|
||||
name: s.school_name,
|
||||
__primarylabel__: 'School'
|
||||
}),
|
||||
departments: collect(DISTINCT {
|
||||
id: d.unique_id,
|
||||
path: d.path,
|
||||
id: d.uuid_string,
|
||||
path: d.node_storage_path,
|
||||
code: d.department_code,
|
||||
school_id: s.unique_id,
|
||||
school_id: s.uuid_string,
|
||||
__primarylabel__: 'Department'
|
||||
}),
|
||||
timetables: collect(DISTINCT {
|
||||
id: t.unique_id,
|
||||
path: t.path,
|
||||
id: t.uuid_string,
|
||||
path: t.node_storage_path,
|
||||
name: t.name,
|
||||
department_id: d.unique_id,
|
||||
department_id: d.uuid_string,
|
||||
__primarylabel__: 'UserTeacherTimetable'
|
||||
}),
|
||||
classes: collect(DISTINCT {
|
||||
id: c.unique_id,
|
||||
path: c.path,
|
||||
id: c.uuid_string,
|
||||
path: c.node_storage_path,
|
||||
code: c.class_code,
|
||||
timetable_id: t.unique_id,
|
||||
timetable_id: t.uuid_string,
|
||||
__primarylabel__: 'Class'
|
||||
}),
|
||||
lessons: collect(DISTINCT {
|
||||
id: l.unique_id,
|
||||
path: l.path,
|
||||
id: l.uuid_string,
|
||||
path: l.node_storage_path,
|
||||
start_time: l.start_time,
|
||||
class_id: c.unique_id,
|
||||
class_id: c.uuid_string,
|
||||
__primarylabel__: 'TimetableLesson'
|
||||
})
|
||||
} as structure
|
||||
@ -369,10 +369,10 @@ def get_worker_structure(db_name: str) -> Dict[str, Any]:
|
||||
def get_school_node(school_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific school node."""
|
||||
query = """
|
||||
MATCH (s:School {unique_id: $school_id})
|
||||
MATCH (s:School {uuid_string: $school_id})
|
||||
RETURN {
|
||||
id: s.unique_id,
|
||||
path: s.path,
|
||||
id: s.uuid_string,
|
||||
path: s.node_storage_path,
|
||||
name: s.school_name,
|
||||
__primarylabel__: 'School'
|
||||
} as node
|
||||
@ -389,10 +389,10 @@ def get_school_node(school_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
def get_department_node(dept_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific department node."""
|
||||
query = """
|
||||
MATCH (d:Department {unique_id: $dept_id})
|
||||
MATCH (d:Department {uuid_string: $dept_id})
|
||||
RETURN {
|
||||
id: d.unique_id,
|
||||
path: d.path,
|
||||
id: d.uuid_string,
|
||||
path: d.node_storage_path,
|
||||
code: d.department_code,
|
||||
__primarylabel__: 'Department'
|
||||
} as node
|
||||
@ -409,10 +409,10 @@ def get_department_node(dept_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
def get_timetable_node(timetable_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific timetable node."""
|
||||
query = """
|
||||
MATCH (t:UserTeacherTimetable {unique_id: $timetable_id})
|
||||
MATCH (t:UserTeacherTimetable {uuid_string: $timetable_id})
|
||||
RETURN {
|
||||
id: t.unique_id,
|
||||
path: t.path,
|
||||
id: t.uuid_string,
|
||||
path: t.node_storage_path,
|
||||
name: t.name,
|
||||
__primarylabel__: 'UserTeacherTimetable'
|
||||
} as node
|
||||
@ -429,10 +429,10 @@ def get_timetable_node(timetable_id: str, db_name: str) -> Optional[Dict[str, An
|
||||
def get_class_node(class_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific class node."""
|
||||
query = """
|
||||
MATCH (c:Class {unique_id: $class_id})
|
||||
MATCH (c:Class {uuid_string: $class_id})
|
||||
RETURN {
|
||||
id: c.unique_id,
|
||||
path: c.path,
|
||||
id: c.uuid_string,
|
||||
path: c.node_storage_path,
|
||||
code: c.class_code,
|
||||
__primarylabel__: 'Class'
|
||||
} as node
|
||||
@ -449,10 +449,10 @@ def get_class_node(class_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
def get_lesson_node(lesson_id: str, db_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific lesson node."""
|
||||
query = """
|
||||
MATCH (l:TimetableLesson {unique_id: $lesson_id})
|
||||
MATCH (l:TimetableLesson {uuid_string: $lesson_id})
|
||||
RETURN {
|
||||
id: l.unique_id,
|
||||
path: l.path,
|
||||
id: l.uuid_string,
|
||||
path: l.node_storage_path,
|
||||
start_time: l.start_time,
|
||||
__primarylabel__: 'TimetableLesson'
|
||||
} as node
|
||||
@ -473,8 +473,8 @@ def get_current_lesson(db_name: str) -> Optional[Dict[str, Any]]:
|
||||
MATCH (l:TimetableLesson)
|
||||
WHERE l.start_time >= $now
|
||||
RETURN {
|
||||
id: l.unique_id,
|
||||
path: l.path,
|
||||
id: l.uuid_string,
|
||||
path: l.node_storage_path,
|
||||
start_time: l.start_time,
|
||||
__primarylabel__: 'TimetableLesson'
|
||||
} as node
|
||||
|
||||
@ -15,8 +15,6 @@ logging = logger.get_logger(
|
||||
import requests
|
||||
import base64
|
||||
|
||||
dev_mode = os.getenv('DEV_MODE', 'false')
|
||||
|
||||
def send_query(query, encoded_credentials=None, params=None, method='POST', database="system", endpoint="/tx/commit"):
|
||||
if encoded_credentials is None:
|
||||
logging.debug(f"Sending query to Neo4j: {query}")
|
||||
|
||||
@ -15,16 +15,16 @@ logging = logger.get_logger(
|
||||
import modules.database.tools.queries as query
|
||||
from contextlib import suppress
|
||||
|
||||
def get_node_by_unique_id_and_adjacent_nodes(session, unique_id):
|
||||
return session.read_transaction(_get_node_by_unique_id_and_adjacent_nodes, unique_id)
|
||||
def get_node_by_uuid_string_and_adjacent_nodes(session, uuid_string):
|
||||
return session.read_transaction(_get_node_by_uuid_string_and_adjacent_nodes, uuid_string)
|
||||
|
||||
def _get_node_by_unique_id_and_adjacent_nodes(tx, unique_id):
|
||||
def _get_node_by_uuid_string_and_adjacent_nodes(tx, uuid_string):
|
||||
query = """
|
||||
MATCH (n {unique_id: $unique_id})
|
||||
MATCH (n {uuid_string: $uuid_string})
|
||||
OPTIONAL MATCH (n)-[r]-(adjacent)
|
||||
RETURN n AS node, COLLECT(DISTINCT {node: adjacent, relationship: r}) AS connected_nodes
|
||||
"""
|
||||
result = tx.run(query, unique_id=unique_id)
|
||||
result = tx.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
node = record["node"]
|
||||
@ -242,20 +242,20 @@ def _find_nodes_by_label(tx, label):
|
||||
result = tx.run(query)
|
||||
return [record["n"] for record in result]
|
||||
|
||||
def get_node_by_unique_id(session, unique_id):
|
||||
return session.read_transaction(_get_node_by_unique_id, unique_id)
|
||||
def get_node_by_uuid_string(session, uuid_string):
|
||||
return session.read_transaction(_get_node_by_uuid_string, uuid_string)
|
||||
|
||||
def _get_node_by_unique_id(tx, unique_id):
|
||||
def _get_node_by_uuid_string(tx, uuid_string):
|
||||
query = f"""
|
||||
MATCH (n)
|
||||
WHERE n.unique_id = $unique_id
|
||||
WHERE n.uuid_string = $uuid_string
|
||||
RETURN n
|
||||
"""
|
||||
logging.debug(f"Executing query with unique_id: {unique_id}")
|
||||
result = tx.run(query, unique_id=unique_id)
|
||||
logging.debug(f"Executing query with uuid_string: {uuid_string}")
|
||||
result = tx.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record is None:
|
||||
logging.warning(f"No node found with unique_id: {unique_id}")
|
||||
logging.warning(f"No node found with uuid_string: {uuid_string}")
|
||||
return None
|
||||
return record[0]
|
||||
|
||||
|
||||
@ -70,11 +70,10 @@ class BaseNode(CommonModel): # pyre-ignore[13]
|
||||
|
||||
def merge(self, database: str = 'neo4j') -> None:
|
||||
"""Merge this node into the graph."""
|
||||
|
||||
|
||||
params = self._get_merge_parameters()
|
||||
|
||||
all_labels = [self.__primarylabel__] + self.__secondarylabels__
|
||||
|
||||
|
||||
cypher = f"""
|
||||
MERGE (n:{":".join(all_labels)} {{ {self.__primaryproperty__}: $pp }})
|
||||
ON MATCH SET n += $set_on_match
|
||||
@ -82,10 +81,15 @@ class BaseNode(CommonModel): # pyre-ignore[13]
|
||||
SET n += $always_set
|
||||
RETURN n
|
||||
"""
|
||||
|
||||
|
||||
print(f"DEBUG: Executing merge query: {cypher}")
|
||||
print(f"DEBUG: With params: {params}")
|
||||
print(f"DEBUG: Database: {database}")
|
||||
|
||||
graph = GraphConnection()
|
||||
with graph.driver.session(database=database) as session:
|
||||
result = session.run(cypher, params).single()
|
||||
print(f"DEBUG: Merge result: {result}")
|
||||
if result:
|
||||
return self.__class__(**dict(result["n"]))
|
||||
return None
|
||||
|
||||
@ -60,14 +60,20 @@ def create_or_merge_neontology_node(node: BaseNode, database: str = 'neo4j', ope
|
||||
operation (str): The operation to perform ('create' or 'merge'). Defaults to 'merge'.
|
||||
"""
|
||||
try:
|
||||
logging.debug(f"Creating/merging node: {node.__class__.__name__} with label '{node.__primarylabel__}' in database '{database}'")
|
||||
logging.debug(f"Node data: {node.to_dict()}")
|
||||
|
||||
if operation == "create":
|
||||
node.create(database=database)
|
||||
result = node.create(database=database)
|
||||
logging.debug(f"Create result: {result}")
|
||||
elif operation == "merge":
|
||||
node.merge(database=database)
|
||||
result = node.merge(database=database)
|
||||
logging.debug(f"Merge result: {result}")
|
||||
else:
|
||||
logging.error(f"Invalid operation: {operation}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error in processing node: {e}")
|
||||
raise # Re-raise to see the actual error
|
||||
|
||||
# Create or merge a Neontology node in the Neo4j database. If a ValidationError occurs
|
||||
# due to a NaN value, replace it with a default value and retry.
|
||||
|
||||
451
modules/database/tools/supabase_storage_tools.py
Normal file
451
modules/database/tools/supabase_storage_tools.py
Normal file
@ -0,0 +1,451 @@
|
||||
"""
|
||||
Supabase Storage Tools for ClassroomCopilot
|
||||
Replaces local filesystem paths with Supabase Storage bucket paths
|
||||
"""
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
from typing import Tuple, Optional
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
class SupabaseStorageTools:
|
||||
"""
|
||||
Generates Supabase Storage paths for TLDraw snapshots and other files.
|
||||
|
||||
Path format: bucket/nodetype/node_unique_id
|
||||
Example: cc.snapshots/User/fda8dca7-4d18-43c9-bb74-260777043447
|
||||
"""
|
||||
|
||||
def __init__(self, db_name: str, init_run_type: str = None):
|
||||
self.db_name = db_name
|
||||
self.init_run_type = init_run_type
|
||||
|
||||
# Define bucket mappings based on node types
|
||||
self.bucket_mappings = {
|
||||
'User': 'cc.public.snapshots',
|
||||
'Teacher': 'cc.public.snapshots',
|
||||
'Student': 'cc.public.snapshots',
|
||||
'School': 'cc.public.snapshots',
|
||||
'Department': 'cc.public.snapshots',
|
||||
'Subject': 'cc.public.snapshots',
|
||||
'CalendarYear': 'cc.public.snapshots',
|
||||
'CalendarMonth': 'cc.public.snapshots',
|
||||
'CalendarWeek': 'cc.public.snapshots',
|
||||
'CalendarDay': 'cc.public.snapshots',
|
||||
'CalendarTimeChunk': 'cc.public.snapshots',
|
||||
'KeyStage': 'cc.public.snapshots',
|
||||
'YearGroup': 'cc.public.snapshots',
|
||||
'KeyStageSyllabus': 'cc.public.snapshots',
|
||||
'YearGroupSyllabus': 'cc.public.snapshots',
|
||||
'Topic': 'cc.public.snapshots',
|
||||
'TopicLesson': 'cc.public.snapshots',
|
||||
'LearningStatement': 'cc.public.snapshots',
|
||||
'UserTeacherTimetable': 'cc.public.snapshots',
|
||||
'Class': 'cc.public.snapshots',
|
||||
'TimetableLesson': 'cc.public.snapshots',
|
||||
'SuperAdmin': 'cc.public.snapshots',
|
||||
'Developer': 'cc.public.snapshots',
|
||||
'CurriculumStructure': 'cc.public.snapshots',
|
||||
'PastoralStructure': 'cc.public.snapshots',
|
||||
'DepartmentStructure': 'cc.public.snapshots',
|
||||
}
|
||||
|
||||
logger.info(f"Initializing SupabaseStorageTools with db_name: {db_name} and init_run_type: {init_run_type}")
|
||||
|
||||
def get_storage_path(self, node_type: str, node_id: str) -> str:
|
||||
"""
|
||||
Generate Supabase Storage path for a node.
|
||||
|
||||
Args:
|
||||
node_type: The type of node (e.g., 'User', 'Teacher', 'School')
|
||||
node_id: The unique identifier for the node
|
||||
|
||||
Returns:
|
||||
str: Storage path in format bucket/nodetype/node_id
|
||||
"""
|
||||
bucket = self.bucket_mappings.get(node_type, 'cc.public.snapshots')
|
||||
path = f"{bucket}/{node_type}/{node_id}"
|
||||
|
||||
logger.debug(f"Generated storage path for {node_type} {node_id}: {path}")
|
||||
return path
|
||||
|
||||
def create_user_storage_path(self, user_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a user node.
|
||||
|
||||
Args:
|
||||
user_id: The user's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('User', user_id)
|
||||
logger.info(f"Created user storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_teacher_storage_path(self, teacher_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a teacher node.
|
||||
|
||||
Args:
|
||||
teacher_id: The teacher's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('Teacher', teacher_id)
|
||||
logger.info(f"Created teacher storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_student_storage_path(self, student_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a student node.
|
||||
|
||||
Args:
|
||||
student_id: The student's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('Student', student_id)
|
||||
logger.info(f"Created student storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_school_storage_path(self, school_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a school node.
|
||||
|
||||
Args:
|
||||
school_id: The school's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('School', school_id)
|
||||
logger.info(f"Created school storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_calendar_year_storage_path(self, year: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a calendar year node.
|
||||
|
||||
Args:
|
||||
year: The year
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('CalendarYear', str(year))
|
||||
logger.info(f"Created calendar year storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_calendar_month_storage_path(self, year: int, month: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a calendar month node.
|
||||
|
||||
Args:
|
||||
year: The year
|
||||
month: The month
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
month_id = f"{year}_{month:02d}"
|
||||
path = self.get_storage_path('CalendarMonth', month_id)
|
||||
logger.info(f"Created calendar month storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_calendar_week_storage_path(self, year: int, week: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a calendar week node.
|
||||
|
||||
Args:
|
||||
year: The ISO year
|
||||
week: The ISO week number
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
week_id = f"{year}_{week:02d}"
|
||||
path = self.get_storage_path('CalendarWeek', week_id)
|
||||
logger.info(f"Created calendar week storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_calendar_day_storage_path(self, year: int, month: int, day: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a calendar day node.
|
||||
|
||||
Args:
|
||||
year: The year
|
||||
month: The month
|
||||
day: The day
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
day_id = f"{year}_{month:02d}_{day:02d}"
|
||||
path = self.get_storage_path('CalendarDay', day_id)
|
||||
logger.info(f"Created calendar day storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_calendar_time_chunk_storage_path(self, day_id: str, chunk_index: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a calendar time chunk node.
|
||||
|
||||
Args:
|
||||
day_id: The day identifier (e.g., "2025_01_15")
|
||||
chunk_index: The time chunk index within the day
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
chunk_id = f"{day_id}_{chunk_index:02d}"
|
||||
path = self.get_storage_path('CalendarTimeChunk', chunk_id)
|
||||
logger.info(f"Created calendar time chunk storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_department_storage_path(self, department_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a department node.
|
||||
|
||||
Args:
|
||||
department_id: The department's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('Department', department_id)
|
||||
logger.info(f"Created department storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_subject_storage_path(self, subject_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a subject node.
|
||||
|
||||
Args:
|
||||
subject_id: The subject's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('Subject', subject_id)
|
||||
logger.info(f"Created subject storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_key_stage_storage_path(self, key_stage_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a key stage node.
|
||||
|
||||
Args:
|
||||
key_stage_id: The key stage's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('KeyStage', key_stage_id)
|
||||
logger.info(f"Created key stage storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_year_group_storage_path(self, year_group_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a year group node.
|
||||
|
||||
Args:
|
||||
year_group_id: The year group's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('YearGroup', year_group_id)
|
||||
logger.info(f"Created year group storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_key_stage_syllabus_storage_path(self, syllabus_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a key stage syllabus node.
|
||||
|
||||
Args:
|
||||
syllabus_id: The syllabus's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('KeyStageSyllabus', syllabus_id)
|
||||
logger.info(f"Created key stage syllabus storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_year_group_syllabus_storage_path(self, syllabus_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a year group syllabus node.
|
||||
|
||||
Args:
|
||||
syllabus_id: The syllabus's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('YearGroupSyllabus', syllabus_id)
|
||||
logger.info(f"Created year group syllabus storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_topic_storage_path(self, topic_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a topic node.
|
||||
|
||||
Args:
|
||||
topic_id: The topic's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('Topic', topic_id)
|
||||
logger.info(f"Created topic storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_topic_lesson_storage_path(self, lesson_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a topic lesson node.
|
||||
|
||||
Args:
|
||||
lesson_id: The lesson's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('TopicLesson', lesson_id)
|
||||
logger.info(f"Created topic lesson storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_learning_statement_storage_path(self, statement_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a learning statement node.
|
||||
|
||||
Args:
|
||||
statement_id: The statement's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('LearningStatement', statement_id)
|
||||
logger.info(f"Created learning statement storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_timetable_storage_path(self, timetable_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a timetable node.
|
||||
|
||||
Args:
|
||||
timetable_id: The timetable's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('UserTeacherTimetable', timetable_id)
|
||||
logger.info(f"Created timetable storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_class_storage_path(self, class_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a class node.
|
||||
|
||||
Args:
|
||||
class_id: The class's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('Class', class_id)
|
||||
logger.info(f"Created class storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_timetable_lesson_storage_path(self, lesson_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a timetable lesson node.
|
||||
|
||||
Args:
|
||||
lesson_id: The lesson's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('TimetableLesson', lesson_id)
|
||||
logger.info(f"Created timetable lesson storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_super_admin_storage_path(self, admin_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a super admin node.
|
||||
|
||||
Args:
|
||||
admin_id: The admin's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('SuperAdmin', admin_id)
|
||||
logger.info(f"Created super admin storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_curriculum_storage_path(self, curriculum_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a curriculum structure node.
|
||||
|
||||
Args:
|
||||
curriculum_id: The curriculum's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('CurriculumStructure', curriculum_id)
|
||||
logger.info(f"Created curriculum structure storage path: {path}")
|
||||
return True, path
|
||||
|
||||
def create_pastoral_storage_path(self, pastoral_id: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Create storage path for a pastoral structure node.
|
||||
|
||||
Args:
|
||||
pastoral_id: The pastoral's unique identifier
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, storage_path)
|
||||
"""
|
||||
path = self.get_storage_path('PastoralStructure', pastoral_id)
|
||||
logger.info(f"Created pastoral structure storage path: {path}")
|
||||
return True, path
|
||||
|
||||
# Legacy compatibility methods that return the same interface as filesystem tools
|
||||
def create_private_user_directory(self, user_id: str) -> Tuple[bool, str]:
|
||||
"""Legacy compatibility method."""
|
||||
return self.create_user_storage_path(user_id)
|
||||
|
||||
def create_user_worker_directory(self, user_path: str, worker_id: str, worker_type: str) -> Tuple[bool, str]:
|
||||
"""Legacy compatibility method."""
|
||||
if worker_type in ['teacher', 'email_teacher', 'ms_teacher']:
|
||||
return self.create_teacher_storage_path(worker_id)
|
||||
elif worker_type in ['student', 'email_student', 'ms_student']:
|
||||
return self.create_student_storage_path(worker_id)
|
||||
elif worker_type == 'superadmin':
|
||||
return self.create_super_admin_storage_path(worker_id)
|
||||
elif worker_type == 'developer':
|
||||
return self.create_developer_storage_path(worker_id)
|
||||
else:
|
||||
# Default to generic storage path
|
||||
path = self.get_storage_path(worker_type.title(), worker_id)
|
||||
return True, path
|
||||
|
||||
def create_school_directory(self, school_uuid_string: str) -> Tuple[bool, str]:
|
||||
"""Legacy compatibility method."""
|
||||
return self.create_school_storage_path(school_uuid_string)
|
||||
|
||||
def create_school_curriculum_directory(self, school_path: Optional[str] = None) -> Tuple[bool, str]:
|
||||
"""Legacy compatibility method - returns empty path since curriculum is handled by individual nodes."""
|
||||
return True, ""
|
||||
|
||||
def create_school_pastoral_directory(self, school_path: Optional[str] = None) -> Tuple[bool, str]:
|
||||
"""Legacy compatibility method - returns empty path since pastoral is handled by individual nodes."""
|
||||
return True, ""
|
||||
|
||||
def create_directory(self, path: str) -> bool:
|
||||
"""Legacy compatibility method - always returns True since we don't create physical directories."""
|
||||
return True
|
||||
544
modules/document_analysis.py
Normal file
544
modules/document_analysis.py
Normal file
@ -0,0 +1,544 @@
|
||||
"""
|
||||
Enhanced Document Analysis Module
|
||||
|
||||
This module provides comprehensive document structure analysis beyond basic split maps,
|
||||
including multi-level hierarchies, numbering system detection, and content type analysis.
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import uuid
|
||||
import datetime
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
import fitz # PyMuPDF
|
||||
from modules.logger_tool import initialise_logger
|
||||
import os
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
@dataclass
|
||||
class DocumentSection:
|
||||
"""Represents a document section at any hierarchy level"""
|
||||
id: str
|
||||
title: str
|
||||
level: int
|
||||
start_page: int
|
||||
end_page: int
|
||||
numbering: Optional[str] = None
|
||||
parent_id: Optional[str] = None
|
||||
children: List[str] = None
|
||||
content_types: Dict[str, int] = None
|
||||
confidence: float = 0.8
|
||||
|
||||
def __post_init__(self):
|
||||
if self.children is None:
|
||||
self.children = []
|
||||
if self.content_types is None:
|
||||
self.content_types = {}
|
||||
|
||||
@dataclass
|
||||
class NumberingSystem:
|
||||
"""Represents a detected numbering or coding system"""
|
||||
system_id: str
|
||||
pattern: str
|
||||
description: str
|
||||
examples: List[str]
|
||||
applies_to_levels: List[int]
|
||||
confidence: float
|
||||
|
||||
@dataclass
|
||||
class ContentBlock:
|
||||
"""Represents a content block with type information"""
|
||||
block_id: str
|
||||
page: int
|
||||
bbox: Tuple[float, float, float, float] # x0, y0, x1, y1
|
||||
content_type: str # 'text', 'image', 'table', 'formula', 'diagram'
|
||||
text_content: Optional[str] = None
|
||||
metadata: Dict[str, Any] = None
|
||||
|
||||
class DocumentAnalyzer:
|
||||
"""Enhanced document analyzer for comprehensive structure detection"""
|
||||
|
||||
def __init__(self):
|
||||
self.numbering_patterns = [
|
||||
# Roman numerals
|
||||
(r'^([IVX]+)\.?\s+(.+)', 'roman_numerals', 'Roman numeral chapters (I, II, III, ...)'),
|
||||
# Decimal numbering
|
||||
(r'^(\d+(?:\.\d+)*)\.?\s+(.+)', 'decimal_numbering', 'Decimal numbering (1.1, 1.2.1, ...)'),
|
||||
# Letter numbering
|
||||
(r'^([A-Z])\.?\s+(.+)', 'letter_chapters', 'Letter chapters (A, B, C, ...)'),
|
||||
(r'^([a-z])\.?\s+(.+)', 'letter_sections', 'Letter sections (a, b, c, ...)'),
|
||||
# Bracketed numbering
|
||||
(r'^\((\d+)\)\s+(.+)', 'bracketed_numbers', 'Bracketed numbers ((1), (2), ...)'),
|
||||
# Legal numbering
|
||||
(r'^§\s*(\d+(?:\.\d+)*)\s+(.+)', 'legal_sections', 'Legal sections (§1, §1.1, ...)'),
|
||||
# Article numbering
|
||||
(r'^(?:Article|Art\.?)\s+(\d+(?:\.\d+)*)\s+(.+)', 'articles', 'Article numbering'),
|
||||
]
|
||||
|
||||
def analyze_document_structure(self, pdf_bytes: bytes, tika_json: Dict = None,
|
||||
docling_json: Dict = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Fast, header-only structure analysis.
|
||||
- Prefer PDF outline/bookmarks
|
||||
- Otherwise, use Docling heading roles from existing artefact JSON
|
||||
- No full-text scans; no per-page content analysis
|
||||
"""
|
||||
logger.info("Starting FAST document structure analysis (headings only)")
|
||||
|
||||
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
||||
page_count = len(doc)
|
||||
|
||||
# Try PDF outline first
|
||||
sections: List[DocumentSection] = self._extract_from_pdf_outline(doc)
|
||||
|
||||
# Fallback to Docling headings if outline inadequate
|
||||
if (not sections) and docling_json:
|
||||
sections = self._extract_from_docling(docling_json)
|
||||
|
||||
# Final fallback: coarse windows
|
||||
if not sections:
|
||||
sections = []
|
||||
step = max(10, min(30, page_count // 5 or 1))
|
||||
i = 1
|
||||
idx = 1
|
||||
while i <= page_count:
|
||||
end = min(page_count, i + step - 1)
|
||||
sections.append(DocumentSection(
|
||||
id=f"sec{idx:02d}",
|
||||
title=f"Pages {i}-{end}",
|
||||
level=1,
|
||||
start_page=i,
|
||||
end_page=end,
|
||||
confidence=0.2
|
||||
))
|
||||
i = end + 1
|
||||
idx += 1
|
||||
|
||||
# Build hierarchy relationships and adjust parent end-pages using sibling boundaries
|
||||
sections = self._build_section_hierarchy(sections)
|
||||
# Normalize and finalize sections (clamp, front matter, last-page coverage)
|
||||
sections = self._normalize_and_cover(sections, page_count)
|
||||
|
||||
doc.close()
|
||||
|
||||
return {
|
||||
"version": 2,
|
||||
"analysis_timestamp": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"page_count": page_count,
|
||||
"sections": [asdict(section) for section in sections],
|
||||
"metadata": {
|
||||
"analyzer_version": "2.1-fast",
|
||||
"analysis_methods": ["pdf_outline", "docling_headings"],
|
||||
}
|
||||
}
|
||||
|
||||
def _extract_hierarchical_structure(self, doc: fitz.Document, tika_json: Dict = None,
|
||||
docling_json: Dict = None) -> List[DocumentSection]:
|
||||
# Kept for backward compat; delegate to outline + docling only
|
||||
sections = self._extract_from_pdf_outline(doc)
|
||||
if (not sections) and docling_json:
|
||||
sections = self._extract_from_docling(docling_json)
|
||||
return sections
|
||||
|
||||
def _extract_from_pdf_outline(self, doc: fitz.Document) -> List[DocumentSection]:
|
||||
"""Extract sections from PDF outline/bookmarks"""
|
||||
sections = []
|
||||
toc = doc.get_toc(simple=False)
|
||||
|
||||
for i, (level, title, page, dest) in enumerate(toc):
|
||||
if page < 1:
|
||||
continue
|
||||
|
||||
section_id = f"outline_{i:03d}"
|
||||
|
||||
# Calculate end page (next section's start - 1, or last page)
|
||||
end_page = page
|
||||
for j in range(i + 1, len(toc)):
|
||||
if toc[j][2] > 0: # Valid page number
|
||||
end_page = toc[j][2] - 1
|
||||
break
|
||||
else:
|
||||
end_page = len(doc)
|
||||
|
||||
section = DocumentSection(
|
||||
id=section_id,
|
||||
title=title.strip(),
|
||||
level=level,
|
||||
start_page=page,
|
||||
end_page=end_page,
|
||||
confidence=0.95
|
||||
)
|
||||
sections.append(section)
|
||||
|
||||
return sections
|
||||
|
||||
def _extract_from_docling(self, docling_json: Dict) -> List[DocumentSection]:
|
||||
"""Extract sections from Docling analysis"""
|
||||
sections: List[DocumentSection] = []
|
||||
blocks = docling_json.get("blocks", []) or docling_json.get("elements", [])
|
||||
|
||||
# 1) Collect headings with page and level, preserving order
|
||||
heading_items: List[Tuple[int, str, int, str]] = [] # (page, level, order_index, title)
|
||||
order_index = 0
|
||||
for block in blocks:
|
||||
role_raw = (block.get("role") or block.get("type") or "").lower()
|
||||
if not ("heading" in role_raw or role_raw in ("h1", "h2", "h3", "h4", "h5", "h6", "title")):
|
||||
continue
|
||||
text = (block.get("text") or block.get("content") or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
page_val = block.get("page", None)
|
||||
if page_val is None:
|
||||
page_val = block.get("page_no", None)
|
||||
if page_val is None:
|
||||
page_val = block.get("pageIndex", None)
|
||||
try:
|
||||
page_int = int(page_val) if page_val is not None else 1
|
||||
except Exception:
|
||||
page_int = 1
|
||||
# Normalize to 1-based
|
||||
page_int = page_int + 1 if page_int == 0 else page_int
|
||||
|
||||
# Determine heading level
|
||||
level = 1
|
||||
if "1" in role_raw or role_raw == "title":
|
||||
level = 1
|
||||
elif "2" in role_raw:
|
||||
level = 2
|
||||
elif "3" in role_raw:
|
||||
level = 3
|
||||
elif "4" in role_raw:
|
||||
level = 4
|
||||
elif "5" in role_raw:
|
||||
level = 5
|
||||
elif "6" in role_raw:
|
||||
level = 6
|
||||
|
||||
heading_items.append((page_int, level, order_index, text))
|
||||
order_index += 1
|
||||
|
||||
if not heading_items:
|
||||
return sections
|
||||
|
||||
# 2) Sort by page then order_index to preserve within-page order
|
||||
heading_items.sort(key=lambda h: (h[0], h[2]))
|
||||
|
||||
# 3) Build sections with hierarchical end-page computation
|
||||
stack: List[DocumentSection] = []
|
||||
idx_counter = 0
|
||||
|
||||
def close_until(level_threshold: int, next_page: int):
|
||||
nonlocal sections, stack
|
||||
while stack and stack[-1].level >= level_threshold:
|
||||
cur = stack.pop()
|
||||
# If next heading on same page, close at same page; else previous page
|
||||
if next_page <= cur.start_page:
|
||||
cur.end_page = cur.start_page
|
||||
else:
|
||||
cur.end_page = next_page - 1
|
||||
if cur.end_page < cur.start_page:
|
||||
cur.end_page = cur.start_page
|
||||
sections.append(cur)
|
||||
|
||||
for page_int, level, _, text in heading_items:
|
||||
# Close siblings and deeper levels
|
||||
close_until(level, page_int)
|
||||
# Open new heading
|
||||
section_id = f"docling_{idx_counter:03d}"
|
||||
idx_counter += 1
|
||||
new_sec = DocumentSection(
|
||||
id=section_id,
|
||||
title=text,
|
||||
level=level,
|
||||
start_page=page_int,
|
||||
end_page=page_int, # temporary; will finalize when closing
|
||||
confidence=0.8
|
||||
)
|
||||
stack.append(new_sec)
|
||||
|
||||
# Close any remaining open sections at document end later in normalization
|
||||
while stack:
|
||||
cur = stack.pop()
|
||||
sections.append(cur)
|
||||
|
||||
return sections
|
||||
|
||||
def _normalize_and_cover(self, sections: List[DocumentSection], page_count: int) -> List[DocumentSection]:
|
||||
"""Harden outline sections while preserving hierarchy:
|
||||
- clamp each section to [1, page_count]
|
||||
- fix inverted ranges (but DO NOT remove hierarchical overlaps)
|
||||
- ensure coverage from page 1 with synthetic front-matter if needed
|
||||
- ensure last top-level section extends to page_count
|
||||
- compute numbering hint if missing
|
||||
"""
|
||||
if not sections:
|
||||
return sections
|
||||
|
||||
# Clamp values per section; do not modify overlap relationships
|
||||
for s in sections:
|
||||
s.start_page = max(1, min(s.start_page or 1, page_count))
|
||||
s.end_page = max(1, min(s.end_page or s.start_page, page_count))
|
||||
if s.end_page < s.start_page:
|
||||
s.end_page = s.start_page
|
||||
|
||||
# Maintain original order (as produced by extractor and hierarchy builder)
|
||||
|
||||
# Insert synthetic front matter if needed
|
||||
if sections and sections[0].start_page > 1:
|
||||
# Generate a unique synthetic id that won't collide with existing ids
|
||||
existing_ids = {s.id for s in sections}
|
||||
base_id = "outline_front_matter"
|
||||
syn_id = base_id
|
||||
idx = 1
|
||||
while syn_id in existing_ids:
|
||||
syn_id = f"{base_id}_{idx}"
|
||||
idx += 1
|
||||
front = DocumentSection(
|
||||
id=syn_id,
|
||||
title="Front matter",
|
||||
level=1,
|
||||
start_page=1,
|
||||
end_page=sections[0].start_page - 1,
|
||||
confidence=0.6
|
||||
)
|
||||
sections.insert(0, front)
|
||||
|
||||
# Ensure last top-level section covers to page_count
|
||||
top_levels = [s for s in sections if s.parent_id is None]
|
||||
if top_levels:
|
||||
last_top = top_levels[-1]
|
||||
if last_top.end_page < page_count:
|
||||
last_top.end_page = page_count
|
||||
|
||||
# Light numbering extraction based on heading text prefix
|
||||
for s in sections:
|
||||
m = re.match(r"^\s*([A-Za-z]+|[IVXLCM]+|\d+(?:\.\d+)*)\.?\s+", s.title)
|
||||
if m:
|
||||
s.numbering = s.numbering or m.group(1)
|
||||
|
||||
return sections
|
||||
|
||||
def _extract_from_text_patterns(self, doc: fitz.Document) -> List[DocumentSection]:
|
||||
# Disabled in fast mode
|
||||
return []
|
||||
|
||||
def _estimate_section_level(self, numbering: str, system_type: str) -> int:
|
||||
"""Estimate section level based on numbering pattern"""
|
||||
if system_type == 'roman_numerals':
|
||||
return 1 # Typically chapter level
|
||||
elif system_type == 'decimal_numbering':
|
||||
dots = numbering.count('.')
|
||||
return min(dots + 1, 6) # 1.1.1 = level 3
|
||||
elif system_type == 'letter_chapters':
|
||||
return 1
|
||||
elif system_type == 'letter_sections':
|
||||
return 2
|
||||
else:
|
||||
return 2 # Default
|
||||
|
||||
def _detect_numbering_systems(self, sections: List[DocumentSection]) -> List[NumberingSystem]:
|
||||
"""Detect numbering systems used in the document"""
|
||||
systems = []
|
||||
|
||||
# Group sections by their numbering patterns
|
||||
pattern_groups = {}
|
||||
for section in sections:
|
||||
if section.numbering:
|
||||
for pattern, system_type, description in self.numbering_patterns:
|
||||
if re.match(pattern.replace(r'^(.+)', section.numbering), section.numbering):
|
||||
if system_type not in pattern_groups:
|
||||
pattern_groups[system_type] = {
|
||||
'pattern': pattern,
|
||||
'description': description,
|
||||
'examples': [],
|
||||
'levels': set(),
|
||||
'count': 0
|
||||
}
|
||||
pattern_groups[system_type]['examples'].append(section.numbering)
|
||||
pattern_groups[system_type]['levels'].add(section.level)
|
||||
pattern_groups[system_type]['count'] += 1
|
||||
break
|
||||
|
||||
# Create NumberingSystem objects
|
||||
for system_type, data in pattern_groups.items():
|
||||
if data['count'] >= 2: # At least 2 examples to be confident
|
||||
system = NumberingSystem(
|
||||
system_id=system_type,
|
||||
pattern=data['pattern'],
|
||||
description=data['description'],
|
||||
examples=data['examples'][:5], # First 5 examples
|
||||
applies_to_levels=list(data['levels']),
|
||||
confidence=min(0.9, 0.5 + (data['count'] * 0.1))
|
||||
)
|
||||
systems.append(system)
|
||||
|
||||
return systems
|
||||
|
||||
def _analyze_content_types(self, doc: fitz.Document, sections: List[DocumentSection]) -> Dict[str, Any]:
|
||||
# Disabled in fast mode
|
||||
return {"total_blocks": 0, "content_types": {}, "sections": {}}
|
||||
|
||||
def _detect_tables_in_page(self, page) -> int:
|
||||
return 0
|
||||
|
||||
def _has_complex_formatting(self, page) -> bool:
|
||||
return False
|
||||
|
||||
def _merge_and_deduplicate_sections(self, sections: List[DocumentSection]) -> List[DocumentSection]:
|
||||
"""Merge overlapping sections and remove duplicates"""
|
||||
if not sections:
|
||||
return []
|
||||
|
||||
# Sort by start page, then by level
|
||||
sections.sort(key=lambda s: (s.start_page, s.level))
|
||||
|
||||
merged = []
|
||||
for section in sections:
|
||||
# Check if this section overlaps significantly with existing ones
|
||||
is_duplicate = False
|
||||
for existing in merged:
|
||||
if (existing.start_page == section.start_page and
|
||||
abs(existing.level - section.level) <= 1 and
|
||||
self._text_similarity(existing.title, section.title) > 0.8):
|
||||
# This is likely a duplicate, merge information
|
||||
if section.confidence > existing.confidence:
|
||||
existing.title = section.title
|
||||
existing.numbering = section.numbering or existing.numbering
|
||||
existing.confidence = section.confidence
|
||||
is_duplicate = True
|
||||
break
|
||||
|
||||
if not is_duplicate:
|
||||
merged.append(section)
|
||||
|
||||
return merged
|
||||
|
||||
def _text_similarity(self, text1: str, text2: str) -> float:
|
||||
"""Calculate text similarity (simple implementation)"""
|
||||
if not text1 or not text2:
|
||||
return 0.0
|
||||
|
||||
# Simple word-based similarity
|
||||
words1 = set(text1.lower().split())
|
||||
words2 = set(text2.lower().split())
|
||||
|
||||
if not words1 and not words2:
|
||||
return 1.0
|
||||
|
||||
intersection = words1.intersection(words2)
|
||||
union = words1.union(words2)
|
||||
|
||||
return len(intersection) / len(union) if union else 0.0
|
||||
|
||||
def _build_section_hierarchy(self, sections: List[DocumentSection]) -> List[DocumentSection]:
|
||||
"""Build parent-child relationships between sections"""
|
||||
if not sections:
|
||||
return []
|
||||
|
||||
# Sort by start page and level
|
||||
sections.sort(key=lambda s: (s.start_page, s.level))
|
||||
|
||||
# Build hierarchy
|
||||
for i, section in enumerate(sections):
|
||||
# Find parent (previous section with lower level)
|
||||
for j in range(i - 1, -1, -1):
|
||||
potential_parent = sections[j]
|
||||
if (potential_parent.level < section.level and
|
||||
potential_parent.start_page <= section.start_page and
|
||||
potential_parent.end_page >= section.start_page):
|
||||
section.parent_id = potential_parent.id
|
||||
potential_parent.children.append(section.id)
|
||||
break
|
||||
|
||||
# Update end pages based on children
|
||||
for j in range(i + 1, len(sections)):
|
||||
next_section = sections[j]
|
||||
if (next_section.level <= section.level and
|
||||
next_section.start_page > section.start_page):
|
||||
section.end_page = min(section.end_page, next_section.start_page - 1)
|
||||
break
|
||||
|
||||
return sections
|
||||
|
||||
def _generate_processing_recommendations(self, sections: List[DocumentSection],
|
||||
content_analysis: Dict) -> Dict[str, Any]:
|
||||
"""Generate intelligent processing recommendations"""
|
||||
recommendations = {
|
||||
"document_length_category": "short", # short, medium, long
|
||||
"suggested_processing_approach": "full_document", # full_document, section_by_section
|
||||
"high_priority_sections": [],
|
||||
"ocr_recommended_sections": [],
|
||||
"image_analysis_sections": [],
|
||||
"table_extraction_sections": [],
|
||||
"estimated_total_time": 0
|
||||
}
|
||||
|
||||
total_pages = max(s.end_page for s in sections) if sections else 0
|
||||
|
||||
# Categorize document length
|
||||
if total_pages <= 10:
|
||||
recommendations["document_length_category"] = "short"
|
||||
recommendations["suggested_processing_approach"] = "full_document"
|
||||
elif total_pages <= 50:
|
||||
recommendations["document_length_category"] = "medium"
|
||||
recommendations["suggested_processing_approach"] = "section_by_section"
|
||||
else:
|
||||
recommendations["document_length_category"] = "long"
|
||||
recommendations["suggested_processing_approach"] = "section_by_section"
|
||||
|
||||
# Analyze each section for recommendations
|
||||
total_estimated_time = 0
|
||||
for section in sections:
|
||||
section_data = content_analysis["sections"].get(section.id, {})
|
||||
|
||||
# High priority sections (first few sections, or sections with important titles)
|
||||
if (section.start_page <= 5 or
|
||||
any(keyword in section.title.lower() for keyword in
|
||||
['abstract', 'summary', 'introduction', 'conclusion', 'executive'])):
|
||||
recommendations["high_priority_sections"].append(section.id)
|
||||
|
||||
# OCR recommendations
|
||||
if section_data.get("images", 0) > 0 or section_data.get("has_complex_formatting", False):
|
||||
recommendations["ocr_recommended_sections"].append(section.id)
|
||||
|
||||
# Image analysis recommendations
|
||||
if section_data.get("images", 0) > 2: # Sections with multiple images
|
||||
recommendations["image_analysis_sections"].append(section.id)
|
||||
|
||||
# Table extraction recommendations
|
||||
if section_data.get("tables", 0) > 0:
|
||||
recommendations["table_extraction_sections"].append(section.id)
|
||||
|
||||
total_estimated_time += section_data.get("estimated_processing_time", 0)
|
||||
|
||||
recommendations["estimated_total_time"] = total_estimated_time
|
||||
|
||||
return recommendations
|
||||
|
||||
def create_document_outline_hierarchy_artefact(file_id: str, pdf_bytes: bytes,
|
||||
tika_json: Dict = None,
|
||||
docling_json: Dict = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a comprehensive document outline hierarchy artefact
|
||||
|
||||
Args:
|
||||
file_id: File ID
|
||||
pdf_bytes: PDF file content
|
||||
tika_json: Optional Tika analysis results
|
||||
docling_json: Optional Docling analysis results
|
||||
|
||||
Returns:
|
||||
Document outline hierarchy artefact
|
||||
"""
|
||||
analyzer = DocumentAnalyzer()
|
||||
analysis = analyzer.analyze_document_structure(pdf_bytes, tika_json, docling_json)
|
||||
|
||||
# Add file metadata
|
||||
analysis["file_id"] = file_id
|
||||
analysis["artefact_id"] = str(uuid.uuid4())
|
||||
analysis["artefact_type"] = "document_outline_hierarchy"
|
||||
|
||||
return analysis
|
||||
@ -39,28 +39,41 @@ class DocumentProcessor:
|
||||
|
||||
# Use LibreOffice for conversion
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
output_file = Path(temp_dir) / f"{input_file.stem}.pdf"
|
||||
|
||||
# Convert using LibreOffice
|
||||
cmd = [
|
||||
'libreoffice',
|
||||
'--headless',
|
||||
'--convert-to', 'pdf',
|
||||
'--outdir', str(temp_dir),
|
||||
str(input_file)
|
||||
]
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"Conversion failed: {e.stderr.decode()}")
|
||||
with tempfile.TemporaryDirectory() as profile_dir:
|
||||
output_file = Path(temp_dir) / f"{input_file.stem}.pdf"
|
||||
|
||||
# Convert using LibreOffice with explicit profile directory
|
||||
cmd = [
|
||||
'/Applications/LibreOffice.app/Contents/MacOS/soffice',
|
||||
'--headless',
|
||||
'--invisible',
|
||||
'--nodefault',
|
||||
'--nolockcheck',
|
||||
'--nologo',
|
||||
'--norestore',
|
||||
f'-env:UserInstallation=file://{profile_dir}',
|
||||
'--convert-to', 'pdf',
|
||||
'--outdir', str(temp_dir),
|
||||
str(input_file)
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, check=True, capture_output=True, timeout=120)
|
||||
except subprocess.CalledProcessError as e:
|
||||
stderr_msg = e.stderr.decode() if e.stderr else "No stderr output"
|
||||
stdout_msg = e.stdout.decode() if e.stdout else "No stdout output"
|
||||
raise RuntimeError(f"Conversion failed: {stderr_msg}. Stdout: {stdout_msg}")
|
||||
except subprocess.TimeoutExpired:
|
||||
raise RuntimeError("Conversion failed: Process timed out after 120 seconds")
|
||||
|
||||
if not output_file.exists():
|
||||
raise RuntimeError("Conversion failed: Output file not created")
|
||||
if not output_file.exists():
|
||||
# List all files in temp_dir for debugging
|
||||
files_in_dir = list(Path(temp_dir).glob('*'))
|
||||
raise RuntimeError(f"Conversion failed: Output file not created. Files in output dir: {files_in_dir}")
|
||||
|
||||
# Read and return the PDF content
|
||||
with open(output_file, 'rb') as f:
|
||||
return f.read()
|
||||
# Read and return the PDF content
|
||||
with open(output_file, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
def batch_convert_directory(self, directory: str) -> List[Dict]:
|
||||
"""
|
||||
|
||||
411
modules/memory_aware_queue.py
Normal file
411
modules/memory_aware_queue.py
Normal file
@ -0,0 +1,411 @@
|
||||
"""
|
||||
Memory-Aware Queue Management System
|
||||
====================================
|
||||
|
||||
Provides intelligent queue management based on memory usage and file sizes
|
||||
rather than simple task count limits. Supports multiple users with fair
|
||||
queuing and capacity management.
|
||||
|
||||
Features:
|
||||
- Memory-based queue limits (not just task count)
|
||||
- Fair queuing across multiple users
|
||||
- Upload capacity checking with user feedback
|
||||
- Graceful degradation under load
|
||||
- Service-specific memory tracking
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
import redis
|
||||
from .redis_manager import get_redis_manager
|
||||
import psutil
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class QueueStatus(Enum):
|
||||
ACCEPTING = "accepting" # Normal operation
|
||||
BUSY = "busy" # High load, warn users
|
||||
OVERLOADED = "overloaded" # Reject new uploads
|
||||
MAINTENANCE = "maintenance" # Manual override
|
||||
|
||||
@dataclass
|
||||
class MemoryConfig:
|
||||
"""Memory configuration for queue management."""
|
||||
max_total_memory_mb: int = 2048 # 2GB total queue memory
|
||||
max_user_memory_mb: int = 512 # 512MB per user
|
||||
max_file_size_mb: int = 100 # 100MB max file size
|
||||
memory_warning_threshold: float = 0.8 # Warn at 80%
|
||||
memory_reject_threshold: float = 0.95 # Reject at 95%
|
||||
|
||||
@dataclass
|
||||
class QueuedFile:
|
||||
"""Represents a file waiting in the queue."""
|
||||
file_id: str
|
||||
user_id: str
|
||||
filename: str
|
||||
size_bytes: int
|
||||
mime_type: str
|
||||
cabinet_id: str
|
||||
priority: int = 1
|
||||
queued_at: float = 0
|
||||
estimated_processing_time: int = 300 # seconds
|
||||
memory_estimate_mb: float = 0
|
||||
|
||||
def __post_init__(self):
|
||||
if self.queued_at == 0:
|
||||
self.queued_at = time.time()
|
||||
|
||||
# Estimate memory usage (rough heuristic)
|
||||
self.memory_estimate_mb = self._estimate_memory_usage()
|
||||
|
||||
def _estimate_memory_usage(self) -> float:
|
||||
"""Estimate memory usage for this file during processing."""
|
||||
base_mb = self.size_bytes / (1024 * 1024)
|
||||
|
||||
# Processing multipliers based on operations
|
||||
if self.mime_type == 'application/pdf':
|
||||
# PDF: original + extracted text + images + thumbnails
|
||||
return base_mb * 3.5
|
||||
elif self.mime_type.startswith('image/'):
|
||||
# Images: original + resized variants + OCR text
|
||||
return base_mb * 2.5
|
||||
else:
|
||||
# Other docs: original + PDF conversion + processing
|
||||
return base_mb * 4.0
|
||||
|
||||
class MemoryAwareQueue:
|
||||
"""Memory-aware queue management system."""
|
||||
|
||||
def __init__(self, environment: str = "dev"):
|
||||
self.redis_manager = get_redis_manager(environment)
|
||||
self.redis_client = self.redis_manager.client
|
||||
self.config = self._load_config()
|
||||
|
||||
# Redis keys
|
||||
self.upload_queue_key = "upload_queue"
|
||||
self.processing_memory_key = "processing_memory"
|
||||
self.user_quota_key = "user_quotas"
|
||||
self.system_status_key = "system_status"
|
||||
|
||||
logger.info(f"🧠 Memory-aware queue initialized (max: {self.config.max_total_memory_mb}MB)")
|
||||
|
||||
def _load_config(self) -> MemoryConfig:
|
||||
"""Load memory configuration from environment."""
|
||||
return MemoryConfig(
|
||||
max_total_memory_mb=int(os.getenv('QUEUE_MAX_MEMORY_MB', '2048')),
|
||||
max_user_memory_mb=int(os.getenv('QUEUE_MAX_USER_MEMORY_MB', '512')),
|
||||
max_file_size_mb=int(os.getenv('MAX_FILE_SIZE_MB', '100')),
|
||||
memory_warning_threshold=float(os.getenv('MEMORY_WARNING_THRESHOLD', '0.8')),
|
||||
memory_reject_threshold=float(os.getenv('MEMORY_REJECT_THRESHOLD', '0.95'))
|
||||
)
|
||||
|
||||
def check_upload_capacity(self, user_id: str, file_size_bytes: int,
|
||||
mime_type: str) -> Tuple[bool, str, Dict[str, Any]]:
|
||||
"""
|
||||
Check if system can accept a new upload.
|
||||
|
||||
Returns:
|
||||
(can_accept, message, queue_info)
|
||||
"""
|
||||
|
||||
# Create temporary QueuedFile to estimate memory
|
||||
temp_file = QueuedFile(
|
||||
file_id="temp",
|
||||
user_id=user_id,
|
||||
filename="temp",
|
||||
size_bytes=file_size_bytes,
|
||||
mime_type=mime_type,
|
||||
cabinet_id="temp"
|
||||
)
|
||||
|
||||
file_memory_mb = temp_file.memory_estimate_mb
|
||||
|
||||
# Check file size limit
|
||||
if file_size_bytes > (self.config.max_file_size_mb * 1024 * 1024):
|
||||
return False, f"File too large (max: {self.config.max_file_size_mb}MB)", {}
|
||||
|
||||
# Get current memory usage
|
||||
current_memory = self._get_current_memory_usage()
|
||||
user_memory = self._get_user_memory_usage(user_id)
|
||||
|
||||
# Check user quota
|
||||
if user_memory + file_memory_mb > self.config.max_user_memory_mb:
|
||||
return False, f"User quota exceeded (limit: {self.config.max_user_memory_mb}MB)", {
|
||||
'user_current': user_memory,
|
||||
'user_limit': self.config.max_user_memory_mb
|
||||
}
|
||||
|
||||
# Check system capacity
|
||||
total_after = current_memory + file_memory_mb
|
||||
max_memory = self.config.max_total_memory_mb
|
||||
|
||||
if total_after > (max_memory * self.config.memory_reject_threshold):
|
||||
queue_info = self._get_queue_info()
|
||||
return False, "System overloaded. Please try again later.", {
|
||||
'current_memory': current_memory,
|
||||
'max_memory': max_memory,
|
||||
'utilization': current_memory / max_memory,
|
||||
'queue_position': queue_info['total_queued'] + 1
|
||||
}
|
||||
|
||||
# Calculate wait time estimate
|
||||
wait_estimate = self._estimate_wait_time(user_id)
|
||||
|
||||
status = "ready"
|
||||
message = "Upload accepted"
|
||||
|
||||
if total_after > (max_memory * self.config.memory_warning_threshold):
|
||||
status = "busy"
|
||||
message = f"System busy. Estimated wait: {wait_estimate // 60}m {wait_estimate % 60}s"
|
||||
|
||||
return True, message, {
|
||||
'status': status,
|
||||
'estimated_wait_seconds': wait_estimate,
|
||||
'memory_usage': {
|
||||
'current': current_memory,
|
||||
'after_upload': total_after,
|
||||
'limit': max_memory,
|
||||
'utilization': total_after / max_memory
|
||||
},
|
||||
'user_quota': {
|
||||
'used': user_memory,
|
||||
'after_upload': user_memory + file_memory_mb,
|
||||
'limit': self.config.max_user_memory_mb
|
||||
}
|
||||
}
|
||||
|
||||
def enqueue_file(self, file_id: str, user_id: str, filename: str,
|
||||
size_bytes: int, mime_type: str, cabinet_id: str,
|
||||
priority: int = 1) -> Dict[str, Any]:
|
||||
"""
|
||||
Add file to upload queue.
|
||||
|
||||
Returns:
|
||||
Queue information including position and estimated wait time
|
||||
"""
|
||||
|
||||
queued_file = QueuedFile(
|
||||
file_id=file_id,
|
||||
user_id=user_id,
|
||||
filename=filename,
|
||||
size_bytes=size_bytes,
|
||||
mime_type=mime_type,
|
||||
cabinet_id=cabinet_id,
|
||||
priority=priority
|
||||
)
|
||||
|
||||
# Serialize and add to Redis queue (priority queue: higher priority = lower score)
|
||||
score = time.time() - (priority * 1000000) # Priority affects score significantly
|
||||
|
||||
self.redis_client.zadd(
|
||||
self.upload_queue_key,
|
||||
{json.dumps(asdict(queued_file)): score}
|
||||
)
|
||||
|
||||
# Update user quota tracking
|
||||
self._update_user_quota(user_id, queued_file.memory_estimate_mb, increment=True)
|
||||
|
||||
# Get queue position and wait estimate
|
||||
position = self._get_queue_position(file_id)
|
||||
wait_estimate = self._estimate_wait_time(user_id)
|
||||
|
||||
logger.info(f"📋 Queued file {file_id} for user {user_id} (pos: {position}, wait: {wait_estimate}s)")
|
||||
|
||||
return {
|
||||
'queued': True,
|
||||
'file_id': file_id,
|
||||
'queue_position': position,
|
||||
'estimated_wait_seconds': wait_estimate,
|
||||
'memory_estimate_mb': queued_file.memory_estimate_mb
|
||||
}
|
||||
|
||||
def dequeue_next_file(self, service_name: str) -> Optional[QueuedFile]:
|
||||
"""
|
||||
Get next file from queue for processing.
|
||||
|
||||
Args:
|
||||
service_name: The service requesting work (for capacity management)
|
||||
"""
|
||||
|
||||
# Check if service has capacity
|
||||
service_memory = self._get_service_memory_usage(service_name)
|
||||
service_limit = self._get_service_memory_limit(service_name)
|
||||
|
||||
if service_memory >= service_limit:
|
||||
logger.debug(f"Service {service_name} at capacity ({service_memory}/{service_limit}MB)")
|
||||
return None
|
||||
|
||||
# Get next item from priority queue (lowest score first)
|
||||
items = self.redis_client.zrange(self.upload_queue_key, 0, 0, withscores=True)
|
||||
|
||||
if not items:
|
||||
return None
|
||||
|
||||
file_data_json, score = items[0]
|
||||
file_data = json.loads(file_data_json)
|
||||
queued_file = QueuedFile(**file_data)
|
||||
|
||||
# Check if this file would exceed service memory limit
|
||||
if service_memory + queued_file.memory_estimate_mb > service_limit:
|
||||
# Skip this file for now, try smaller ones later
|
||||
logger.debug(f"File {queued_file.file_id} too large for {service_name} capacity")
|
||||
return None
|
||||
|
||||
# Remove from queue
|
||||
self.redis_client.zrem(self.upload_queue_key, file_data_json)
|
||||
|
||||
# Update tracking
|
||||
self._update_user_quota(queued_file.user_id, queued_file.memory_estimate_mb, increment=False)
|
||||
self._update_service_memory(service_name, queued_file.memory_estimate_mb, increment=True)
|
||||
|
||||
logger.info(f"🎯 Dequeued file {queued_file.file_id} for {service_name} processing")
|
||||
|
||||
return queued_file
|
||||
|
||||
def complete_processing(self, service_name: str, file_id: str, memory_used_mb: float):
|
||||
"""Mark file processing as complete and free memory."""
|
||||
self._update_service_memory(service_name, memory_used_mb, increment=False)
|
||||
logger.info(f"✅ Completed processing {file_id} in {service_name} (freed {memory_used_mb}MB)")
|
||||
|
||||
def _get_current_memory_usage(self) -> float:
|
||||
"""Get current total memory usage across all services."""
|
||||
services = ['docling', 'tika', 'llm', 'document_analysis']
|
||||
total = 0
|
||||
|
||||
for service in services:
|
||||
service_key = f"{self.processing_memory_key}:{service}"
|
||||
memory = float(self.redis_client.get(service_key) or 0)
|
||||
total += memory
|
||||
|
||||
return total
|
||||
|
||||
def _get_user_memory_usage(self, user_id: str) -> float:
|
||||
"""Get current memory usage for a specific user."""
|
||||
user_key = f"{self.user_quota_key}:{user_id}"
|
||||
return float(self.redis_client.get(user_key) or 0)
|
||||
|
||||
def _get_service_memory_usage(self, service_name: str) -> float:
|
||||
"""Get current memory usage for a service."""
|
||||
service_key = f"{self.processing_memory_key}:{service_name}"
|
||||
return float(self.redis_client.get(service_key) or 0)
|
||||
|
||||
def _get_service_memory_limit(self, service_name: str) -> float:
|
||||
"""Get memory limit for a service."""
|
||||
# Service-specific memory limits as percentage of total
|
||||
limits = {
|
||||
'docling': 0.4, # 40% for Docling (memory-intensive)
|
||||
'tika': 0.2, # 20% for Tika
|
||||
'llm': 0.3, # 30% for LLM processing
|
||||
'document_analysis': 0.1 # 10% for document analysis
|
||||
}
|
||||
|
||||
percentage = limits.get(service_name, 0.1)
|
||||
return self.config.max_total_memory_mb * percentage
|
||||
|
||||
def _update_user_quota(self, user_id: str, memory_mb: float, increment: bool):
|
||||
"""Update user memory quota tracking."""
|
||||
user_key = f"{self.user_quota_key}:{user_id}"
|
||||
|
||||
if increment:
|
||||
self.redis_client.incrbyfloat(user_key, memory_mb)
|
||||
else:
|
||||
current = float(self.redis_client.get(user_key) or 0)
|
||||
new_value = max(0, current - memory_mb)
|
||||
self.redis_client.set(user_key, new_value)
|
||||
|
||||
# Set expiration for cleanup
|
||||
self.redis_client.expire(user_key, 86400) # 24 hours
|
||||
|
||||
def _update_service_memory(self, service_name: str, memory_mb: float, increment: bool):
|
||||
"""Update service memory usage tracking."""
|
||||
service_key = f"{self.processing_memory_key}:{service_name}"
|
||||
|
||||
if increment:
|
||||
self.redis_client.incrbyfloat(service_key, memory_mb)
|
||||
else:
|
||||
current = float(self.redis_client.get(service_key) or 0)
|
||||
new_value = max(0, current - memory_mb)
|
||||
self.redis_client.set(service_key, new_value)
|
||||
|
||||
# Set expiration for cleanup
|
||||
self.redis_client.expire(service_key, 3600) # 1 hour
|
||||
|
||||
def _get_queue_position(self, file_id: str) -> int:
|
||||
"""Get position of file in queue."""
|
||||
items = self.redis_client.zrange(self.upload_queue_key, 0, -1)
|
||||
for i, item in enumerate(items):
|
||||
file_data = json.loads(item)
|
||||
if file_data['file_id'] == file_id:
|
||||
return i + 1
|
||||
return 0
|
||||
|
||||
def _estimate_wait_time(self, user_id: str) -> int:
|
||||
"""Estimate wait time for user's next file."""
|
||||
# Simple estimation based on queue position and average processing time
|
||||
queue_size = self.redis_client.zcard(self.upload_queue_key)
|
||||
avg_processing_time = 300 # 5 minutes average
|
||||
|
||||
return int(queue_size * avg_processing_time * 0.5) # Assume parallel processing
|
||||
|
||||
def _get_queue_info(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive queue information."""
|
||||
total_queued = self.redis_client.zcard(self.upload_queue_key)
|
||||
current_memory = self._get_current_memory_usage()
|
||||
max_memory = self.config.max_total_memory_mb
|
||||
|
||||
return {
|
||||
'total_queued': total_queued,
|
||||
'memory_usage': {
|
||||
'current_mb': current_memory,
|
||||
'max_mb': max_memory,
|
||||
'utilization': current_memory / max_memory if max_memory > 0 else 0
|
||||
},
|
||||
'status': self._determine_system_status(current_memory, max_memory)
|
||||
}
|
||||
|
||||
def _determine_system_status(self, current_memory: float, max_memory: float) -> str:
|
||||
"""Determine current system status based on memory usage."""
|
||||
utilization = current_memory / max_memory if max_memory > 0 else 0
|
||||
|
||||
if utilization >= self.config.memory_reject_threshold:
|
||||
return "overloaded"
|
||||
elif utilization >= self.config.memory_warning_threshold:
|
||||
return "busy"
|
||||
else:
|
||||
return "ready"
|
||||
|
||||
def get_system_status(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive system status for monitoring."""
|
||||
queue_info = self._get_queue_info()
|
||||
|
||||
# Service-specific info
|
||||
services = {}
|
||||
for service_name in ['docling', 'tika', 'llm', 'document_analysis']:
|
||||
services[service_name] = {
|
||||
'memory_used_mb': self._get_service_memory_usage(service_name),
|
||||
'memory_limit_mb': self._get_service_memory_limit(service_name),
|
||||
'utilization': self._get_service_memory_usage(service_name) / self._get_service_memory_limit(service_name)
|
||||
}
|
||||
|
||||
return {
|
||||
'status': queue_info['status'],
|
||||
'queue': queue_info,
|
||||
'services': services,
|
||||
'config': asdict(self.config)
|
||||
}
|
||||
|
||||
# Convenience functions
|
||||
def get_memory_queue(environment: str = "dev") -> MemoryAwareQueue:
|
||||
"""Get memory-aware queue instance."""
|
||||
return MemoryAwareQueue(environment)
|
||||
|
||||
def check_upload_capacity(user_id: str, file_size: int, mime_type: str, environment: str = "dev") -> Tuple[bool, str, Dict]:
|
||||
"""Quick capacity check for upload."""
|
||||
queue = get_memory_queue(environment)
|
||||
return queue.check_upload_capacity(user_id, file_size, mime_type)
|
||||
223
modules/page_image_generator.py
Normal file
223
modules/page_image_generator.py
Normal file
@ -0,0 +1,223 @@
|
||||
"""
|
||||
Page Image Generation Module
|
||||
|
||||
This module generates full-resolution page images and thumbnails from PDF documents
|
||||
for use in the document viewer UI.
|
||||
"""
|
||||
|
||||
import io
|
||||
import uuid
|
||||
from typing import Dict, List, Any, Tuple
|
||||
from pathlib import Path
|
||||
import fitz # PyMuPDF
|
||||
from PIL import Image
|
||||
from modules.logger_tool import initialise_logger
|
||||
import os
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
class PageImageGenerator:
|
||||
"""Generates page images and thumbnails from PDF documents"""
|
||||
|
||||
def __init__(self):
|
||||
# Image generation settings
|
||||
self.full_image_dpi = 200 # High quality for full images
|
||||
self.thumbnail_dpi = 100 # Lower quality for thumbnails
|
||||
self.thumbnail_max_width = 300
|
||||
self.thumbnail_max_height = 400
|
||||
self.image_format = "PNG"
|
||||
self.thumbnail_format = "WEBP" # More efficient for thumbnails
|
||||
|
||||
def generate_page_images(self, file_id: str, cabinet_id: str, pdf_bytes: bytes) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate full page images and thumbnails for a PDF document
|
||||
|
||||
Args:
|
||||
file_id: File ID
|
||||
cabinet_id: Cabinet ID for storage path
|
||||
pdf_bytes: PDF file content
|
||||
|
||||
Returns:
|
||||
Page images artefact data
|
||||
"""
|
||||
logger.info(f"Starting page image generation for file_id={file_id}")
|
||||
|
||||
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
||||
page_count = len(doc)
|
||||
|
||||
artefact_id = str(uuid.uuid4())
|
||||
page_images = []
|
||||
|
||||
for page_num in range(page_count):
|
||||
page = doc[page_num]
|
||||
page_number = page_num + 1
|
||||
|
||||
logger.debug(f"Processing page {page_number}/{page_count} for file_id={file_id}")
|
||||
|
||||
# Generate full resolution image
|
||||
full_image_data, full_dimensions = self._generate_full_image(page, page_number)
|
||||
full_image_path = f"{cabinet_id}/{file_id}/{artefact_id}/page_{page_number:03d}_full.png"
|
||||
|
||||
# Generate thumbnail
|
||||
thumbnail_data, thumbnail_dimensions = self._generate_thumbnail(page, page_number)
|
||||
thumbnail_path = f"{cabinet_id}/{file_id}/{artefact_id}/page_{page_number:03d}_thumb.webp"
|
||||
|
||||
page_info = {
|
||||
"page": page_number,
|
||||
"full_image_path": full_image_path,
|
||||
"full_image_data": full_image_data, # Will be uploaded separately
|
||||
"full_dimensions": full_dimensions,
|
||||
"thumbnail_path": thumbnail_path,
|
||||
"thumbnail_data": thumbnail_data, # Will be uploaded separately
|
||||
"thumbnail_dimensions": thumbnail_dimensions,
|
||||
"rotation": page.rotation,
|
||||
"has_text": bool(page.get_text().strip()),
|
||||
"has_images": len(page.get_images()) > 0,
|
||||
"has_drawings": len(page.get_drawings()) > 0
|
||||
}
|
||||
|
||||
page_images.append(page_info)
|
||||
|
||||
doc.close()
|
||||
|
||||
artefact_data = {
|
||||
"version": 1,
|
||||
"file_id": file_id,
|
||||
"artefact_id": artefact_id,
|
||||
"artefact_type": "page_images",
|
||||
"generation_timestamp": self._get_timestamp(),
|
||||
"page_count": page_count,
|
||||
"page_images": page_images,
|
||||
"generation_settings": {
|
||||
"full_image_dpi": self.full_image_dpi,
|
||||
"thumbnail_dpi": self.thumbnail_dpi,
|
||||
"thumbnail_max_width": self.thumbnail_max_width,
|
||||
"thumbnail_max_height": self.thumbnail_max_height,
|
||||
"image_format": self.image_format,
|
||||
"thumbnail_format": self.thumbnail_format
|
||||
},
|
||||
"storage_info": {
|
||||
"total_full_images": page_count,
|
||||
"total_thumbnails": page_count,
|
||||
"estimated_storage_mb": self._estimate_storage_size(page_images)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"Generated {page_count} page images for file_id={file_id}")
|
||||
return artefact_data
|
||||
|
||||
def _generate_full_image(self, page: fitz.Page, page_number: int) -> Tuple[bytes, Dict[str, int]]:
|
||||
"""Generate full resolution page image"""
|
||||
# Create transformation matrix for high DPI
|
||||
mat = fitz.Matrix(self.full_image_dpi / 72, self.full_image_dpi / 72)
|
||||
|
||||
# Render page to pixmap
|
||||
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||
|
||||
# Convert to PNG bytes
|
||||
img_data = pix.tobytes("png")
|
||||
|
||||
dimensions = {
|
||||
"width": pix.width,
|
||||
"height": pix.height
|
||||
}
|
||||
|
||||
pix = None # Free memory
|
||||
|
||||
return img_data, dimensions
|
||||
|
||||
def _generate_thumbnail(self, page: fitz.Page, page_number: int) -> Tuple[bytes, Dict[str, int]]:
|
||||
"""Generate thumbnail image"""
|
||||
# Create transformation matrix for lower DPI
|
||||
mat = fitz.Matrix(self.thumbnail_dpi / 72, self.thumbnail_dpi / 72)
|
||||
|
||||
# Render page to pixmap
|
||||
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||
|
||||
# Convert to PIL Image for resizing
|
||||
img_data = pix.tobytes("png")
|
||||
pil_image = Image.open(io.BytesIO(img_data))
|
||||
|
||||
# Resize to thumbnail dimensions while maintaining aspect ratio
|
||||
pil_image.thumbnail((self.thumbnail_max_width, self.thumbnail_max_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# Convert to WebP for better compression
|
||||
thumbnail_buffer = io.BytesIO()
|
||||
pil_image.save(thumbnail_buffer, format="WEBP", quality=85, optimize=True)
|
||||
thumbnail_data = thumbnail_buffer.getvalue()
|
||||
|
||||
dimensions = {
|
||||
"width": pil_image.width,
|
||||
"height": pil_image.height
|
||||
}
|
||||
|
||||
pix = None # Free memory
|
||||
pil_image.close()
|
||||
|
||||
return thumbnail_data, dimensions
|
||||
|
||||
def _estimate_storage_size(self, page_images: List[Dict]) -> float:
|
||||
"""Estimate total storage size in MB"""
|
||||
total_bytes = 0
|
||||
|
||||
for page_info in page_images:
|
||||
# Estimate full image size (PNG is roughly 3-4 bytes per pixel)
|
||||
full_dims = page_info["full_dimensions"]
|
||||
full_size = full_dims["width"] * full_dims["height"] * 3.5
|
||||
|
||||
# Estimate thumbnail size (WebP is much more efficient)
|
||||
thumb_dims = page_info["thumbnail_dimensions"]
|
||||
thumb_size = thumb_dims["width"] * thumb_dims["height"] * 0.5
|
||||
|
||||
total_bytes += full_size + thumb_size
|
||||
|
||||
return round(total_bytes / (1024 * 1024), 2) # Convert to MB
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Get current timestamp in ISO format"""
|
||||
import datetime
|
||||
return datetime.datetime.utcnow().isoformat() + "Z"
|
||||
|
||||
def generate_single_page_image(self, pdf_bytes: bytes, page_number: int,
|
||||
image_type: str = "full") -> Tuple[bytes, Dict[str, int]]:
|
||||
"""
|
||||
Generate a single page image (for on-demand generation)
|
||||
|
||||
Args:
|
||||
pdf_bytes: PDF file content
|
||||
page_number: Page number (1-based)
|
||||
image_type: "full" or "thumbnail"
|
||||
|
||||
Returns:
|
||||
Tuple of (image_bytes, dimensions)
|
||||
"""
|
||||
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
||||
|
||||
if page_number < 1 or page_number > len(doc):
|
||||
doc.close()
|
||||
raise ValueError(f"Page number {page_number} is out of range (1-{len(doc)})")
|
||||
|
||||
page = doc[page_number - 1] # Convert to 0-based index
|
||||
|
||||
if image_type == "thumbnail":
|
||||
image_data, dimensions = self._generate_thumbnail(page, page_number)
|
||||
else:
|
||||
image_data, dimensions = self._generate_full_image(page, page_number)
|
||||
|
||||
doc.close()
|
||||
return image_data, dimensions
|
||||
|
||||
def create_page_images_artefact(file_id: str, cabinet_id: str, pdf_bytes: bytes) -> Dict[str, Any]:
|
||||
"""
|
||||
Create page images artefact for a PDF document
|
||||
|
||||
Args:
|
||||
file_id: File ID
|
||||
cabinet_id: Cabinet ID
|
||||
pdf_bytes: PDF file content
|
||||
|
||||
Returns:
|
||||
Page images artefact data
|
||||
"""
|
||||
generator = PageImageGenerator()
|
||||
return generator.generate_page_images(file_id, cabinet_id, pdf_bytes)
|
||||
1316
modules/pipeline_controller.py
Normal file
1316
modules/pipeline_controller.py
Normal file
File diff suppressed because it is too large
Load Diff
679
modules/queue_system.py
Normal file
679
modules/queue_system.py
Normal file
@ -0,0 +1,679 @@
|
||||
"""
|
||||
Robust Document Processing Queue System
|
||||
|
||||
This module provides a Redis-based queuing system for document processing tasks
|
||||
to prevent server overload and handle concurrent processing efficiently.
|
||||
|
||||
Features:
|
||||
- Priority-based queuing (high, normal, low)
|
||||
- Rate limiting per service (Tika, Docling, LLM)
|
||||
- Concurrent processing limits
|
||||
- Task retry mechanism with exponential backoff
|
||||
- Dead letter queue for failed tasks
|
||||
- Health monitoring and metrics
|
||||
- Graceful shutdown handling
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
import asyncio
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any, Callable, Union
|
||||
from urllib.parse import urlparse
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
import redis
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
from .redis_manager import get_redis_manager, Environment
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
# Custom exceptions
|
||||
class QueueConnectionError(Exception):
|
||||
"""Raised when the queue system cannot connect to Redis"""
|
||||
pass
|
||||
|
||||
class QueueFullError(Exception):
|
||||
"""Raised when a service queue is at capacity"""
|
||||
pass
|
||||
|
||||
class RateLimitError(Exception):
|
||||
"""Raised when rate limits are exceeded"""
|
||||
pass
|
||||
|
||||
class TaskPriority(Enum):
|
||||
HIGH = "high" # Interactive uploads, user waiting
|
||||
NORMAL = "normal" # Regular batch processing
|
||||
LOW = "low" # Background/cleanup tasks
|
||||
|
||||
class TaskStatus(Enum):
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
RETRYING = "retrying"
|
||||
DEAD = "dead"
|
||||
|
||||
class ServiceType(Enum):
|
||||
TIKA = "tika"
|
||||
DOCLING = "docling"
|
||||
LLM = "llm"
|
||||
SPLIT_MAP = "split_map"
|
||||
DOCUMENT_ANALYSIS = "document_analysis"
|
||||
PAGE_IMAGES = "page_images"
|
||||
|
||||
@dataclass
|
||||
class QueueTask:
|
||||
"""Represents a task in the processing queue."""
|
||||
id: str
|
||||
service: ServiceType
|
||||
priority: TaskPriority
|
||||
file_id: str
|
||||
task_type: str # e.g., "tika_metadata", "docling_frontmatter", "llm_classify"
|
||||
payload: Dict[str, Any]
|
||||
created_at: float
|
||||
scheduled_at: float = None # For delayed/retry tasks
|
||||
attempts: int = 0
|
||||
max_attempts: int = 3
|
||||
timeout: int = 300 # seconds
|
||||
callback_url: Optional[str] = None
|
||||
user_id: Optional[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.scheduled_at is None:
|
||||
self.scheduled_at = time.time()
|
||||
|
||||
class DocumentProcessingQueue:
|
||||
"""Redis-based document processing queue with rate limiting and priorities."""
|
||||
|
||||
def __init__(self, environment: str = None, redis_url: str = None):
|
||||
"""Initialize queue with Redis connection using new Redis manager.
|
||||
|
||||
Args:
|
||||
environment: 'dev', 'prod', or 'test' (auto-detected if not provided)
|
||||
redis_url: Legacy parameter for backward compatibility
|
||||
"""
|
||||
# Auto-detect environment from startup mode if not provided
|
||||
if not environment:
|
||||
environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'
|
||||
|
||||
# Initialize Redis manager for this environment
|
||||
self.redis_manager = get_redis_manager(environment)
|
||||
|
||||
# Initialize Redis environment (ensures service running and connects)
|
||||
if not self.redis_manager.initialize_environment():
|
||||
raise ConnectionError(f"Failed to initialize Redis for {environment} environment")
|
||||
|
||||
# Use the managed Redis client
|
||||
self.redis_client = self.redis_manager.client
|
||||
self.environment = environment
|
||||
|
||||
logger.info(f"🎯 Queue initialized for {environment} environment (db={self.redis_manager.config.db})")
|
||||
|
||||
# Queue configuration
|
||||
self.service_limits = {
|
||||
ServiceType.TIKA: int(os.getenv('QUEUE_TIKA_LIMIT', '3')),
|
||||
ServiceType.DOCLING: int(os.getenv('QUEUE_DOCLING_LIMIT', '2')),
|
||||
ServiceType.LLM: int(os.getenv('QUEUE_LLM_LIMIT', '5')),
|
||||
ServiceType.SPLIT_MAP: int(os.getenv('QUEUE_SPLIT_MAP_LIMIT', '10')),
|
||||
ServiceType.DOCUMENT_ANALYSIS: int(os.getenv('QUEUE_DOCUMENT_ANALYSIS_LIMIT', '5')),
|
||||
ServiceType.PAGE_IMAGES: int(os.getenv('QUEUE_PAGE_IMAGES_LIMIT', '3'))
|
||||
}
|
||||
|
||||
# Rate limiting (requests per minute)
|
||||
self.rate_limits = {
|
||||
ServiceType.TIKA: int(os.getenv('QUEUE_TIKA_RATE', '60')),
|
||||
ServiceType.DOCLING: int(os.getenv('QUEUE_DOCLING_RATE', '30')),
|
||||
ServiceType.LLM: int(os.getenv('QUEUE_LLM_RATE', '120')),
|
||||
ServiceType.SPLIT_MAP: int(os.getenv('QUEUE_SPLIT_MAP_RATE', '100'))
|
||||
}
|
||||
|
||||
# Queue names
|
||||
self.queue_keys = {
|
||||
priority: f"queue:{priority.value}" for priority in TaskPriority
|
||||
}
|
||||
self.processing_key = "processing"
|
||||
self.dead_letter_key = "dead_letter"
|
||||
self.metrics_key = "metrics"
|
||||
|
||||
# Worker control
|
||||
self.workers_running = {}
|
||||
self.shutdown_event = threading.Event()
|
||||
|
||||
logger.info(f"⚙️ Queue service limits: {dict(self.service_limits)}")
|
||||
|
||||
def _get_task_key(self, task_id: str) -> str:
|
||||
"""Get Redis key for task data."""
|
||||
return f"task:{task_id}"
|
||||
|
||||
def _get_service_processing_key(self, service: ServiceType) -> str:
|
||||
"""Get Redis key for service processing counter."""
|
||||
return f"processing:{service.value}"
|
||||
|
||||
def _get_rate_limit_key(self, service: ServiceType) -> str:
|
||||
"""Get Redis key for rate limiting."""
|
||||
return f"rate_limit:{service.value}:{int(time.time() // 60)}"
|
||||
|
||||
def enqueue_task(self,
|
||||
service: ServiceType,
|
||||
task_type: str,
|
||||
file_id: str,
|
||||
payload: Dict[str, Any],
|
||||
priority: TaskPriority = TaskPriority.NORMAL,
|
||||
user_id: str = None,
|
||||
timeout: int = 300,
|
||||
max_attempts: int = 3) -> str:
|
||||
"""
|
||||
Enqueue a new processing task.
|
||||
|
||||
Returns:
|
||||
str: Task ID
|
||||
"""
|
||||
task_id = str(uuid.uuid4())
|
||||
task = QueueTask(
|
||||
id=task_id,
|
||||
service=service,
|
||||
priority=priority,
|
||||
file_id=file_id,
|
||||
task_type=task_type,
|
||||
payload=payload,
|
||||
created_at=time.time(),
|
||||
timeout=timeout,
|
||||
max_attempts=max_attempts,
|
||||
user_id=user_id
|
||||
)
|
||||
|
||||
try:
|
||||
# Store task data (convert enums to strings for Redis)
|
||||
task_dict = asdict(task)
|
||||
task_dict['service'] = task_dict['service'].value
|
||||
task_dict['priority'] = task_dict['priority'].value
|
||||
task_dict['payload'] = json.dumps(task_dict['payload'])
|
||||
# Convert None values to empty strings for Redis
|
||||
for key, value in task_dict.items():
|
||||
if value is None:
|
||||
task_dict[key] = ''
|
||||
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task_id),
|
||||
mapping=task_dict
|
||||
)
|
||||
self.redis_client.expire(self._get_task_key(task_id), 86400) # 24 hours TTL
|
||||
|
||||
# Add to priority queue
|
||||
queue_key = self.queue_keys[priority]
|
||||
self.redis_client.lpush(queue_key, task_id)
|
||||
|
||||
except redis.ConnectionError as e:
|
||||
logger.error(f"Redis connection failed when enqueueing task: {e}")
|
||||
logger.error("Please ensure Redis is running. Start the API server with './start.sh dev' to auto-start Redis.")
|
||||
raise QueueConnectionError(f"Queue system unavailable: Redis connection failed")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to enqueue task {task_id}: {e}")
|
||||
raise
|
||||
|
||||
# Update metrics
|
||||
self._update_metrics("enqueued", service.value, priority.value)
|
||||
|
||||
logger.info(f"Enqueued task {task_id}: {service.value}/{task_type} for file {file_id}")
|
||||
return task_id
|
||||
|
||||
def dequeue_task(self, timeout: int = 10) -> Optional[QueueTask]:
|
||||
"""
|
||||
Dequeue next available task respecting service limits and rate limits.
|
||||
|
||||
Args:
|
||||
timeout: Blocking timeout in seconds
|
||||
|
||||
Returns:
|
||||
QueueTask or None if no task available
|
||||
"""
|
||||
# Check all priority queues in order
|
||||
for priority in [TaskPriority.HIGH, TaskPriority.NORMAL, TaskPriority.LOW]:
|
||||
queue_key = self.queue_keys[priority]
|
||||
|
||||
# Non-blocking pop to check availability
|
||||
task_id = self.redis_client.rpop(queue_key)
|
||||
if not task_id:
|
||||
continue
|
||||
|
||||
# Get task data
|
||||
task_data = self.redis_client.hgetall(self._get_task_key(task_id))
|
||||
if not task_data:
|
||||
logger.warning(f"Task {task_id} data not found, skipping")
|
||||
continue
|
||||
|
||||
# Reconstruct task object (ignore non-dataclass keys like 'status', 'result', timestamps)
|
||||
task_data['service'] = ServiceType(task_data['service'])
|
||||
task_data['priority'] = TaskPriority(task_data['priority'])
|
||||
task_data['created_at'] = float(task_data['created_at'])
|
||||
task_data['scheduled_at'] = float(task_data['scheduled_at'])
|
||||
task_data['attempts'] = int(task_data['attempts'])
|
||||
task_data['max_attempts'] = int(task_data['max_attempts'])
|
||||
task_data['timeout'] = int(task_data['timeout'])
|
||||
task_data['payload'] = json.loads(task_data['payload'])
|
||||
|
||||
# Drop extraneous keys that may be present in Redis
|
||||
for k in ['status', 'result', 'completed_at', 'failed_at', 'last_error', 'final_error']:
|
||||
task_data.pop(k, None)
|
||||
|
||||
task = QueueTask(**task_data)
|
||||
|
||||
# Check if task is ready (for delayed/retry tasks)
|
||||
if task.scheduled_at > time.time():
|
||||
# Put back in queue for later
|
||||
self.redis_client.lpush(queue_key, task_id)
|
||||
continue
|
||||
|
||||
# Enforce simple dependency ordering using optional depends_on array in payload
|
||||
try:
|
||||
depends_on = []
|
||||
if isinstance(task.payload, dict):
|
||||
depends_on = task.payload.get('depends_on') or []
|
||||
if isinstance(depends_on, list) and len(depends_on) > 0:
|
||||
logger.info(f"Checking {len(depends_on)} dependencies for task {task_id}")
|
||||
unmet = []
|
||||
for dep_id in depends_on:
|
||||
if not dep_id:
|
||||
continue
|
||||
dep_key = self._get_task_key(dep_id)
|
||||
dep_status = self.redis_client.hget(dep_key, 'status') or TaskStatus.PENDING.value
|
||||
if dep_status != TaskStatus.COMPLETED.value:
|
||||
unmet.append((dep_id, dep_status))
|
||||
if len(unmet) > 0:
|
||||
# Reschedule this task a bit later to avoid tight loops
|
||||
next_time = time.time() + 10
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task_id),
|
||||
mapping={'scheduled_at': next_time}
|
||||
)
|
||||
# Put back for later processing
|
||||
self.redis_client.lpush(queue_key, task_id)
|
||||
logger.info(f"Deferring task {task_id} due to unmet dependencies: {unmet}")
|
||||
continue
|
||||
else:
|
||||
logger.info(f"All {len(depends_on)} dependencies satisfied for task {task_id}")
|
||||
except Exception as dep_e:
|
||||
logger.warning(f"Dependency check failed for task {task_id}: {dep_e}")
|
||||
|
||||
# Check service limits
|
||||
service_processing_key = self._get_service_processing_key(task.service)
|
||||
current_processing = int(self.redis_client.get(service_processing_key) or 0)
|
||||
|
||||
if current_processing >= self.service_limits[task.service]:
|
||||
# Put back in queue with delay to prevent infinite loops
|
||||
logger.warning(f"🚨 SERVICE LIMIT: Task {task_id} re-queued due to service limit exceeded: {current_processing}/{self.service_limits[task.service]} for {task.service.value}")
|
||||
|
||||
# Add delay before re-queueing to prevent tight loops
|
||||
next_time = time.time() + 5 # Wait 5 seconds before retrying
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task_id),
|
||||
mapping={'scheduled_at': next_time}
|
||||
)
|
||||
self.redis_client.lpush(queue_key, task_id)
|
||||
continue
|
||||
|
||||
# Check rate limits
|
||||
rate_key = self._get_rate_limit_key(task.service)
|
||||
current_rate = int(self.redis_client.get(rate_key) or 0)
|
||||
|
||||
if current_rate >= self.rate_limits[task.service]:
|
||||
# Put back in queue with delay to prevent infinite loops
|
||||
logger.warning(f"🚨 RATE LIMIT: Task {task_id} re-queued due to rate limit exceeded: {current_rate}/{self.rate_limits[task.service]} for {task.service.value}")
|
||||
|
||||
# Add delay before re-queueing to prevent tight loops
|
||||
next_time = time.time() + 60 # Wait 60 seconds for rate limit reset
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task_id),
|
||||
mapping={'scheduled_at': next_time}
|
||||
)
|
||||
self.redis_client.lpush(queue_key, task_id)
|
||||
continue
|
||||
|
||||
# Task can be processed
|
||||
# Increment processing counter
|
||||
self.redis_client.incr(service_processing_key)
|
||||
self.redis_client.expire(service_processing_key, 3600) # 1 hour
|
||||
|
||||
# Increment rate limit counter
|
||||
self.redis_client.incr(rate_key)
|
||||
self.redis_client.expire(rate_key, 60) # 1 minute
|
||||
|
||||
# Add to processing set
|
||||
self.redis_client.hset(
|
||||
self.processing_key,
|
||||
task_id,
|
||||
json.dumps({
|
||||
'service': task.service.value,
|
||||
'started_at': time.time(),
|
||||
'worker_id': threading.current_thread().ident
|
||||
})
|
||||
)
|
||||
|
||||
# Update metrics
|
||||
self._update_metrics("dequeued", task.service.value, task.priority.value)
|
||||
|
||||
logger.debug(f"Dequeued task {task_id}: {task.service.value}/{task.task_type}")
|
||||
return task
|
||||
|
||||
return None
|
||||
|
||||
def complete_task(self, task: QueueTask, result: Dict[str, Any] = None):
|
||||
"""Mark task as completed and clean up."""
|
||||
# Remove from processing
|
||||
self.redis_client.hdel(self.processing_key, task.id)
|
||||
|
||||
# Decrement processing counter
|
||||
service_processing_key = self._get_service_processing_key(task.service)
|
||||
self.redis_client.decr(service_processing_key)
|
||||
|
||||
# Update task status
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task.id),
|
||||
mapping={
|
||||
'status': TaskStatus.COMPLETED.value,
|
||||
'completed_at': time.time(),
|
||||
'result': json.dumps(result or {})
|
||||
}
|
||||
)
|
||||
|
||||
# Update metrics
|
||||
self._update_metrics("completed", task.service.value, task.priority.value)
|
||||
|
||||
logger.info(f"Completed task {task.id}: {task.service.value}/{task.task_type}")
|
||||
|
||||
def fail_task(self, task: QueueTask, error: str, retry: bool = True):
|
||||
"""Handle task failure with retry logic."""
|
||||
# Remove from processing
|
||||
self.redis_client.hdel(self.processing_key, task.id)
|
||||
|
||||
# Decrement processing counter
|
||||
service_processing_key = self._get_service_processing_key(task.service)
|
||||
self.redis_client.decr(service_processing_key)
|
||||
|
||||
task.attempts += 1
|
||||
|
||||
if retry and task.attempts < task.max_attempts:
|
||||
# Enhanced retry logic with progress-aware delays for comparison tasks
|
||||
if task.task_type == 'docling_comparison_analysis':
|
||||
# Special handling for comparison analysis tasks
|
||||
payload = getattr(task, 'payload', {})
|
||||
|
||||
# Check if this is a progress-aware retry
|
||||
if hasattr(error, 'is_progress_retry') and error.is_progress_retry:
|
||||
# Active progress - shorter delay (30s to 2 minutes)
|
||||
delay = min(120, 30 + task.attempts * 15)
|
||||
logger.info(f"Comparison task {task.id}: Active progress detected, shorter retry delay: {delay}s")
|
||||
elif hasattr(error, 'is_alignment_retry') and error.is_alignment_retry:
|
||||
# Alignment issues - medium delay (1-3 minutes)
|
||||
delay = min(180, 60 + task.attempts * 20)
|
||||
logger.info(f"Comparison task {task.id}: Alignment retry, medium delay: {delay}s")
|
||||
elif hasattr(error, 'is_stalled_retry') and error.is_stalled_retry:
|
||||
# Stalled processing - longer delay (2-10 minutes)
|
||||
delay = min(600, 120 + task.attempts * 30)
|
||||
logger.info(f"Comparison task {task.id}: Stalled retry, longer delay: {delay}s")
|
||||
else:
|
||||
# Default comparison delay - extended for large PDFs (5-20 minutes)
|
||||
delay = min(1200, 300 + task.attempts * 60)
|
||||
logger.info(f"Comparison task {task.id}: Standard retry, extended delay: {delay}s")
|
||||
|
||||
# Update progress tracking in payload for next attempt
|
||||
if hasattr(error, 'current_progress'):
|
||||
payload['previous_progress'] = error.current_progress
|
||||
# Update the task payload in Redis
|
||||
import json
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task.id),
|
||||
'payload', json.dumps(payload)
|
||||
)
|
||||
else:
|
||||
# Standard retry with exponential backoff for other tasks
|
||||
delay = min(300, 2 ** task.attempts * 10) # Max 5 minutes
|
||||
|
||||
task.scheduled_at = time.time() + delay
|
||||
task.status = TaskStatus.RETRYING
|
||||
|
||||
# Update task data
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task.id),
|
||||
mapping={
|
||||
'attempts': task.attempts,
|
||||
'scheduled_at': task.scheduled_at,
|
||||
'status': TaskStatus.RETRYING.value,
|
||||
'last_error': error
|
||||
}
|
||||
)
|
||||
|
||||
# Re-queue for retry
|
||||
queue_key = self.queue_keys[task.priority]
|
||||
self.redis_client.lpush(queue_key, task.id)
|
||||
|
||||
# Update metrics
|
||||
self._update_metrics("retried", task.service.value, task.priority.value)
|
||||
|
||||
logger.warning(f"Retrying task {task.id} in {delay}s (attempt {task.attempts}/{task.max_attempts}): {error}")
|
||||
else:
|
||||
# Move to dead letter queue
|
||||
task.status = TaskStatus.DEAD
|
||||
|
||||
self.redis_client.hset(
|
||||
self._get_task_key(task.id),
|
||||
mapping={
|
||||
'attempts': task.attempts,
|
||||
'status': TaskStatus.DEAD.value,
|
||||
'failed_at': time.time(),
|
||||
'final_error': error
|
||||
}
|
||||
)
|
||||
|
||||
self.redis_client.lpush(self.dead_letter_key, task.id)
|
||||
|
||||
# Update metrics
|
||||
self._update_metrics("failed", task.service.value, task.priority.value)
|
||||
|
||||
logger.error(f"Task {task.id} moved to dead letter queue after {task.attempts} attempts: {error}")
|
||||
|
||||
def get_queue_stats(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive queue statistics."""
|
||||
stats = {
|
||||
'queues': {},
|
||||
'processing': {},
|
||||
'service_limits': dict(self.service_limits),
|
||||
'rate_limits': dict(self.rate_limits),
|
||||
'dead_letter_count': self.redis_client.llen(self.dead_letter_key)
|
||||
}
|
||||
|
||||
# Queue lengths
|
||||
for priority in TaskPriority:
|
||||
queue_key = self.queue_keys[priority]
|
||||
stats['queues'][priority.value] = self.redis_client.llen(queue_key)
|
||||
|
||||
# Processing counts
|
||||
for service in ServiceType:
|
||||
service_key = self._get_service_processing_key(service)
|
||||
stats['processing'][service.value] = int(self.redis_client.get(service_key) or 0)
|
||||
|
||||
# Total processing
|
||||
stats['total_processing'] = self.redis_client.hlen(self.processing_key)
|
||||
|
||||
return stats
|
||||
|
||||
def _update_metrics(self, action: str, service: str, priority: str):
|
||||
"""Update queue metrics."""
|
||||
timestamp = int(time.time())
|
||||
metric_key = f"{self.metrics_key}:{action}:{service}:{priority}:{timestamp // 60}"
|
||||
self.redis_client.incr(metric_key)
|
||||
self.redis_client.expire(metric_key, 3600) # 1 hour
|
||||
|
||||
def start_worker(self, worker_id: str = None, services: List[ServiceType] = None):
|
||||
"""Start a queue worker thread."""
|
||||
if worker_id is None:
|
||||
worker_id = f"worker-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
if services is None:
|
||||
services = list(ServiceType)
|
||||
|
||||
def worker_loop():
|
||||
logger.info(f"Starting worker {worker_id} for services: {[s.value for s in services]}")
|
||||
|
||||
while not self.shutdown_event.is_set():
|
||||
try:
|
||||
task = self.dequeue_task(timeout=5)
|
||||
if task is None:
|
||||
continue
|
||||
|
||||
# DEBUG: Log task details immediately after dequeue
|
||||
logger.info(f"🔍 WORKER DEBUG: Dequeued task {task.id}, service={task.service.value}, task_type={task.task_type}")
|
||||
logger.info(f"🔍 WORKER DEBUG: Worker {worker_id} handles services: {[s.value for s in services]}")
|
||||
|
||||
if task.service not in services:
|
||||
# Put back in queue if worker doesn't handle this service
|
||||
# But first clean up the processing state to avoid infinite loops
|
||||
logger.warning(f"🚨 WORKER DEBUG: Task {task.id} service {task.service.value} NOT in worker services {[s.value for s in services]}")
|
||||
self.redis_client.hdel(self.processing_key, task.id)
|
||||
# NOTE: Do NOT decrement service_processing_key here - task will be re-processed by correct worker
|
||||
# service_processing_key = self._get_service_processing_key(task.service)
|
||||
# self.redis_client.decr(service_processing_key) # REMOVED: Caused negative counters
|
||||
|
||||
queue_key = self.queue_keys[task.priority]
|
||||
self.redis_client.lpush(queue_key, task.id)
|
||||
logger.warning(f"🚨 WORKER DEBUG: Task {task.id} re-queued due to service mismatch")
|
||||
continue
|
||||
|
||||
# DEBUG: Confirm we're about to process
|
||||
logger.info(f"✅ WORKER DEBUG: About to process task {task.id}")
|
||||
|
||||
# Process the task
|
||||
self._process_task(task)
|
||||
|
||||
# DEBUG: Confirm processing completed
|
||||
logger.info(f"✅ WORKER DEBUG: Finished processing task {task.id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Worker {worker_id} error: {e}")
|
||||
time.sleep(1)
|
||||
|
||||
logger.info(f"Worker {worker_id} shutting down")
|
||||
|
||||
thread = threading.Thread(target=worker_loop, name=f"QueueWorker-{worker_id}")
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
self.workers_running[worker_id] = thread
|
||||
return worker_id
|
||||
|
||||
def _process_task(self, task: QueueTask):
|
||||
"""Process a single task (to be overridden by specific implementations)."""
|
||||
logger.warning(f"No processor implemented for task {task.id}: {task.service.value}/{task.task_type}")
|
||||
self.fail_task(task, "No processor implemented", retry=False)
|
||||
|
||||
def shutdown(self, timeout: int = 30):
|
||||
"""Gracefully shutdown all workers."""
|
||||
logger.info("Shutting down queue workers...")
|
||||
self.shutdown_event.set()
|
||||
|
||||
# Wait for workers to finish
|
||||
for worker_id, thread in self.workers_running.items():
|
||||
thread.join(timeout=timeout)
|
||||
if thread.is_alive():
|
||||
logger.warning(f"Worker {worker_id} did not shut down gracefully")
|
||||
|
||||
self.workers_running.clear()
|
||||
logger.info("Queue shutdown complete")
|
||||
|
||||
# Global queue instance
|
||||
_queue_instance = None
|
||||
|
||||
def get_queue() -> DocumentProcessingQueue:
|
||||
"""Get the global queue instance."""
|
||||
global _queue_instance
|
||||
if _queue_instance is None:
|
||||
_queue_instance = DocumentProcessingQueue()
|
||||
return _queue_instance
|
||||
|
||||
# Convenience functions for common operations
|
||||
def enqueue_tika_task(file_id: str, payload: Dict[str, Any], priority: TaskPriority = TaskPriority.NORMAL) -> str:
|
||||
"""Enqueue a Tika processing task."""
|
||||
return get_queue().enqueue_task(
|
||||
service=ServiceType.TIKA,
|
||||
task_type="metadata_extraction",
|
||||
file_id=file_id,
|
||||
payload=payload,
|
||||
priority=priority,
|
||||
timeout=int(os.getenv('TIKA_TIMEOUT', '300'))
|
||||
)
|
||||
|
||||
def enqueue_docling_task(file_id: str, task_type: str, payload: Dict[str, Any],
|
||||
priority: TaskPriority = TaskPriority.NORMAL, timeout: int = 1800,
|
||||
max_attempts: int = None) -> str:
|
||||
"""Enqueue a Docling processing task with intelligent retry limits."""
|
||||
|
||||
# Auto-configure max_attempts based on task type and payload
|
||||
if max_attempts is None:
|
||||
if task_type == 'docling_comparison_analysis':
|
||||
# Use the max_retry_attempts from payload, or default to high limit for comparisons
|
||||
max_attempts = payload.get('max_retry_attempts', 50)
|
||||
else:
|
||||
# Standard retry limit for other docling tasks
|
||||
max_attempts = 3
|
||||
|
||||
return get_queue().enqueue_task(
|
||||
service=ServiceType.DOCLING,
|
||||
task_type=task_type,
|
||||
file_id=file_id,
|
||||
payload=payload,
|
||||
priority=priority,
|
||||
timeout=timeout,
|
||||
max_attempts=max_attempts
|
||||
)
|
||||
|
||||
def enqueue_llm_task(file_id: str, task_type: str, payload: Dict[str, Any],
|
||||
priority: TaskPriority = TaskPriority.NORMAL) -> str:
|
||||
"""Enqueue an LLM processing task."""
|
||||
return get_queue().enqueue_task(
|
||||
service=ServiceType.LLM,
|
||||
task_type=task_type,
|
||||
file_id=file_id,
|
||||
payload=payload,
|
||||
priority=priority,
|
||||
timeout=int(os.getenv('LLM_TIMEOUT', '180'))
|
||||
)
|
||||
|
||||
def enqueue_split_map_task(file_id: str, payload: Dict[str, Any],
|
||||
priority: TaskPriority = TaskPriority.NORMAL) -> str:
|
||||
"""Enqueue a split map generation task."""
|
||||
return get_queue().enqueue_task(
|
||||
service=ServiceType.SPLIT_MAP,
|
||||
task_type="generate_split_map",
|
||||
file_id=file_id,
|
||||
payload=payload,
|
||||
priority=priority,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
def enqueue_document_analysis_task(file_id: str, payload: Dict[str, Any],
|
||||
priority: TaskPriority = TaskPriority.NORMAL) -> str:
|
||||
"""Enqueue a document structure analysis task."""
|
||||
return get_queue().enqueue_task(
|
||||
service=ServiceType.DOCUMENT_ANALYSIS,
|
||||
task_type="document_structure_analysis",
|
||||
file_id=file_id,
|
||||
payload=payload,
|
||||
priority=priority,
|
||||
timeout=int(os.getenv('DOCUMENT_ANALYSIS_TIMEOUT', '300'))
|
||||
)
|
||||
|
||||
def enqueue_page_images_task(file_id: str, payload: Dict[str, Any],
|
||||
priority: TaskPriority = TaskPriority.NORMAL) -> str:
|
||||
"""Enqueue a page images generation task."""
|
||||
return get_queue().enqueue_task(
|
||||
service=ServiceType.PAGE_IMAGES,
|
||||
task_type="generate_page_images",
|
||||
file_id=file_id,
|
||||
payload=payload,
|
||||
priority=priority,
|
||||
timeout=int(os.getenv('PAGE_IMAGES_TIMEOUT', '600'))
|
||||
)
|
||||
523
modules/redis_manager.py
Normal file
523
modules/redis_manager.py
Normal file
@ -0,0 +1,523 @@
|
||||
"""
|
||||
Comprehensive Redis Management System
|
||||
=====================================
|
||||
|
||||
Handles environment-specific Redis configuration, service management,
|
||||
task recovery, and health monitoring for ClassroomCopilot.
|
||||
|
||||
Features:
|
||||
- Environment isolation (dev/prod/test databases)
|
||||
- Automatic service management and health checks
|
||||
- Task recovery and persistence strategies
|
||||
- Graceful degradation and error handling
|
||||
"""
|
||||
|
||||
import os
|
||||
import redis
|
||||
import subprocess
|
||||
import time
|
||||
import signal
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Environment(Enum):
|
||||
DEV = "dev"
|
||||
PROD = "prod"
|
||||
TEST = "test"
|
||||
|
||||
@dataclass
|
||||
class RedisConfig:
|
||||
host: str
|
||||
port: int
|
||||
db: int
|
||||
password: Optional[str]
|
||||
ssl: bool
|
||||
persist: bool
|
||||
task_ttl: int
|
||||
url: str
|
||||
|
||||
class RedisManager:
|
||||
"""Comprehensive Redis management with environment isolation and recovery."""
|
||||
|
||||
def __init__(self, environment: Environment = Environment.DEV):
|
||||
self.environment = environment
|
||||
self.config = self._load_config()
|
||||
self.client: Optional[redis.Redis] = None
|
||||
self._subprocess: Optional[subprocess.Popen] = None
|
||||
self._health_check_enabled = True
|
||||
|
||||
logger.info(f"🔧 Redis Manager initialized for {environment.value} environment")
|
||||
logger.info(f"📡 Target: {self.config.host}:{self.config.port}/db{self.config.db}")
|
||||
|
||||
def _load_config(self) -> RedisConfig:
|
||||
"""Load environment-specific Redis configuration."""
|
||||
|
||||
# Base configuration
|
||||
host = os.getenv('REDIS_HOST', 'localhost')
|
||||
port = int(os.getenv('REDIS_PORT', '6379'))
|
||||
password = os.getenv('REDIS_PASSWORD') or None
|
||||
ssl = os.getenv('REDIS_SSL', 'false').lower() == 'true'
|
||||
|
||||
# Environment-specific settings
|
||||
if self.environment == Environment.DEV:
|
||||
db = int(os.getenv('REDIS_DB_DEV', '0'))
|
||||
persist = os.getenv('REDIS_PERSIST_DEV', 'false').lower() == 'true'
|
||||
task_ttl = int(os.getenv('REDIS_TASK_TTL_DEV', '3600'))
|
||||
elif self.environment == Environment.PROD:
|
||||
db = int(os.getenv('REDIS_DB_PROD', '1'))
|
||||
persist = os.getenv('REDIS_PERSIST_PROD', 'true').lower() == 'true'
|
||||
task_ttl = int(os.getenv('REDIS_TASK_TTL_PROD', '86400'))
|
||||
else: # TEST
|
||||
db = int(os.getenv('REDIS_DB_TEST', '2'))
|
||||
persist = False
|
||||
task_ttl = int(os.getenv('REDIS_TASK_TTL_TEST', '1800'))
|
||||
|
||||
# Construct URL
|
||||
auth_part = f":{password}@" if password else ""
|
||||
url = f"redis://{auth_part}{host}:{port}/{db}"
|
||||
|
||||
return RedisConfig(
|
||||
host=host,
|
||||
port=port,
|
||||
db=db,
|
||||
password=password,
|
||||
ssl=ssl,
|
||||
persist=persist,
|
||||
task_ttl=task_ttl,
|
||||
url=url
|
||||
)
|
||||
|
||||
def ensure_service_running(self) -> bool:
|
||||
"""Ensure Redis service is running, start if needed."""
|
||||
|
||||
# Check if Redis is already running
|
||||
if self._is_redis_running():
|
||||
logger.info("✅ Redis service already running")
|
||||
return True
|
||||
|
||||
# Try to start Redis service
|
||||
logger.info("🚀 Starting Redis service...")
|
||||
|
||||
# Try systemctl first (Linux production)
|
||||
if self._try_systemctl_start():
|
||||
return True
|
||||
|
||||
# Try brew services (macOS)
|
||||
if self._try_brew_start():
|
||||
return True
|
||||
|
||||
# Try direct Redis server start
|
||||
if self._try_direct_start():
|
||||
return True
|
||||
|
||||
# Try Docker fallback
|
||||
if self._try_docker_start():
|
||||
return True
|
||||
|
||||
logger.error("❌ Failed to start Redis service with all methods")
|
||||
return False
|
||||
|
||||
def _is_redis_running(self) -> bool:
|
||||
"""Check if Redis is accessible."""
|
||||
try:
|
||||
test_client = redis.Redis(
|
||||
host=self.config.host,
|
||||
port=self.config.port,
|
||||
socket_connect_timeout=2,
|
||||
socket_timeout=2
|
||||
)
|
||||
test_client.ping()
|
||||
test_client.close()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _try_systemctl_start(self) -> bool:
|
||||
"""Try starting Redis with systemctl."""
|
||||
try:
|
||||
if not os.path.exists('/usr/bin/systemctl'):
|
||||
return False
|
||||
|
||||
subprocess.run(['sudo', 'systemctl', 'start', 'redis'],
|
||||
check=True, capture_output=True, timeout=10)
|
||||
time.sleep(2)
|
||||
return self._is_redis_running()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _try_brew_start(self) -> bool:
|
||||
"""Try starting Redis with brew services."""
|
||||
try:
|
||||
if not os.path.exists('/opt/homebrew/bin/brew'):
|
||||
return False
|
||||
|
||||
subprocess.run(['/opt/homebrew/bin/brew', 'services', 'start', 'redis'],
|
||||
check=True, capture_output=True, timeout=10)
|
||||
time.sleep(2)
|
||||
return self._is_redis_running()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _try_direct_start(self) -> bool:
|
||||
"""Try starting Redis server directly."""
|
||||
try:
|
||||
# Find redis-server binary
|
||||
redis_cmd = None
|
||||
for path in ['/opt/homebrew/bin/redis-server', '/usr/local/bin/redis-server', 'redis-server']:
|
||||
try:
|
||||
subprocess.run([path, '--version'], capture_output=True, check=True, timeout=5)
|
||||
redis_cmd = path
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not redis_cmd:
|
||||
return False
|
||||
|
||||
# Start Redis with appropriate config
|
||||
config_args = [
|
||||
redis_cmd,
|
||||
'--port', str(self.config.port),
|
||||
'--bind', self.config.host,
|
||||
'--protected-mode', 'no',
|
||||
'--loglevel', 'notice',
|
||||
'--daemonize', 'yes' # Run as daemon
|
||||
]
|
||||
|
||||
# Add persistence settings
|
||||
if not self.config.persist:
|
||||
config_args.extend(['--save', '', '--appendonly', 'no'])
|
||||
else:
|
||||
config_args.extend(['--save', '60 1000', '--appendonly', 'yes'])
|
||||
|
||||
subprocess.run(config_args, check=True, capture_output=True, timeout=10)
|
||||
time.sleep(3)
|
||||
return self._is_redis_running()
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Direct Redis start failed: {e}")
|
||||
return False
|
||||
|
||||
def _try_docker_start(self) -> bool:
|
||||
"""Try starting Redis with Docker."""
|
||||
try:
|
||||
subprocess.run(['docker', '--version'], capture_output=True, check=True, timeout=5)
|
||||
|
||||
# Check if Redis container already exists
|
||||
result = subprocess.run(
|
||||
['docker', 'ps', '-a', '--filter', 'name=classroomcopilot-redis', '--format', '{{.Names}}'],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
|
||||
if 'classroomcopilot-redis' in result.stdout:
|
||||
# Start existing container
|
||||
subprocess.run(['docker', 'start', 'classroomcopilot-redis'],
|
||||
check=True, capture_output=True, timeout=10)
|
||||
else:
|
||||
# Create new container
|
||||
docker_cmd = [
|
||||
'docker', 'run', '-d',
|
||||
'--name', 'classroomcopilot-redis',
|
||||
'-p', f'{self.config.port}:6379',
|
||||
'redis:alpine'
|
||||
]
|
||||
|
||||
if not self.config.persist:
|
||||
docker_cmd.extend(['redis-server', '--save', '', '--appendonly', 'no'])
|
||||
|
||||
subprocess.run(docker_cmd, check=True, capture_output=True, timeout=30)
|
||||
|
||||
time.sleep(3)
|
||||
return self._is_redis_running()
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Docker Redis start failed: {e}")
|
||||
return False
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""Establish Redis connection with retry logic."""
|
||||
max_attempts = int(os.getenv('REDIS_MAX_RETRY_ATTEMPTS', '3'))
|
||||
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
logger.info(f"🔌 Connecting to Redis... (attempt {attempt}/{max_attempts})")
|
||||
|
||||
self.client = redis.Redis(
|
||||
host=self.config.host,
|
||||
port=self.config.port,
|
||||
db=self.config.db,
|
||||
password=self.config.password,
|
||||
decode_responses=True,
|
||||
socket_connect_timeout=5,
|
||||
socket_timeout=5,
|
||||
retry_on_timeout=True
|
||||
)
|
||||
|
||||
# Test connection
|
||||
self.client.ping()
|
||||
|
||||
logger.info(f"✅ Connected to Redis {self.config.host}:{self.config.port}/db{self.config.db}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"❌ Connection attempt {attempt} failed: {e}")
|
||||
if attempt < max_attempts:
|
||||
logger.info("⏳ Retrying in 2 seconds...")
|
||||
time.sleep(2)
|
||||
else:
|
||||
logger.error("💥 All connection attempts failed")
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def initialize_environment(self) -> bool:
|
||||
"""Initialize Redis environment based on mode."""
|
||||
|
||||
if not self.ensure_service_running():
|
||||
logger.error("Cannot initialize - Redis service not available")
|
||||
return False
|
||||
|
||||
if not self.connect():
|
||||
logger.error("Cannot initialize - Connection failed")
|
||||
return False
|
||||
|
||||
if self.environment == Environment.DEV:
|
||||
return self._initialize_dev_environment()
|
||||
elif self.environment == Environment.PROD:
|
||||
return self._initialize_prod_environment()
|
||||
else:
|
||||
return self._initialize_test_environment()
|
||||
|
||||
def _initialize_dev_environment(self) -> bool:
|
||||
"""Initialize development environment - clean slate."""
|
||||
try:
|
||||
logger.info("🧹 DEV MODE: Clearing all data for clean startup...")
|
||||
|
||||
# Get all keys in this database
|
||||
all_keys = self.client.keys('*')
|
||||
|
||||
if all_keys:
|
||||
# Nuclear option - clear everything in this DB
|
||||
self.client.flushdb()
|
||||
logger.info(f"💥 DEV MODE: Nuked {len(all_keys)} keys for clean startup")
|
||||
else:
|
||||
logger.info("✅ DEV MODE: Database already clean")
|
||||
|
||||
# Set up development-specific config
|
||||
try:
|
||||
self.client.config_set('save', '') # Disable RDB snapshots
|
||||
self.client.config_set('appendonly', 'no') # Disable AOF
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not set Redis config (may not have permissions): {e}")
|
||||
|
||||
logger.info("🎯 DEV MODE: Environment initialized for fast iteration")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize dev environment: {e}")
|
||||
return False
|
||||
|
||||
def _initialize_prod_environment(self) -> bool:
|
||||
"""Initialize production environment - preserve data."""
|
||||
try:
|
||||
logger.info("🏭 PROD MODE: Initializing with data preservation...")
|
||||
|
||||
# Check for existing tasks and report
|
||||
task_keys = self.client.keys('task:*')
|
||||
queue_keys = self.client.keys('queue:*')
|
||||
processing_keys = self.client.keys('processing:*')
|
||||
|
||||
logger.info(f"📊 PROD MODE: Found {len(task_keys)} tasks, {len(queue_keys)} queues, {len(processing_keys)} processing counters")
|
||||
|
||||
# Enable persistence
|
||||
if self.config.persist:
|
||||
try:
|
||||
self.client.config_set('save', '60 1000') # Save every 60s if ≥1000 changes
|
||||
self.client.config_set('appendonly', 'yes') # Enable AOF
|
||||
logger.info("💾 PROD MODE: Persistence enabled")
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not set Redis config (may not have permissions): {e}")
|
||||
|
||||
# Recover any stuck tasks
|
||||
self._recover_stuck_tasks()
|
||||
|
||||
logger.info("✅ PROD MODE: Environment initialized with data recovery")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize prod environment: {e}")
|
||||
return False
|
||||
|
||||
def _initialize_test_environment(self) -> bool:
|
||||
"""Initialize test environment - isolated and clean."""
|
||||
try:
|
||||
logger.info("🧪 TEST MODE: Setting up isolated test environment...")
|
||||
|
||||
# Clear test database
|
||||
self.client.flushdb()
|
||||
|
||||
# Disable persistence for speed
|
||||
try:
|
||||
self.client.config_set('save', '')
|
||||
self.client.config_set('appendonly', 'no')
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not set Redis config (may not have permissions): {e}")
|
||||
|
||||
logger.info("✅ TEST MODE: Clean, isolated environment ready")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize test environment: {e}")
|
||||
return False
|
||||
|
||||
def _recover_stuck_tasks(self):
|
||||
"""Recover tasks that were processing when system shut down."""
|
||||
try:
|
||||
logger.info("🔄 Recovering stuck tasks from previous session...")
|
||||
|
||||
# Get all processing tasks
|
||||
processing_data = self.client.hgetall('processing')
|
||||
|
||||
if not processing_data:
|
||||
logger.info("✅ No stuck tasks to recover")
|
||||
return
|
||||
|
||||
recovered_count = 0
|
||||
|
||||
for task_id, task_info in processing_data.items():
|
||||
try:
|
||||
# Parse task info
|
||||
info = json.loads(task_info)
|
||||
|
||||
# Check if task still exists
|
||||
task_key = f"task:{task_id}"
|
||||
if not self.client.exists(task_key):
|
||||
logger.warning(f"⚠️ Task {task_id} data missing, removing from processing")
|
||||
self.client.hdel('processing', task_id)
|
||||
continue
|
||||
|
||||
# Reset task to pending and re-queue
|
||||
self.client.hset(task_key, 'status', 'pending')
|
||||
self.client.hdel(task_key, 'started_at')
|
||||
|
||||
# Add back to appropriate queue
|
||||
priority = info.get('priority', 'normal')
|
||||
queue_key = f"queue:{priority}"
|
||||
self.client.lpush(queue_key, task_id)
|
||||
|
||||
# Remove from processing
|
||||
self.client.hdel('processing', task_id)
|
||||
|
||||
# Reset service counters
|
||||
service = info.get('service')
|
||||
if service:
|
||||
service_key = f"processing:{service}"
|
||||
current_count = int(self.client.get(service_key) or 0)
|
||||
if current_count > 0:
|
||||
self.client.decr(service_key)
|
||||
|
||||
recovered_count += 1
|
||||
logger.info(f"🔄 Recovered stuck task {task_id} ({service}/{priority})")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to recover task {task_id}: {e}")
|
||||
# Remove problematic entry
|
||||
self.client.hdel('processing', task_id)
|
||||
|
||||
logger.info(f"✅ Recovered {recovered_count} stuck tasks")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Task recovery failed: {e}")
|
||||
|
||||
def health_check(self) -> Dict[str, Any]:
|
||||
"""Comprehensive health check."""
|
||||
health = {
|
||||
'status': 'healthy',
|
||||
'environment': self.environment.value,
|
||||
'database': self.config.db,
|
||||
'connection': False,
|
||||
'memory_usage': None,
|
||||
'queue_stats': {},
|
||||
'error': None
|
||||
}
|
||||
|
||||
try:
|
||||
if not self.client:
|
||||
raise Exception("No Redis connection")
|
||||
|
||||
# Test connection
|
||||
self.client.ping()
|
||||
health['connection'] = True
|
||||
|
||||
# Get memory usage
|
||||
info = self.client.info('memory')
|
||||
health['memory_usage'] = {
|
||||
'used_memory_human': info.get('used_memory_human', 'unknown'),
|
||||
'used_memory_peak_human': info.get('used_memory_peak_human', 'unknown')
|
||||
}
|
||||
|
||||
# Get queue statistics
|
||||
health['queue_stats'] = {
|
||||
'total_keys': len(self.client.keys('*')),
|
||||
'tasks': len(self.client.keys('task:*')),
|
||||
'queues': {
|
||||
'high': self.client.llen('queue:high'),
|
||||
'normal': self.client.llen('queue:normal'),
|
||||
'low': self.client.llen('queue:low')
|
||||
},
|
||||
'processing': self.client.hlen('processing'),
|
||||
'dead_letter': self.client.llen('dead_letter')
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
health['status'] = 'unhealthy'
|
||||
health['error'] = str(e)
|
||||
|
||||
return health
|
||||
|
||||
def shutdown(self, force: bool = False):
|
||||
"""Graceful shutdown with optional data preservation."""
|
||||
|
||||
if self.environment == Environment.DEV and not force:
|
||||
logger.info("🧹 DEV MODE: Clearing data on shutdown...")
|
||||
try:
|
||||
if self.client:
|
||||
self.client.flushdb()
|
||||
logger.info("✅ Dev data cleared")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clear dev data: {e}")
|
||||
|
||||
# Close connection
|
||||
if self.client:
|
||||
try:
|
||||
self.client.close()
|
||||
logger.info("🔌 Redis connection closed")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error closing Redis connection: {e}")
|
||||
|
||||
# Stop subprocess if we started it
|
||||
if self._subprocess:
|
||||
try:
|
||||
self._subprocess.terminate()
|
||||
self._subprocess.wait(timeout=5)
|
||||
logger.info("🛑 Redis subprocess stopped")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error stopping Redis subprocess: {e}")
|
||||
|
||||
logger.info(f"✅ Redis manager shutdown complete ({self.environment.value})")
|
||||
|
||||
# Convenience functions for backward compatibility
|
||||
def get_redis_manager(environment: str = "dev") -> RedisManager:
|
||||
"""Get a Redis manager instance for the specified environment."""
|
||||
env = Environment(environment.lower())
|
||||
return RedisManager(env)
|
||||
|
||||
def ensure_redis_running(environment: str = "dev") -> bool:
|
||||
"""Ensure Redis is running for the specified environment."""
|
||||
manager = get_redis_manager(environment)
|
||||
return manager.ensure_service_running()
|
||||
2531
modules/task_processors.py
Normal file
2531
modules/task_processors.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -77,4 +77,5 @@ python-docx
|
||||
pdfminer.six
|
||||
Pillow
|
||||
psutil
|
||||
PyPDF2
|
||||
PyPDF2
|
||||
PyMuPDF
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,113 +0,0 @@
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.services.school_admin_service import SchoolAdminService
|
||||
from modules.database.supabase.utils.storage import StorageManager
|
||||
from .auth import verify_admin
|
||||
from typing import Dict
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
# Initialize services
|
||||
school_service = SchoolAdminService()
|
||||
storage_manager = StorageManager()
|
||||
|
||||
@router.get("/schools/manage", response_class=HTMLResponse)
|
||||
async def manage_schools(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""Manage schools page"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/schools/manage.html",
|
||||
{"request": request, "admin": admin}
|
||||
)
|
||||
|
||||
@router.get("/storage/manage", response_class=HTMLResponse)
|
||||
async def manage_storage(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""Storage management page"""
|
||||
try:
|
||||
# Get list of storage buckets with correct IDs
|
||||
buckets = [
|
||||
{
|
||||
"id": "cc.institutes",
|
||||
"name": "School Files",
|
||||
"public": False,
|
||||
"file_size_limit": 50 * 1024 * 1024, # 50MB
|
||||
"allowed_mime_types": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"text/plain",
|
||||
"text/csv",
|
||||
"application/json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cc.users",
|
||||
"name": "User Files",
|
||||
"public": False,
|
||||
"file_size_limit": 50 * 1024 * 1024, # 50MB
|
||||
"allowed_mime_types": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"text/plain",
|
||||
"text/csv",
|
||||
"application/json"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"admin/storage/manage.html",
|
||||
{"request": request, "admin": admin, "buckets": buckets}
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/schema", response_class=HTMLResponse)
|
||||
async def manage_schema(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""Schema management page"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/schema/manage.html",
|
||||
{"request": request, "admin": admin}
|
||||
)
|
||||
|
||||
@router.get("/storage/{bucket_id}/contents")
|
||||
async def list_bucket_contents(
|
||||
request: Request,
|
||||
bucket_id: str,
|
||||
path: str = "",
|
||||
admin: Dict = Depends(verify_admin)
|
||||
):
|
||||
"""List contents of a storage bucket"""
|
||||
try:
|
||||
contents = storage_manager.list_bucket_contents(bucket_id, path)
|
||||
bucket = {"id": bucket_id, "name": bucket_id.replace("_", " ").title()}
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"admin/storage/contents.html",
|
||||
{
|
||||
"request": request,
|
||||
"admin": admin,
|
||||
"bucket": bucket,
|
||||
"contents": contents,
|
||||
"current_path": path
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
@ -1,498 +0,0 @@
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException, File, UploadFile, Form
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from typing import Dict
|
||||
import os
|
||||
from modules.logger_tool import initialise_logger
|
||||
from modules.database.services.admin_service import AdminService, AdminProfileBase
|
||||
from modules.database.services.school_admin_service import SchoolAdminService
|
||||
from modules.database.supabase.utils.client import SupabaseAnonClient
|
||||
from modules.database.supabase.utils.storage import StorageManager
|
||||
from .auth import verify_admin
|
||||
import csv
|
||||
import io
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
# Initialize services
|
||||
admin_service = AdminService()
|
||||
school_service = SchoolAdminService()
|
||||
storage_manager = StorageManager(SupabaseAnonClient)
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def admin_dashboard(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""Render admin dashboard"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/dashboard/index.html",
|
||||
{
|
||||
"request": request,
|
||||
"admin": admin,
|
||||
"app_version": os.getenv("APP_VERSION", "Unknown")
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/users")
|
||||
async def list_users(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""List all users"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/users/list.html",
|
||||
{"request": request, "admin": admin}
|
||||
)
|
||||
|
||||
@router.get("/users/{user_id}")
|
||||
async def get_user(request: Request, user_id: str, admin: Dict = Depends(verify_admin)):
|
||||
"""Get user details"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/users/detail.html",
|
||||
{"request": request, "admin": admin, "user_id": user_id}
|
||||
)
|
||||
|
||||
@router.get("/admins")
|
||||
async def list_admins(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""List all admins"""
|
||||
if not admin.get("is_super_admin"):
|
||||
raise HTTPException(status_code=403, detail="Only super admins can view admin list")
|
||||
|
||||
admins = admin_service.list_admins()
|
||||
return templates.TemplateResponse(
|
||||
"admin/users/admins.html",
|
||||
{"request": request, "admin": admin, "admins": admins}
|
||||
)
|
||||
|
||||
@router.post("/admins")
|
||||
async def create_admin(admin_data: AdminProfileBase, current_admin: Dict = Depends(verify_admin)):
|
||||
"""Create a new admin"""
|
||||
try:
|
||||
result = admin_service.create_admin(admin_data, current_admin)
|
||||
return JSONResponse(content={"status": "success", "admin": result})
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
@router.get("/schools/manage", response_class=HTMLResponse)
|
||||
async def manage_schools(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""Manage schools page"""
|
||||
try:
|
||||
# Fetch schools from Supabase
|
||||
result = admin_service.supabase.table("schools").select("*").execute()
|
||||
schools = result.data if result else []
|
||||
|
||||
# Sort schools by establishment_name
|
||||
schools.sort(key=lambda x: x.get("establishment_name", ""))
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"admin/schools/manage.html",
|
||||
{
|
||||
"request": request,
|
||||
"admin": admin,
|
||||
"schools": schools,
|
||||
"schools_count": len(schools)
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching schools: {str(e)}")
|
||||
return templates.TemplateResponse(
|
||||
"admin/schools/manage.html",
|
||||
{
|
||||
"request": request,
|
||||
"admin": admin,
|
||||
"schools": [],
|
||||
"schools_count": 0,
|
||||
"error": str(e)
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/schools/import")
|
||||
async def import_schools(
|
||||
file: UploadFile = File(...),
|
||||
admin: Dict = Depends(verify_admin)
|
||||
):
|
||||
"""Import schools from CSV file"""
|
||||
if not file.filename.endswith('.csv'):
|
||||
raise HTTPException(status_code=400, detail="Please upload a CSV file")
|
||||
|
||||
try:
|
||||
# Process the CSV file
|
||||
content = await file.read()
|
||||
csv_text = content.decode('utf-8-sig') # Handle BOM if present
|
||||
csv_reader = csv.DictReader(io.StringIO(csv_text))
|
||||
|
||||
# Prepare data for batch insert
|
||||
schools_data = []
|
||||
for row in csv_reader:
|
||||
school_data = {
|
||||
"urn": row.get("URN"),
|
||||
"la_code": row.get("LA (code)"),
|
||||
"la_name": row.get("LA (name)"),
|
||||
"establishment_number": row.get("EstablishmentNumber"),
|
||||
"establishment_name": row.get("EstablishmentName"),
|
||||
"establishment_type": row.get("TypeOfEstablishment (name)"),
|
||||
"establishment_type_group": row.get("EstablishmentTypeGroup (name)"),
|
||||
"establishment_status": row.get("EstablishmentStatus (name)"),
|
||||
"reason_establishment_opened": row.get("ReasonEstablishmentOpened (name)"),
|
||||
"open_date": row.get("OpenDate"),
|
||||
"reason_establishment_closed": row.get("ReasonEstablishmentClosed (name)"),
|
||||
"close_date": row.get("CloseDate"),
|
||||
"phase_of_education": row.get("PhaseOfEducation (name)"),
|
||||
"statutory_low_age": row.get("StatutoryLowAge"),
|
||||
"statutory_high_age": row.get("StatutoryHighAge"),
|
||||
"boarders": row.get("Boarders (name)"),
|
||||
"nursery_provision": row.get("NurseryProvision (name)"),
|
||||
"official_sixth_form": row.get("OfficialSixthForm (name)"),
|
||||
"gender": row.get("Gender (name)"),
|
||||
"religious_character": row.get("ReligiousCharacter (name)"),
|
||||
"religious_ethos": row.get("ReligiousEthos (name)"),
|
||||
"diocese": row.get("Diocese (name)"),
|
||||
"admissions_policy": row.get("AdmissionsPolicy (name)"),
|
||||
"school_capacity": row.get("SchoolCapacity"),
|
||||
"special_classes": row.get("SpecialClasses (name)"),
|
||||
"census_date": row.get("CensusDate"),
|
||||
"number_of_pupils": row.get("NumberOfPupils"),
|
||||
"number_of_boys": row.get("NumberOfBoys"),
|
||||
"number_of_girls": row.get("NumberOfGirls"),
|
||||
"percentage_fsm": row.get("PercentageFSM"),
|
||||
"trust_school_flag": row.get("TrustSchoolFlag (name)"),
|
||||
"trusts_name": row.get("Trusts (name)"),
|
||||
"school_sponsor_flag": row.get("SchoolSponsorFlag (name)"),
|
||||
"school_sponsors_name": row.get("SchoolSponsors (name)"),
|
||||
"federation_flag": row.get("FederationFlag (name)"),
|
||||
"federations_name": row.get("Federations (name)"),
|
||||
"ukprn": row.get("UKPRN"),
|
||||
"fehe_identifier": row.get("FEHEIdentifier"),
|
||||
"further_education_type": row.get("FurtherEducationType (name)"),
|
||||
"ofsted_last_inspection": row.get("OfstedLastInsp"),
|
||||
"last_changed_date": row.get("LastChangedDate"),
|
||||
"street": row.get("Street"),
|
||||
"locality": row.get("Locality"),
|
||||
"address3": row.get("Address3"),
|
||||
"town": row.get("Town"),
|
||||
"county": row.get("County (name)"),
|
||||
"postcode": row.get("Postcode"),
|
||||
"school_website": row.get("SchoolWebsite"),
|
||||
"telephone_num": row.get("TelephoneNum"),
|
||||
"head_title": row.get("HeadTitle (name)"),
|
||||
"head_first_name": row.get("HeadFirstName"),
|
||||
"head_last_name": row.get("HeadLastName"),
|
||||
"head_preferred_job_title": row.get("HeadPreferredJobTitle"),
|
||||
"gssla_code": row.get("GSSLACode (name)"),
|
||||
"parliamentary_constituency": row.get("ParliamentaryConstituency (name)"),
|
||||
"urban_rural": row.get("UrbanRural (name)"),
|
||||
"rsc_region": row.get("RSCRegion (name)"),
|
||||
"country": row.get("Country (name)"),
|
||||
"uprn": row.get("UPRN"),
|
||||
"sen_stat": row.get("SENStat") == "true",
|
||||
"sen_no_stat": row.get("SENNoStat") == "true",
|
||||
"sen_unit_on_roll": row.get("SenUnitOnRoll"),
|
||||
"sen_unit_capacity": row.get("SenUnitCapacity"),
|
||||
"resourced_provision_on_roll": row.get("ResourcedProvisionOnRoll"),
|
||||
"resourced_provision_capacity": row.get("ResourcedProvisionCapacity"),
|
||||
}
|
||||
|
||||
# Clean up empty strings and convert types
|
||||
for key, value in school_data.items():
|
||||
if value == "":
|
||||
school_data[key] = None
|
||||
elif key in ["statutory_low_age", "statutory_high_age", "school_capacity",
|
||||
"number_of_pupils", "number_of_boys", "number_of_girls",
|
||||
"sen_unit_on_roll", "sen_unit_capacity",
|
||||
"resourced_provision_on_roll", "resourced_provision_capacity"]:
|
||||
if value:
|
||||
try:
|
||||
float_val = float(value)
|
||||
int_val = int(float_val)
|
||||
school_data[key] = int_val
|
||||
except (ValueError, TypeError):
|
||||
school_data[key] = None
|
||||
elif key == "percentage_fsm":
|
||||
if value:
|
||||
try:
|
||||
school_data[key] = float(value)
|
||||
except (ValueError, TypeError):
|
||||
school_data[key] = None
|
||||
elif key in ["open_date", "close_date", "census_date",
|
||||
"ofsted_last_inspection", "last_changed_date"]:
|
||||
if value:
|
||||
try:
|
||||
# Convert date from DD-MM-YYYY to YYYY-MM-DD
|
||||
parts = value.split("-")
|
||||
if len(parts) == 3:
|
||||
school_data[key] = f"{parts[2]}-{parts[1]}-{parts[0]}"
|
||||
else:
|
||||
school_data[key] = None
|
||||
except:
|
||||
school_data[key] = None
|
||||
|
||||
schools_data.append(school_data)
|
||||
|
||||
# Batch insert schools using admin service's Supabase client
|
||||
if schools_data:
|
||||
result = admin_service.supabase.table("schools").upsert(
|
||||
schools_data,
|
||||
on_conflict="urn" # Update if URN already exists
|
||||
).execute()
|
||||
|
||||
logger.info(f"Imported {len(schools_data)} schools")
|
||||
return {"status": "success", "imported_count": len(schools_data)}
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="No valid school data found in CSV")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error importing schools: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/initialize-schools-database")
|
||||
async def initialize_schools_database(admin: Dict = Depends(verify_admin)):
|
||||
"""Initialize schools database"""
|
||||
if not admin.get("is_super_admin"):
|
||||
raise HTTPException(status_code=403, detail="Only super admins can initialize database")
|
||||
|
||||
result = school_service.create_schools_database()
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=500, detail=result["message"])
|
||||
return result
|
||||
|
||||
@router.get("/check-schools-database")
|
||||
async def check_schools_database(admin: Dict = Depends(verify_admin)):
|
||||
"""Check schools database status"""
|
||||
try:
|
||||
# Use SchoolService to check if database exists and has required nodes/relationships
|
||||
result = school_service.check_schools_database()
|
||||
return {"exists": result["status"] == "success"}
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking schools database: {str(e)}")
|
||||
return {"exists": False, "error": str(e)}
|
||||
|
||||
@router.get("/storage", response_class=HTMLResponse)
|
||||
async def storage_management(request: Request, admin: Dict = Depends(verify_admin)):
|
||||
"""Storage management page"""
|
||||
try:
|
||||
# Get list of storage buckets with correct IDs
|
||||
buckets = [
|
||||
{
|
||||
"id": "cc.institutes",
|
||||
"name": "School Files",
|
||||
"public": False,
|
||||
"file_size_limit": 50 * 1024 * 1024, # 50MB
|
||||
"allowed_mime_types": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"text/plain",
|
||||
"text/csv",
|
||||
"application/json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cc.users",
|
||||
"name": "User Files",
|
||||
"public": False,
|
||||
"file_size_limit": 50 * 1024 * 1024, # 50MB
|
||||
"allowed_mime_types": [
|
||||
"image/*",
|
||||
"video/*",
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"text/plain",
|
||||
"text/csv",
|
||||
"application/json"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"admin/storage/manage.html",
|
||||
{"request": request, "admin": admin, "buckets": buckets}
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/storage/{bucket_id}/contents")
|
||||
async def list_bucket_contents(
|
||||
request: Request,
|
||||
bucket_id: str,
|
||||
path: str = "",
|
||||
admin: Dict = Depends(verify_admin)
|
||||
):
|
||||
"""List contents of a storage bucket"""
|
||||
try:
|
||||
contents = storage_manager.list_bucket_contents(bucket_id, path)
|
||||
bucket = {"id": bucket_id, "name": bucket_id.replace("_", " ").title()}
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"admin/storage/contents.html",
|
||||
{
|
||||
"request": request,
|
||||
"admin": admin,
|
||||
"bucket": bucket,
|
||||
"contents": contents,
|
||||
"current_path": path
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/storage/{bucket_id}/download/{file_path:path}")
|
||||
async def download_file(
|
||||
bucket_id: str,
|
||||
file_path: str,
|
||||
admin: Dict = Depends(verify_admin)
|
||||
):
|
||||
"""Get download URL for a file"""
|
||||
try:
|
||||
url = storage_manager.create_signed_url(bucket_id, file_path)
|
||||
return {"url": url}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete("/storage/{bucket_id}/objects/{object_path:path}")
|
||||
async def delete_object(
|
||||
bucket_id: str,
|
||||
object_path: str,
|
||||
admin: Dict = Depends(verify_admin)
|
||||
):
|
||||
"""Delete an object from storage"""
|
||||
try:
|
||||
storage_manager.delete_file(bucket_id, object_path)
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/check-storage")
|
||||
async def check_storage(admin: Dict = Depends(verify_admin)):
|
||||
"""Check storage buckets status"""
|
||||
try:
|
||||
# Use the same bucket IDs as defined in initialize_storage
|
||||
buckets = [
|
||||
{"id": "cc.users", "name": "User Files"},
|
||||
{"id": "cc.institutes", "name": "School Files"}
|
||||
]
|
||||
|
||||
results = []
|
||||
for bucket in buckets:
|
||||
exists = storage_manager.check_bucket_exists(bucket["id"])
|
||||
results.append({
|
||||
"id": bucket["id"],
|
||||
"name": bucket["name"],
|
||||
"exists": exists
|
||||
})
|
||||
|
||||
return {"buckets": results}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/initialize-storage")
|
||||
async def initialize_storage(admin: Dict = Depends(verify_admin)):
|
||||
"""Initialize storage buckets and policies for schools"""
|
||||
try:
|
||||
# Verify super admin status
|
||||
if not admin.get('is_super_admin'):
|
||||
raise HTTPException(status_code=403, detail="Only super admins can initialize storage")
|
||||
|
||||
# Use the storage manager to initialize storage
|
||||
storage_manager = StorageManager(SupabaseAnonClient)
|
||||
return storage_manager.initialize_storage()
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing storage: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/check-schema")
|
||||
async def check_schema(admin: Dict = Depends(verify_admin)):
|
||||
"""Check Neo4j schema status"""
|
||||
try:
|
||||
from modules.database.services.graph_service import GraphService
|
||||
graph_service = GraphService()
|
||||
|
||||
# Get actual schema status
|
||||
schema_status = graph_service.check_schema_status()
|
||||
|
||||
# Return status with proper validation
|
||||
return {
|
||||
"constraints_valid": schema_status["constraints_count"] > 0,
|
||||
"constraints_count": schema_status["constraints_count"],
|
||||
"indexes_valid": schema_status["indexes_count"] > 0,
|
||||
"indexes_count": schema_status["indexes_count"],
|
||||
"labels_valid": schema_status["labels_count"] > 0,
|
||||
"labels_count": schema_status["labels_count"]
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking schema: {str(e)}")
|
||||
return {
|
||||
"constraints_valid": False,
|
||||
"constraints_count": 0,
|
||||
"indexes_valid": False,
|
||||
"indexes_count": 0,
|
||||
"labels_valid": False,
|
||||
"labels_count": 0,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
@router.post("/initialize-schema")
|
||||
async def initialize_schema(admin: Dict = Depends(verify_admin)):
|
||||
"""Initialize Neo4j schema (constraints and indexes)"""
|
||||
if not admin.get("is_super_admin"):
|
||||
raise HTTPException(status_code=403, detail="Only super admins can initialize schema")
|
||||
|
||||
try:
|
||||
from modules.database.services.graph_service import GraphService
|
||||
graph_service = GraphService()
|
||||
|
||||
# Initialize schema
|
||||
result = graph_service.initialize_schema()
|
||||
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=500, detail=result["message"])
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing schema: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/schools/{school_id}")
|
||||
async def view_school(request: Request, school_id: str, admin: Dict = Depends(verify_admin)):
|
||||
"""View school details"""
|
||||
try:
|
||||
# Fetch school details from Supabase
|
||||
result = admin_service.supabase.table("schools").select("*").eq("id", school_id).single().execute()
|
||||
school = result.data if result else None
|
||||
|
||||
if not school:
|
||||
raise HTTPException(status_code=404, detail="School not found")
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"admin/schools/detail.html",
|
||||
{"request": request, "admin": admin, "school": school}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching school details: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete("/schools/{school_id}")
|
||||
async def delete_school(school_id: str, admin: Dict = Depends(verify_admin)):
|
||||
"""Delete a school"""
|
||||
try:
|
||||
# Verify super admin status
|
||||
if not admin.get("is_super_admin"):
|
||||
raise HTTPException(status_code=403, detail="Only super admins can delete schools")
|
||||
|
||||
# Delete the school from Supabase
|
||||
result = admin_service.supabase.table("schools").delete().eq("id", school_id).execute()
|
||||
|
||||
if not result.data:
|
||||
raise HTTPException(status_code=404, detail="School not found")
|
||||
|
||||
return {"status": "success", "message": "School deleted successfully"}
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting school: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
7
routers/assets/docling/__init__.py
Normal file
7
routers/assets/docling/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/ping")
|
||||
def ping():
|
||||
return {"status": "ok"}
|
||||
@ -45,7 +45,7 @@ async def login_page(
|
||||
|
||||
# If no super admin and init flag is true, show initialization form
|
||||
if not has_super_admin:
|
||||
expected_email = os.getenv("VITE_SUPER_ADMIN_EMAIL")
|
||||
expected_email = os.getenv("ADMIN_EMAIL")
|
||||
return templates.TemplateResponse(
|
||||
"admin/login.html",
|
||||
{
|
||||
|
||||
3
routers/database/files/__init__.py
Normal file
3
routers/database/files/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from . import cabinets, files
|
||||
|
||||
|
||||
75
routers/database/files/cabinets.py
Normal file
75
routers/database/files/cabinets.py
Normal file
@ -0,0 +1,75 @@
|
||||
import os
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from typing import Any, Dict
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
|
||||
@router.get("/cabinets")
|
||||
def list_cabinets(payload: Dict[str, Any] = Depends(auth)):
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=401, detail="Invalid token payload")
|
||||
client = SupabaseServiceRoleClient()
|
||||
# Owned + shared via membership
|
||||
owned = client.supabase.table('file_cabinets').select('*').eq('user_id', user_id).execute().data
|
||||
shared = client.supabase.table('cabinet_memberships').select('cabinet_id').eq('profile_id', user_id).execute().data
|
||||
shared_ids = [m['cabinet_id'] for m in (shared or [])]
|
||||
shared_rows = client.supabase.table('file_cabinets').select('*').in_('id', shared_ids).execute().data if shared_ids else []
|
||||
return {"owned": owned or [], "shared": shared_rows or []}
|
||||
|
||||
@router.post("/cabinets")
|
||||
def create_cabinet(body: Dict[str, Any], payload: Dict[str, Any] = Depends(auth)):
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
name = (body or {}).get('name')
|
||||
if not user_id or not name:
|
||||
raise HTTPException(status_code=400, detail="name is required")
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('file_cabinets').insert({
|
||||
'user_id': user_id,
|
||||
'name': name
|
||||
}).execute()
|
||||
return res.data
|
||||
|
||||
@router.patch("/cabinets/{cabinet_id}")
|
||||
def rename_cabinet(cabinet_id: str, body: Dict[str, Any], payload: Dict[str, Any] = Depends(auth)):
|
||||
name = (body or {}).get('name')
|
||||
if not name:
|
||||
raise HTTPException(status_code=400, detail="name is required")
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('file_cabinets').update({'name': name}).eq('id', cabinet_id).execute()
|
||||
return res.data
|
||||
|
||||
@router.delete("/cabinets/{cabinet_id}")
|
||||
def delete_cabinet(cabinet_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('file_cabinets').delete().eq('id', cabinet_id).execute()
|
||||
return res.data
|
||||
|
||||
@router.post("/cabinets/{cabinet_id}/members")
|
||||
def add_member(cabinet_id: str, body: Dict[str, Any], payload: Dict[str, Any] = Depends(auth)):
|
||||
target_profile_id = (body or {}).get('profile_id')
|
||||
role = (body or {}).get('role', 'viewer')
|
||||
if not target_profile_id:
|
||||
raise HTTPException(status_code=400, detail="profile_id required")
|
||||
client = SupabaseServiceRoleClient()
|
||||
# Insert membership (RLS will ensure only owner can do it)
|
||||
res = client.supabase.table('cabinet_memberships').upsert({
|
||||
'cabinet_id': cabinet_id,
|
||||
'profile_id': target_profile_id,
|
||||
'role': role
|
||||
}).execute()
|
||||
return res.data
|
||||
|
||||
@router.delete("/cabinets/{cabinet_id}/members/{profile_id}")
|
||||
def remove_member(cabinet_id: str, profile_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('cabinet_memberships').delete().match({
|
||||
'cabinet_id': cabinet_id,
|
||||
'profile_id': profile_id
|
||||
}).execute()
|
||||
return res.data
|
||||
|
||||
|
||||
1000
routers/database/files/files.py
Normal file
1000
routers/database/files/files.py
Normal file
File diff suppressed because it is too large
Load Diff
256
routers/database/files/files_simplified.py
Normal file
256
routers/database/files/files_simplified.py
Normal file
@ -0,0 +1,256 @@
|
||||
"""
|
||||
Simplified Files Router
|
||||
======================
|
||||
|
||||
Simplified version of the files router with auto-processing removed.
|
||||
Keeps only essential functionality for file management and manual processing triggers.
|
||||
|
||||
This replaces the complex auto-processing system with simple file storage.
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, BackgroundTasks
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
from modules.database.supabase.utils.storage import StorageAdmin
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
def _choose_bucket(scope: str, user_id: str, school_id: Optional[str]) -> str:
|
||||
"""Choose appropriate bucket based on scope - matches old system logic."""
|
||||
scope = (scope or 'teacher').lower()
|
||||
if scope == 'school' and school_id:
|
||||
return f"cc.institutes.{school_id}.private"
|
||||
# teacher / student fall back to users bucket for now
|
||||
return 'cc.users'
|
||||
|
||||
@router.post("/files/upload")
|
||||
async def upload_file(
|
||||
cabinet_id: str = Form(...),
|
||||
path: str = Form(...),
|
||||
scope: str = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
SIMPLIFIED file upload - no automatic processing.
|
||||
Just stores the file and creates a database record.
|
||||
|
||||
This is the legacy endpoint maintained for backward compatibility.
|
||||
"""
|
||||
|
||||
try:
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=401, detail="User ID required")
|
||||
|
||||
# Read file content
|
||||
file_bytes = await file.read()
|
||||
file_size = len(file_bytes)
|
||||
mime_type = file.content_type or 'application/octet-stream'
|
||||
filename = file.filename or path
|
||||
|
||||
logger.info(f"📤 Simplified upload: {filename} ({file_size} bytes) for user {user_id}")
|
||||
|
||||
# Initialize services
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Generate file ID and storage path
|
||||
file_id = str(uuid.uuid4())
|
||||
# Use same bucket logic as old system for consistency
|
||||
bucket = _choose_bucket('teacher', user_id, None)
|
||||
storage_path = f"{cabinet_id}/{file_id}/{filename}"
|
||||
|
||||
# Store file in Supabase storage
|
||||
try:
|
||||
storage.upload_file(bucket, storage_path, file_bytes, mime_type, upsert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Storage upload failed for {file_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Storage upload failed: {str(e)}")
|
||||
|
||||
# Create database record
|
||||
try:
|
||||
insert_res = client.supabase.table('files').insert({
|
||||
'id': file_id,
|
||||
'name': filename,
|
||||
'cabinet_id': cabinet_id,
|
||||
'bucket': bucket,
|
||||
'path': storage_path,
|
||||
'mime_type': mime_type,
|
||||
'uploaded_by': user_id,
|
||||
'size_bytes': file_size,
|
||||
'source': 'classroomcopilot-web',
|
||||
'is_directory': False,
|
||||
'processing_status': 'uploaded', # No auto-processing
|
||||
'relative_path': filename
|
||||
}).execute()
|
||||
|
||||
if not insert_res.data:
|
||||
# Clean up storage on DB failure
|
||||
try:
|
||||
storage.delete_file(bucket, storage_path)
|
||||
except:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail="Failed to create file record")
|
||||
|
||||
file_record = insert_res.data[0]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Database insert failed for {file_id}: {e}")
|
||||
# Clean up storage
|
||||
try:
|
||||
storage.delete_file(bucket, storage_path)
|
||||
except:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
|
||||
|
||||
logger.info(f"✅ Simplified upload completed: {file_id}")
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'File uploaded successfully (no auto-processing)',
|
||||
'file': file_record,
|
||||
'auto_processing_disabled': True,
|
||||
'next_steps': 'Use manual processing endpoints if needed'
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Upload error: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
|
||||
|
||||
@router.get("/files")
|
||||
def list_files(cabinet_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""List files in a cabinet."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('files').select('*').eq('cabinet_id', cabinet_id).execute()
|
||||
return res.data
|
||||
|
||||
@router.get("/files/{file_id}")
|
||||
def get_file(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get file details."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('files').select('*').eq('id', file_id).single().execute()
|
||||
|
||||
if not res.data:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
return res.data
|
||||
|
||||
@router.delete("/files/{file_id}")
|
||||
def delete_file(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Delete a file."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Get file info first
|
||||
res = client.supabase.table('files').select('*').eq('id', file_id).single().execute()
|
||||
|
||||
if not res.data:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
file_data = res.data
|
||||
|
||||
# Delete from storage
|
||||
try:
|
||||
storage.delete_file(file_data['bucket'], file_data['path'])
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete file from storage: {e}")
|
||||
|
||||
# Delete from database
|
||||
delete_res = client.supabase.table('files').delete().eq('id', file_id).execute()
|
||||
|
||||
logger.info(f"🗑️ Deleted file: {file_id}")
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'File deleted successfully'
|
||||
}
|
||||
|
||||
@router.post("/files/{file_id}/process-manual")
|
||||
async def trigger_manual_processing(
|
||||
file_id: str,
|
||||
processing_type: str = Form('basic'), # basic, advanced, custom
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
Trigger manual processing for a file.
|
||||
This is where users can manually start processing when they want it.
|
||||
"""
|
||||
|
||||
# TODO: Implement manual processing triggers
|
||||
# This would call the archived processing logic when the user explicitly requests it
|
||||
|
||||
logger.info(f"🔧 Manual processing requested for file {file_id} (type: {processing_type})")
|
||||
|
||||
return {
|
||||
'status': 'accepted',
|
||||
'message': f'Manual processing queued for file {file_id}',
|
||||
'processing_type': processing_type,
|
||||
'note': 'Manual processing not yet implemented - will use archived auto-processing logic'
|
||||
}
|
||||
|
||||
@router.get("/files/{file_id}/status")
|
||||
def get_processing_status(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get processing status for a file."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
res = client.supabase.table('files').select('processing_status, error_message, extra').eq('id', file_id).single().execute()
|
||||
|
||||
if not res.data:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
return {
|
||||
'file_id': file_id,
|
||||
'status': res.data.get('processing_status', 'unknown'),
|
||||
'error': res.data.get('error_message'),
|
||||
'details': res.data.get('extra', {})
|
||||
}
|
||||
|
||||
# Keep existing artefacts endpoints for backward compatibility
|
||||
@router.get("/files/{file_id}/artefacts")
|
||||
def list_file_artefacts(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""List artefacts for a file."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
res = client.supabase.table('document_artefacts').select('*').eq('file_id', file_id).execute()
|
||||
return res.data or []
|
||||
|
||||
@router.get("/files/{file_id}/viewer-artefacts")
|
||||
def list_viewer_artefacts(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""List artefacts organized for the viewer."""
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
# Get all artefacts
|
||||
res = client.supabase.table('document_artefacts').select('*').eq('file_id', file_id).execute()
|
||||
artefacts = res.data or []
|
||||
|
||||
# Simple organization - no complex bundle logic
|
||||
organized = {
|
||||
'document_analysis': [],
|
||||
'processing_bundles': [],
|
||||
'raw_data': []
|
||||
}
|
||||
|
||||
for artefact in artefacts:
|
||||
artefact_type = artefact.get('type', '')
|
||||
|
||||
if 'analysis' in artefact_type.lower():
|
||||
organized['document_analysis'].append(artefact)
|
||||
elif any(bundle_type in artefact_type for bundle_type in ['docling', 'bundle']):
|
||||
organized['processing_bundles'].append(artefact)
|
||||
else:
|
||||
organized['raw_data'].append(artefact)
|
||||
|
||||
return organized
|
||||
588
routers/database/files/split_map.py
Normal file
588
routers/database/files/split_map.py
Normal file
@ -0,0 +1,588 @@
|
||||
# api/routers/database/files/split_map.py
|
||||
"""
|
||||
Automatic split_map.json generator for uploaded documents.
|
||||
|
||||
This module creates chapter/section boundaries for documents using existing artefacts
|
||||
(Tika JSON, Docling frontmatter OCR) and optional PDF outline extraction.
|
||||
|
||||
Strategy (waterfall, stop at confidence ≥ 0.7):
|
||||
1. PDF Outline/Bookmarks (best): confidence ≈ 0.95
|
||||
2. Headings from Docling JSON: confidence ≈ 0.8
|
||||
3. TOC from Tika text: confidence ≈ 0.7-0.8
|
||||
4. Fixed windows: confidence ≈ 0.2
|
||||
|
||||
Hard constraints:
|
||||
- For any fallback Docling "no-OCR" call: limit page_range to [1, min(30, page_count)]
|
||||
- Never process more than 30 pages in one Docling request
|
||||
- Use existing artefacts whenever possible
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import uuid
|
||||
import datetime
|
||||
import os
|
||||
import requests
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
from modules.database.supabase.utils.storage import StorageAdmin
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
# ---------- Utilities
|
||||
|
||||
def _now_iso():
|
||||
"""Return current UTC timestamp in ISO format."""
|
||||
return datetime.datetime.utcnow().replace(microsecond=0).isoformat() + "Z"
|
||||
|
||||
def _load_artefact_json(storage: StorageAdmin, bucket: str, rel_path: str) -> Optional[Dict[str, Any]]:
|
||||
"""Load JSON artefact from storage."""
|
||||
try:
|
||||
raw = storage.download_file(bucket, rel_path)
|
||||
return json.loads(raw.decode("utf-8"))
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to load artefact {rel_path}: {e}")
|
||||
return None
|
||||
|
||||
def _page_count_from_tika(tika_json: Dict[str, Any]) -> Optional[int]:
|
||||
"""Extract page count from Tika JSON metadata."""
|
||||
for k in ("xmpTPg:NPages", "Page-Count", "pdf:PageCount", "pdf:pagecount"):
|
||||
v = tika_json.get(k) or tika_json.get(k.lower())
|
||||
try:
|
||||
if v is not None:
|
||||
return int(v)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
# ---------- A) Outline via PyMuPDF (optional but recommended)
|
||||
|
||||
def _try_outline(pdf_bytes: bytes) -> Optional[List[Tuple[str, int]]]:
|
||||
"""
|
||||
Extract PDF outline/bookmarks using PyMuPDF.
|
||||
Returns [(title, start_page)] for level-1 bookmarks only.
|
||||
"""
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
|
||||
toc = doc.get_toc(simple=True) # list of [level, title, page]
|
||||
doc.close()
|
||||
|
||||
# Keep level-1 only, ensure valid pages
|
||||
out = []
|
||||
for level, title, page in toc:
|
||||
if level == 1 and page >= 1:
|
||||
clean_title = title.strip()
|
||||
if clean_title and len(clean_title) > 1:
|
||||
out.append((clean_title, page))
|
||||
|
||||
return out if len(out) >= 2 else None # Need at least 2 chapters
|
||||
except ImportError:
|
||||
logger.debug("PyMuPDF not available, skipping outline extraction")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.debug(f"Outline extraction failed: {e}")
|
||||
return None
|
||||
|
||||
# ---------- B) Headings from Docling JSON
|
||||
|
||||
def _try_headings(docling_json: Dict[str, Any]) -> Optional[List[Tuple[str, int, int]]]:
|
||||
"""
|
||||
Extract headings from Docling JSON.
|
||||
Returns [(title, start_page, level)] — we only return starts; end pages are computed later.
|
||||
"""
|
||||
if not docling_json:
|
||||
return None
|
||||
|
||||
# Handle different Docling JSON structures
|
||||
blocks = (docling_json.get("blocks") or
|
||||
docling_json.get("elements") or
|
||||
docling_json.get("body", {}).get("blocks") or [])
|
||||
|
||||
candidates: List[Tuple[str, int, int]] = []
|
||||
|
||||
for b in blocks:
|
||||
# Check if this is a heading block
|
||||
role = (b.get("role") or b.get("type") or "").lower()
|
||||
if not ("heading" in role or role in ("h1", "h2", "title", "section-header")):
|
||||
continue
|
||||
|
||||
# Extract text content
|
||||
text = (b.get("text") or b.get("content") or "").strip()
|
||||
if not text or len(text) < 3:
|
||||
continue
|
||||
|
||||
# Extract page number with robust handling of 0-based pageIndex
|
||||
p = None
|
||||
if b.get("pageIndex") is not None:
|
||||
try:
|
||||
p = int(b.get("pageIndex")) + 1
|
||||
except Exception:
|
||||
p = None
|
||||
if p is None:
|
||||
for key in ("page", "page_no", "page_number"):
|
||||
if b.get(key) is not None:
|
||||
try:
|
||||
p = int(b.get(key))
|
||||
except Exception:
|
||||
p = None
|
||||
break
|
||||
if p is None or p < 1:
|
||||
continue
|
||||
|
||||
# Determine heading level
|
||||
level = 1 # default
|
||||
if "1" in role or "h1" in role:
|
||||
level = 1
|
||||
elif "2" in role or "h2" in role:
|
||||
level = 2
|
||||
|
||||
# Chapter regex boosts to level 1
|
||||
if re.match(r"^\s*(chapter|ch\.?|section|part)\s+\d+", text, re.I):
|
||||
level = 1
|
||||
|
||||
candidates.append((text, p, level))
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
# Prefer level 1; if none, promote level 2 to level 1
|
||||
l1 = [(t, p, l) for (t, p, l) in candidates if l == 1]
|
||||
if not l1:
|
||||
l1 = [(t, p, 1) for (t, p, _) in candidates]
|
||||
|
||||
# Sort by page and keep strictly increasing pages only
|
||||
l1_sorted = []
|
||||
seen = set()
|
||||
for (t, p, l) in sorted(l1, key=lambda x: x[1]):
|
||||
if p not in seen and p >= 1:
|
||||
l1_sorted.append((t, p, l))
|
||||
seen.add(p)
|
||||
|
||||
return l1_sorted if len(l1_sorted) >= 2 else None
|
||||
|
||||
def _try_headings_fallback(file_id: str, cabinet_id: str, bucket: str,
|
||||
processing_bytes: bytes, processing_mime: str,
|
||||
page_count: int) -> Optional[List[Tuple[str, int, int]]]:
|
||||
"""
|
||||
Make a limited Docling no-OCR call (max 30 pages) to extract headings.
|
||||
This is used only when existing artefacts don't have sufficient heading data.
|
||||
"""
|
||||
try:
|
||||
docling_url = os.getenv('DOCLING_URL') or os.getenv('NEOFS_DOCLING_URL')
|
||||
if not docling_url:
|
||||
logger.debug("No Docling URL configured for headings fallback")
|
||||
return None
|
||||
|
||||
# Strictly limit to first 30 pages
|
||||
max_pages = min(30, page_count)
|
||||
logger.info(f"Headings fallback: limited Docling call for file_id={file_id}, pages=1-{max_pages}")
|
||||
|
||||
# Build Docling request (no-OCR, limited pages)
|
||||
docling_api_key = os.getenv('DOCLING_API_KEY')
|
||||
headers = {'Accept': 'application/json'}
|
||||
if docling_api_key:
|
||||
headers['X-Api-Key'] = docling_api_key
|
||||
|
||||
form_data = [
|
||||
('target_type', 'inbody'),
|
||||
('to_formats', 'json'),
|
||||
('do_ocr', 'false'),
|
||||
('force_ocr', 'false'),
|
||||
('image_export_mode', 'embedded'),
|
||||
('pdf_backend', 'dlparse_v4'),
|
||||
('table_mode', 'fast'),
|
||||
('page_range', '1'),
|
||||
('page_range', str(max_pages))
|
||||
]
|
||||
|
||||
files = [('files', ('file', processing_bytes, processing_mime))]
|
||||
|
||||
# Make the request with timeout
|
||||
timeout = int(os.getenv('DOCLING_HEADINGS_TIMEOUT', '1800')) # 30 minutes default
|
||||
resp = requests.post(
|
||||
f"{docling_url.rstrip('/')}/v1/convert/file",
|
||||
files=files,
|
||||
data=form_data,
|
||||
headers=headers,
|
||||
timeout=timeout
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
docling_json = resp.json()
|
||||
logger.debug(f"Headings fallback: received Docling response for file_id={file_id}")
|
||||
|
||||
return _try_headings(docling_json)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Headings fallback failed for file_id={file_id}: {e}")
|
||||
return None
|
||||
|
||||
# ---------- C) TOC from Tika text (dot leaders & page num)
|
||||
|
||||
TOC_LINE = re.compile(r"^\s*(.+?)\s?(\.{2,}|\s{3,})\s*(\d{1,4})\s*$")
|
||||
|
||||
def _try_toc_text(tika_text: str) -> Optional[List[Tuple[str, int]]]:
|
||||
"""
|
||||
Parse TOC from Tika text using dot leaders and page numbers.
|
||||
Returns [(title, start_page)] if successful.
|
||||
"""
|
||||
if not tika_text:
|
||||
return None
|
||||
|
||||
# Heuristic: only scan first ~1500 lines (roughly first 15 pages)
|
||||
head = "\n".join(tika_text.splitlines()[:1500])
|
||||
pairs = []
|
||||
|
||||
for line in head.splitlines():
|
||||
m = TOC_LINE.match(line)
|
||||
if not m:
|
||||
continue
|
||||
|
||||
title = m.group(1).strip()
|
||||
try:
|
||||
page = int(m.group(3))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Reject obvious junk
|
||||
if len(title) < 3 or page < 1 or page > 9999:
|
||||
continue
|
||||
|
||||
# Skip common false positives
|
||||
if any(skip in title.lower() for skip in ['copyright', 'isbn', 'published', 'printed']):
|
||||
continue
|
||||
|
||||
pairs.append((title, page))
|
||||
|
||||
# Require at least 5 entries and monotonic pages
|
||||
if len(pairs) >= 5:
|
||||
pages = [p for _, p in pairs]
|
||||
if pages == sorted(pages):
|
||||
logger.debug(f"TOC extraction found {len(pairs)} entries")
|
||||
return pairs
|
||||
|
||||
return None
|
||||
|
||||
# ---------- Build entries with ends, apply smoothing
|
||||
|
||||
def _entries_from_starts(starts: List[Tuple[str, int, int]], page_count: int, source: str = "headings") -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Build entries from start points with computed end pages.
|
||||
starts: [(title, page, level)]
|
||||
"""
|
||||
entries = []
|
||||
base_confidence = 0.8 if source == "headings" else 0.75
|
||||
|
||||
for i, (title, start, level) in enumerate(starts):
|
||||
end = (starts[i + 1][1] - 1) if i + 1 < len(starts) else page_count
|
||||
entries.append({
|
||||
"id": f"sec{i + 1:02d}",
|
||||
"title": title,
|
||||
"level": level,
|
||||
"start_page": int(start),
|
||||
"end_page": int(end),
|
||||
"source": source,
|
||||
"confidence": base_confidence
|
||||
})
|
||||
|
||||
# Merge tiny sections (< 3 pages) into previous
|
||||
merged = []
|
||||
for e in entries:
|
||||
section_size = e["end_page"] - e["start_page"] + 1
|
||||
if merged and section_size < 3:
|
||||
# Merge into previous section
|
||||
merged[-1]["end_page"] = e["end_page"]
|
||||
merged[-1]["title"] += " / " + e["title"]
|
||||
merged[-1]["confidence"] *= 0.95 # Slight confidence penalty for merging
|
||||
else:
|
||||
merged.append(e)
|
||||
|
||||
return merged
|
||||
|
||||
def _entries_from_pairs(pairs: List[Tuple[str, int]], page_count: int, source: str = "outline") -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Build entries from (title, start_page) pairs.
|
||||
"""
|
||||
entries = []
|
||||
base_confidence = 0.95 if source == "outline" else (0.8 if source == "toc" else 0.75)
|
||||
|
||||
for i, (title, start) in enumerate(pairs):
|
||||
end = (pairs[i + 1][1] - 1) if i + 1 < len(pairs) else page_count
|
||||
entries.append({
|
||||
"id": f"sec{i + 1:02d}",
|
||||
"title": title,
|
||||
"level": 1,
|
||||
"start_page": int(start),
|
||||
"end_page": int(end),
|
||||
"source": source,
|
||||
"confidence": base_confidence
|
||||
})
|
||||
|
||||
# Apply same merging logic for tiny sections
|
||||
merged = []
|
||||
for e in entries:
|
||||
section_size = e["end_page"] - e["start_page"] + 1
|
||||
if merged and section_size < 3:
|
||||
merged[-1]["end_page"] = e["end_page"]
|
||||
merged[-1]["title"] += " / " + e["title"]
|
||||
merged[-1]["confidence"] *= 0.95
|
||||
else:
|
||||
merged.append(e)
|
||||
|
||||
return merged
|
||||
|
||||
# ---------- Post-processing normalization
|
||||
|
||||
def _normalize_entries(entries: List[Dict[str, Any]], page_count: int) -> List[Dict[str, Any]]:
|
||||
"""Normalize entries to ensure:
|
||||
- coverage from page 1
|
||||
- 1 <= start_page <= end_page <= page_count
|
||||
- strictly increasing, non-overlapping ranges
|
||||
- fill initial gap with a synthetic front matter section if needed
|
||||
"""
|
||||
if not entries:
|
||||
return entries
|
||||
|
||||
# Sanitize and sort by start_page
|
||||
safe: List[Dict[str, Any]] = []
|
||||
for e in entries:
|
||||
try:
|
||||
s = int(e.get("start_page", 1))
|
||||
t = int(e.get("end_page", s))
|
||||
except Exception:
|
||||
continue
|
||||
s = max(1, min(s, page_count))
|
||||
t = max(1, min(t, page_count))
|
||||
if t < s:
|
||||
t = s
|
||||
ne = dict(e)
|
||||
ne["start_page"], ne["end_page"] = s, t
|
||||
safe.append(ne)
|
||||
safe.sort(key=lambda x: (x["start_page"], x.get("level", 1)))
|
||||
|
||||
# De-overlap by adjusting starts; ensure monotonic ranges
|
||||
normalized: List[Dict[str, Any]] = []
|
||||
for e in safe:
|
||||
if not normalized:
|
||||
normalized.append(e)
|
||||
continue
|
||||
prev = normalized[-1]
|
||||
if e["start_page"] <= prev["end_page"]:
|
||||
e["start_page"] = prev["end_page"] + 1
|
||||
if e["start_page"] > page_count:
|
||||
continue
|
||||
if e["end_page"] < e["start_page"]:
|
||||
e["end_page"] = e["start_page"]
|
||||
e["end_page"] = min(e["end_page"], page_count)
|
||||
normalized.append(e)
|
||||
|
||||
# Insert synthetic front matter if first start > 1
|
||||
if normalized and normalized[0]["start_page"] > 1:
|
||||
front = {
|
||||
"id": "sec00",
|
||||
"title": "Front matter",
|
||||
"level": 1,
|
||||
"start_page": 1,
|
||||
"end_page": normalized[0]["start_page"] - 1,
|
||||
"source": "synthetic",
|
||||
"confidence": 0.6,
|
||||
}
|
||||
normalized.insert(0, front)
|
||||
|
||||
# Ensure last section ends at page_count
|
||||
if normalized and normalized[-1]["end_page"] < page_count:
|
||||
normalized[-1]["end_page"] = page_count
|
||||
|
||||
# Renumber ids sequentially
|
||||
out: List[Dict[str, Any]] = []
|
||||
for idx, e in enumerate(normalized, start=1):
|
||||
ne = dict(e)
|
||||
ne["id"] = f"sec{idx:02d}"
|
||||
out.append(ne)
|
||||
return out
|
||||
|
||||
# ---------- Main entry point
|
||||
|
||||
def create_split_map_for_file(file_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Create split_map.json for a file using waterfall strategy:
|
||||
1. PDF outline (best)
|
||||
2. Docling headings (from existing or limited fallback)
|
||||
3. Tika TOC parsing
|
||||
4. Fixed windows (fallback)
|
||||
"""
|
||||
logger.info(f"Creating split_map for file_id={file_id}")
|
||||
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# 1) Lookup file row & bucket
|
||||
fr = client.supabase.table('files').select('id,bucket,cabinet_id,name,path,mime_type').eq('id', file_id).single().execute()
|
||||
file_row = fr.data or {}
|
||||
bucket = file_row.get('bucket')
|
||||
cabinet_id = file_row.get('cabinet_id')
|
||||
|
||||
# 2) Find artefacts
|
||||
arts = client.supabase.table('document_artefacts') \
|
||||
.select('*').eq('file_id', file_id).order('created_at', desc=True).execute().data or []
|
||||
|
||||
def find_art(t):
|
||||
for a in arts:
|
||||
if a.get('type') == t:
|
||||
return a
|
||||
return None
|
||||
|
||||
a_pdf = find_art('document_pdf') # if converted to PDF
|
||||
a_tika = find_art('tika_json')
|
||||
a_noocr = find_art('docling_noocr_json')
|
||||
a_fm = find_art('docling_frontmatter_json')
|
||||
|
||||
# 3) Load JSON/text
|
||||
tika_json = _load_artefact_json(storage, bucket, a_tika['rel_path']) if a_tika else None
|
||||
docling_noocr = _load_artefact_json(storage, bucket, a_noocr['rel_path']) if a_noocr else None
|
||||
docling_fm = _load_artefact_json(storage, bucket, a_fm['rel_path']) if a_fm else None
|
||||
|
||||
# Get page count
|
||||
page_count = _page_count_from_tika(tika_json or {}) or 100 # reasonable default
|
||||
|
||||
# Get PDF bytes for outline extraction
|
||||
pdf_bytes = None
|
||||
processing_bytes = None
|
||||
processing_mime = None
|
||||
|
||||
if a_pdf:
|
||||
# Use converted PDF
|
||||
pdf_bytes = storage.download_file(bucket, a_pdf['rel_path'])
|
||||
processing_bytes = pdf_bytes
|
||||
processing_mime = 'application/pdf'
|
||||
else:
|
||||
# Check if original file is PDF
|
||||
if file_row.get('mime_type') == 'application/pdf':
|
||||
pdf_bytes = storage.download_file(bucket, file_row['path'])
|
||||
processing_bytes = pdf_bytes
|
||||
processing_mime = 'application/pdf'
|
||||
|
||||
# 4) Try methods in waterfall order
|
||||
method = "fixed"
|
||||
confidence = 0.2
|
||||
entries: List[Dict[str, Any]] = []
|
||||
|
||||
# A) PDF Outline/Bookmarks (best)
|
||||
if pdf_bytes and not entries:
|
||||
logger.debug(f"Trying outline extraction for file_id={file_id}")
|
||||
pairs = _try_outline(pdf_bytes)
|
||||
if pairs:
|
||||
entries = _entries_from_pairs(pairs, page_count, source="outline")
|
||||
method, confidence = "outline", 0.95
|
||||
logger.info(f"Split map: outline method found {len(entries)} sections")
|
||||
|
||||
# B) Headings from existing Docling JSON
|
||||
if not entries:
|
||||
logger.debug(f"Trying headings from existing Docling JSON for file_id={file_id}")
|
||||
# Try no-OCR first, then frontmatter
|
||||
for docling_json, source_name in [(docling_noocr, "noocr"), (docling_fm, "frontmatter")]:
|
||||
if docling_json:
|
||||
starts = _try_headings(docling_json)
|
||||
if starts:
|
||||
entries = _entries_from_starts(starts, page_count, source="headings")
|
||||
method, confidence = "headings", 0.8
|
||||
logger.info(f"Split map: headings method ({source_name}) found {len(entries)} sections")
|
||||
break
|
||||
|
||||
# B2) Headings fallback with limited Docling call (if we have processing bytes)
|
||||
if not entries and processing_bytes and processing_mime:
|
||||
logger.debug(f"Trying headings fallback with limited Docling call for file_id={file_id}")
|
||||
starts = _try_headings_fallback(file_id, cabinet_id, bucket, processing_bytes, processing_mime, page_count)
|
||||
if starts:
|
||||
entries = _entries_from_starts(starts, page_count, source="headings")
|
||||
method, confidence = "headings", 0.75 # Slightly lower confidence for fallback
|
||||
logger.info(f"Split map: headings fallback found {len(entries)} sections")
|
||||
|
||||
# C) TOC from Tika text
|
||||
if not entries and tika_json:
|
||||
logger.debug(f"Trying TOC extraction from Tika text for file_id={file_id}")
|
||||
# Try common Tika text keys
|
||||
text = tika_json.get("X-TIKA:content") or tika_json.get("content") or ""
|
||||
pairs = _try_toc_text(text)
|
||||
if pairs:
|
||||
entries = _entries_from_pairs(pairs, page_count, source="toc")
|
||||
method, confidence = "toc", 0.75
|
||||
logger.info(f"Split map: TOC method found {len(entries)} sections")
|
||||
|
||||
# D) Fixed windows (fallback)
|
||||
if not entries:
|
||||
logger.info(f"Using fixed window fallback for file_id={file_id}")
|
||||
step = max(10, min(20, page_count // 10)) # Adaptive step size
|
||||
pairs = []
|
||||
for i in range(1, page_count + 1, step):
|
||||
end_page = min(i + step - 1, page_count)
|
||||
title = f"Pages {i}-{end_page}" if i != end_page else f"Page {i}"
|
||||
pairs.append((title, i))
|
||||
|
||||
entries = _entries_from_pairs(pairs, page_count, source="fixed")
|
||||
method, confidence = "fixed", 0.2
|
||||
logger.info(f"Split map: fixed method created {len(entries)} sections")
|
||||
|
||||
# 5) Normalize entries and build split_map.json
|
||||
entries = _normalize_entries(entries, page_count)
|
||||
split_map = {
|
||||
"version": 1,
|
||||
"file_id": file_id,
|
||||
"source_pdf_artefact_id": a_pdf['id'] if a_pdf else None,
|
||||
"sources": {
|
||||
"docling_noocr_json": a_noocr['id'] if a_noocr else None,
|
||||
"docling_frontmatter_json": a_fm['id'] if a_fm else None,
|
||||
"tika_json": a_tika['id'] if a_tika else None
|
||||
},
|
||||
"method": method,
|
||||
"confidence": confidence,
|
||||
"page_count": page_count,
|
||||
"entries": entries,
|
||||
"created_at": _now_iso(),
|
||||
"notes": f"auto-generated using {method} method; user can edit in Split Marker UI"
|
||||
}
|
||||
|
||||
# 6) Store as artefact
|
||||
artefact_id = str(uuid.uuid4())
|
||||
rel_path = f"{cabinet_id}/{file_id}/{artefact_id}/split_map.json"
|
||||
|
||||
storage.upload_file(
|
||||
bucket,
|
||||
rel_path,
|
||||
json.dumps(split_map, ensure_ascii=False, indent=2).encode("utf-8"),
|
||||
"application/json",
|
||||
upsert=True
|
||||
)
|
||||
|
||||
# Enhanced metadata for UI display
|
||||
enhanced_extra = {
|
||||
"method": method,
|
||||
"confidence": confidence,
|
||||
"entries_count": len(entries),
|
||||
"display_name": "Document Structure Map",
|
||||
"bundle_label": "Split Map",
|
||||
"section_title": "Document Structure Map",
|
||||
"page_count": page_count,
|
||||
"bundle_type": "split_map_json",
|
||||
"processing_mode": "document_analysis",
|
||||
"pipeline": "structure_analysis",
|
||||
"is_structure_map": True,
|
||||
"ui_category": "document_analysis",
|
||||
"ui_order": 2,
|
||||
"description": f"Document section boundaries identified using {method} method with {confidence:.1%} confidence ({len(entries)} sections)",
|
||||
"viewer_type": "json"
|
||||
}
|
||||
|
||||
client.supabase.table('document_artefacts').insert({
|
||||
"id": artefact_id,
|
||||
"file_id": file_id,
|
||||
"type": "split_map_json",
|
||||
"rel_path": rel_path,
|
||||
"extra": enhanced_extra,
|
||||
"status": "completed"
|
||||
}).execute()
|
||||
|
||||
logger.info(f"Split map stored: file_id={file_id}, method={method}, confidence={confidence:.2f}, entries={len(entries)}")
|
||||
return split_map
|
||||
@ -31,20 +31,19 @@ async def upload_curriculum(file: UploadFile = File(...), db_name: str = Form(..
|
||||
async def upload_school_curriculum(
|
||||
file: UploadFile = File(...),
|
||||
db_name: str = Form(...),
|
||||
school_uuid: str = Form(...),
|
||||
school_uuid_string: str = Form(...),
|
||||
school_name: str = Form(...),
|
||||
school_website: str = Form(...),
|
||||
school_path: str = Form(...)
|
||||
school_node_storage_path: str = Form(...)
|
||||
):
|
||||
if file.content_type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||
return {"status": "Error", "message": "Invalid file format"}
|
||||
logging.info(f"Uploading curriculum for school {school_name} in {db_name}")
|
||||
dataframes = xl.create_dataframes_from_fastapiuploadfile(file)
|
||||
school_node = SchoolNode(
|
||||
unique_id=f'School_{school_uuid}',
|
||||
school_uuid=school_uuid,
|
||||
school_name=school_name,
|
||||
school_website=school_website,
|
||||
path=school_path
|
||||
uuid_string=school_uuid_string,
|
||||
name=school_name,
|
||||
website=school_website,
|
||||
node_storage_path=school_node_storage_path
|
||||
)
|
||||
return init_school_curriculum.create_curriculum(db_name, dataframes, school_node)
|
||||
@ -23,9 +23,9 @@ def initialise_schools_from_config():
|
||||
"""Initialize a school with the configuration provided from env variables
|
||||
"""
|
||||
default_config = {
|
||||
"school_uuid": "kevlarai",
|
||||
"school_name": "KevlarAI School",
|
||||
"school_website": "https://kevlarai.com",
|
||||
"uuid_string": "kevlarai-dev",
|
||||
"name": "KevlarAI School",
|
||||
"website": "https://kevlarai.com",
|
||||
"timetable_file": "kevlarai_data/kevlarai_timetable.xlsx",
|
||||
"curriculum_file": "kevlarai_data/kevlarai_curriculum.xlsx"
|
||||
}
|
||||
@ -33,10 +33,10 @@ def initialise_schools_from_config():
|
||||
# school_config_str = os.getenv("SCHOOL_CONFIG") # TODO: Implement this
|
||||
school_config = default_config
|
||||
|
||||
db_name = f"cc.institutes.{school_config['school_uuid']}"
|
||||
db_name = f"cc.institutes.{school_config['uuid_string']}"
|
||||
curriculum_db_name = f"{db_name}.curriculum"
|
||||
|
||||
logger.info(f"Creating database for {school_config['school_name']} using db_name: {db_name}")
|
||||
logger.info(f"Creating database for {school_config['name']} using db_name: {db_name}")
|
||||
driver = driver_tools.get_driver()
|
||||
if driver is None:
|
||||
logger.error("Failed to connect to Neo4j")
|
||||
@ -54,7 +54,7 @@ def initialise_schools_from_config():
|
||||
# Add filesystem path debugging
|
||||
base_path = os.getenv("NODE_FILESYSTEM_PATH")
|
||||
schools_path = os.path.join(base_path, "schools")
|
||||
school_path = os.path.join(schools_path, f"cc.institutes.{school_config['school_uuid']}")
|
||||
school_path = os.path.join(schools_path, f"cc.institutes.{school_config['uuid_string']}")
|
||||
|
||||
logger.debug("Filesystem paths:", {
|
||||
"base_path": base_path,
|
||||
@ -70,29 +70,34 @@ def initialise_schools_from_config():
|
||||
})
|
||||
|
||||
# Create database entry for school without timetable or curriculum
|
||||
logger.info(f"Creating school entry for {school_config['school_name']} in database {db_name} without timetable or curriculum")
|
||||
logger.info(f"Creating school entry for {school_config['name']} in database {db_name} without timetable or curriculum")
|
||||
|
||||
school_uuid_string=school_config["uuid_strig"]
|
||||
school_name=school_config["name"]
|
||||
school_website=school_config["website"]
|
||||
|
||||
result = init_school.create_school(
|
||||
db_name=db_name,
|
||||
school_uuid=school_config["school_uuid"],
|
||||
school_name=school_config["school_name"],
|
||||
school_website=school_config["school_website"]
|
||||
school_type="development",
|
||||
uuid_string=school_uuid_string,
|
||||
name=school_name,
|
||||
website=school_website
|
||||
)
|
||||
logger.success(f"{school_config['school_name']} school entry created successfully")
|
||||
logger.success(f"{school_config['name']} school entry created successfully")
|
||||
|
||||
# Create school node from result
|
||||
school_node = result['school_node']
|
||||
refreshed_school_node = SchoolNode(
|
||||
unique_id=school_node.unique_id,
|
||||
school_uuid=school_node.school_uuid,
|
||||
school_name=school_node.school_name,
|
||||
school_website=school_node.school_website,
|
||||
path=school_node.path
|
||||
school_type="development",
|
||||
uuid_string=school_node.uuid_string,
|
||||
name=school_node.name,
|
||||
website=school_node.website
|
||||
)
|
||||
|
||||
# Create timetable entries for school from Excel file
|
||||
timetable_file = os.path.join(os.getenv("BACKEND_INIT_PATH"), school_config["timetable_file"])
|
||||
|
||||
logger.info(f"Creating timetable entries for {school_config['school_name']} using timetable file: {timetable_file}.")
|
||||
logger.info(f"Creating timetable entries for {school_config['name']} using timetable file: {timetable_file}.")
|
||||
school_timetable_dataframes = xl.create_dataframes(timetable_file)
|
||||
|
||||
|
||||
@ -107,7 +112,7 @@ def initialise_schools_from_config():
|
||||
curriculum_file = os.path.join(os.getenv("BACKEND_INIT_PATH"), school_config["curriculum_file"])
|
||||
school_curriculum_dataframes = xl.create_dataframes(curriculum_file)
|
||||
|
||||
logger.info(f"Creating curriculum entries for {school_config['school_name']} using curriculum file: {curriculum_file}.")
|
||||
logger.info(f"Creating curriculum entries for {school_config['name']} using curriculum file: {curriculum_file}.")
|
||||
init_school_curriculum.create_curriculum(
|
||||
dataframes=school_curriculum_dataframes,
|
||||
db_name=db_name,
|
||||
@ -123,18 +128,18 @@ async def create_user(
|
||||
user_type: str = Form(...),
|
||||
user_name: str = Form(...),
|
||||
user_email: str = Form(...),
|
||||
school_uuid: str = Form(None),
|
||||
school_uuid_string: str = Form(None),
|
||||
school_name: str = Form(None),
|
||||
school_website: str = Form(None),
|
||||
school_path: str = Form(None),
|
||||
school_node_storage_path: str = Form(None),
|
||||
worker_data: str = Form(None)
|
||||
):
|
||||
logger.info(f"Creating user with user_id: {user_id}, user_type: {user_type}, user_name: {user_name}, user_email: {user_email}")
|
||||
|
||||
if school_uuid:
|
||||
logger.info(f"School UUID provided: {school_uuid}")
|
||||
if school_uuid_string:
|
||||
logger.info(f"School UUID string provided: {school_uuid_string}")
|
||||
else:
|
||||
logger.info(f"No school UUID provided")
|
||||
logger.info(f"No school UUID string provided")
|
||||
|
||||
if school_name:
|
||||
logger.info(f"School name provided: {school_name}")
|
||||
@ -146,8 +151,8 @@ async def create_user(
|
||||
else:
|
||||
logger.info(f"No school website provided")
|
||||
|
||||
if school_path:
|
||||
logger.info(f"School path provided: {school_path}")
|
||||
if school_node_storage_path:
|
||||
logger.info(f"School path provided: {school_node_storage_path}")
|
||||
else:
|
||||
logger.info(f"No school path provided")
|
||||
|
||||
@ -169,13 +174,12 @@ async def create_user(
|
||||
|
||||
# Create school node if school data provided
|
||||
school_node = None
|
||||
if all([school_uuid, school_name, school_website, school_path]):
|
||||
if all([school_uuid_string, school_name, school_website, school_node_storage_path]):
|
||||
school_node = SchoolNode(
|
||||
unique_id=f'School_{school_uuid}',
|
||||
school_uuid=school_uuid,
|
||||
school_name=school_name,
|
||||
school_website=school_website,
|
||||
path=school_path
|
||||
uuid_string=school_uuid_string,
|
||||
name=school_name,
|
||||
website=school_website,
|
||||
node_storage_path=school_node_storage_path
|
||||
)
|
||||
|
||||
# Create user with single database reference
|
||||
@ -219,23 +223,23 @@ async def create_schools():
|
||||
@router.post("/create-department")
|
||||
async def create_department(
|
||||
db_name: str = Form(...),
|
||||
unique_id: str = Form(...),
|
||||
uuid_string: str = Form(...),
|
||||
department_name: str = Form(...),
|
||||
department_code: str = Form(...),
|
||||
path: str = Form(...)
|
||||
department_node_storage_path: str = Form(...)
|
||||
):
|
||||
if db_name is None or unique_id is None or department_name is None or department_code is None or path is None:
|
||||
logging.error(f"Invalid department data: {db_name}, {unique_id}, {department_name}, {department_code}, {path}")
|
||||
if db_name is None or uuid_string is None or department_name is None or department_code is None or department_node_storage_path is None:
|
||||
logging.error(f"Invalid department data: {db_name}, {uuid_string}, {department_name}, {department_code}, {department_node_storage_path}")
|
||||
raise HTTPException(status_code=400, detail="Invalid department data")
|
||||
|
||||
department = DepartmentNode(
|
||||
unique_id=unique_id,
|
||||
uuid_string=uuid_string,
|
||||
department_name=department_name,
|
||||
department_code=department_code,
|
||||
path=path
|
||||
node_storage_path=department_node_storage_path
|
||||
)
|
||||
|
||||
logger.info(f"Creating department {department_name} with unique_id {unique_id}")
|
||||
logger.info(f"Creating department {department_name} with uuid_string {uuid_string}")
|
||||
try:
|
||||
result = init_school.create_department(db_name, department)
|
||||
return JSONResponse(content={"status": "success", "data": result})
|
||||
@ -246,20 +250,20 @@ async def create_department(
|
||||
@router.post("/create-class")
|
||||
async def create_class(
|
||||
db_name: str = Form(...),
|
||||
unique_id: str = Form(...),
|
||||
uuid_string: str = Form(...),
|
||||
subject_class_code: str = Form(...),
|
||||
year_group: str = Form(...),
|
||||
subject: str = Form(...),
|
||||
subject_code: str = Form(...),
|
||||
path: str = Form(...)
|
||||
subject_node_storage_path: str = Form(...)
|
||||
):
|
||||
subject_class_node = SubjectClassNode(
|
||||
unique_id=unique_id,
|
||||
uuid_string=uuid_string,
|
||||
subject_class_code=subject_class_code,
|
||||
year_group=year_group,
|
||||
subject=subject,
|
||||
subject_code=subject_code,
|
||||
path=path
|
||||
node_storage_path=subject_node_storage_path
|
||||
)
|
||||
# Implementation for creating a class
|
||||
pass
|
||||
@ -267,14 +271,14 @@ async def create_class(
|
||||
@router.post("/create-room")
|
||||
async def create_room(
|
||||
db_name: str = Form(...),
|
||||
room_unique_id: str = Form(...),
|
||||
room_uuid_string: str = Form(...),
|
||||
room_code: str = Form(...),
|
||||
path: str = Form(...)
|
||||
room_node_storage_path: str = Form(...)
|
||||
):
|
||||
room = RoomNode(
|
||||
room_unique_id=room_unique_id,
|
||||
room_uuid_string=room_uuid_string,
|
||||
room_code=room_code,
|
||||
path=path
|
||||
node_storage_path=room_node_storage_path
|
||||
)
|
||||
# Implementation for creating a room
|
||||
pass
|
||||
@ -15,7 +15,7 @@ logging = logger.get_logger(
|
||||
from fastapi import APIRouter, File, UploadFile, Form, HTTPException, BackgroundTasks
|
||||
import pandas as pd
|
||||
import modules.database.tools.neo4j_driver_tools as driver
|
||||
from modules.database.tools.neo4j_session_tools import get_node_by_unique_id
|
||||
from modules.database.tools.neo4j_session_tools import get_node_by_uuid_string
|
||||
import modules.database.init.init_school_timetable as init_school_timetable
|
||||
import modules.database.init.init_worker_timetable as init_worker_timetable
|
||||
from modules.database.schemas.nodes.schools.schools import SchoolNode
|
||||
@ -28,18 +28,16 @@ router = APIRouter()
|
||||
async def upload_school_timetable(
|
||||
file: UploadFile = File(...),
|
||||
db_name: str = Form(...),
|
||||
unique_id: str = Form(...),
|
||||
school_uuid: str = Form(...),
|
||||
school_uuid_string: str = Form(...),
|
||||
school_name: str = Form(...),
|
||||
school_website: str = Form(...),
|
||||
path: str = Form(...)
|
||||
school_node_storage_path: str = Form(...)
|
||||
):
|
||||
school_node = SchoolNode(
|
||||
unique_id=unique_id,
|
||||
school_uuid=school_uuid,
|
||||
school_name=school_name,
|
||||
school_website=school_website,
|
||||
path=path
|
||||
uuid_string=school_uuid_string,
|
||||
name=school_name,
|
||||
website=school_website,
|
||||
node_storage_path=school_node_storage_path
|
||||
)
|
||||
if file.content_type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||
return {"status": "Error", "message": "Invalid file format"}
|
||||
@ -91,13 +89,13 @@ async def process_worker_timetable(file_content, worker_node_data):
|
||||
timetable_df = pd.read_excel(BytesIO(file_content))
|
||||
|
||||
# Get the school version of the worker node
|
||||
logging.info(f"Getting school worker node for {worker_node_data['unique_id']} from {worker_node_data['worker_db_name']}")
|
||||
logging.info(f"Getting school worker node for {worker_node_data['uuid_string']} from {worker_node_data['worker_db_name']}")
|
||||
|
||||
with neo_driver.session(database=worker_node_data['worker_db_name']) as neo_session:
|
||||
school_worker_node = get_node_by_unique_id(session=neo_session, unique_id=worker_node_data['unique_id'])
|
||||
school_worker_node = get_node_by_uuid_string(session=neo_session, uuid_string=worker_node_data['uuid_string'])
|
||||
|
||||
if school_worker_node is None:
|
||||
error_msg = f"School worker node not found for unique_id: {worker_node_data['unique_id']}"
|
||||
error_msg = f"School worker node not found for uuid_string: {worker_node_data['uuid_string']}"
|
||||
logging.error(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ logging = logger.get_logger(
|
||||
from fastapi import APIRouter, File, UploadFile, Form, HTTPException, BackgroundTasks
|
||||
import pandas as pd
|
||||
import modules.database.tools.neo4j_driver_tools as driver
|
||||
from modules.database.tools.neo4j_session_tools import get_node_by_unique_id
|
||||
from modules.database.tools.neo4j_session_tools import get_node_by_uuid_string
|
||||
import modules.database.init.init_school_timetable as init_school_timetable
|
||||
import modules.database.init.init_worker_timetable as init_worker_timetable
|
||||
from modules.database.schemas.nodes.users import UserNode
|
||||
@ -31,18 +31,16 @@ router = APIRouter()
|
||||
async def upload_school_timetable(
|
||||
file: UploadFile = File(...),
|
||||
db_name: str = Form(...),
|
||||
unique_id: str = Form(...),
|
||||
school_uuid: str = Form(...),
|
||||
school_uuid_string: str = Form(...),
|
||||
school_name: str = Form(...),
|
||||
school_website: str = Form(...),
|
||||
path: str = Form(...)
|
||||
school_node_storage_path: str = Form(...)
|
||||
):
|
||||
school_node = SchoolNode(
|
||||
unique_id=unique_id,
|
||||
school_uuid=school_uuid,
|
||||
school_name=school_name,
|
||||
school_website=school_website,
|
||||
path=path
|
||||
uuid_string=school_uuid_string,
|
||||
name=school_name,
|
||||
website=school_website,
|
||||
node_storage_path=school_node_storage_path
|
||||
)
|
||||
if file.content_type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||
return {"status": "Error", "message": "Invalid file format"}
|
||||
@ -101,13 +99,13 @@ async def process_worker_timetable(file_content, user_node_data, worker_node_dat
|
||||
timetable_df = pd.read_excel(BytesIO(file_content))
|
||||
|
||||
# Get the school version of the worker node
|
||||
logging.info(f"Getting school worker node for {worker_node_data['unique_id']} from {worker_node_data['worker_db_name']}")
|
||||
logging.info(f"Getting school worker node for {worker_node_data['uuid_string']} from {worker_node_data['worker_db_name']}")
|
||||
|
||||
with neo_driver.session(database=worker_node_data['worker_db_name']) as neo_session:
|
||||
school_worker_node = get_node_by_unique_id(session=neo_session, unique_id=worker_node_data['unique_id'])
|
||||
school_worker_node = get_node_by_uuid_string(session=neo_session, uuid_string=worker_node_data['uuid_string'])
|
||||
|
||||
if school_worker_node is None:
|
||||
error_msg = f"School worker node not found for unique_id: {worker_node_data['unique_id']}"
|
||||
error_msg = f"School worker node not found for uuid_string: {worker_node_data['uuid_string']}"
|
||||
logging.error(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
@ -127,23 +125,23 @@ async def process_worker_timetable(file_content, user_node_data, worker_node_dat
|
||||
|
||||
# Create TeacherNode from worker_node_data
|
||||
user_worker_node = TeacherNode(
|
||||
unique_id=worker_node_data['unique_id'],
|
||||
uuid_string=worker_node_data['uuid_string'],
|
||||
teacher_code=worker_node_data['teacher_code'],
|
||||
teacher_name_formal=worker_node_data['teacher_name_formal'],
|
||||
teacher_email=worker_node_data['teacher_email'],
|
||||
path=worker_node_data['path'],
|
||||
node_storage_path=worker_node_data['node_storage_path'],
|
||||
worker_db_name=worker_node_data['worker_db_name'],
|
||||
user_db_name=worker_node_data['user_db_name']
|
||||
)
|
||||
|
||||
# Create user node
|
||||
user_node = UserNode(
|
||||
unique_id=user_node_data['unique_id'],
|
||||
uuid_string=user_node_data['uuid_string'],
|
||||
user_id=user_node_data['user_id'],
|
||||
user_type=user_node_data['user_type'],
|
||||
user_name=user_node_data['user_name'],
|
||||
user_email=user_node_data['user_email'],
|
||||
path=user_node_data['path'],
|
||||
node_storage_path=user_node_data['node_storage_path'],
|
||||
worker_node_data=user_node_data['worker_node_data']
|
||||
)
|
||||
|
||||
|
||||
@ -27,29 +27,29 @@ async def get_calendar_structure(db_name: str) -> Dict[str, Any]:
|
||||
// Collect all nodes with dates converted to strings
|
||||
RETURN {
|
||||
years: collect(DISTINCT {
|
||||
id: y.unique_id,
|
||||
path: y.path,
|
||||
id: y.uuid_string,
|
||||
path: y.node_storage_path,
|
||||
date: toString(y.date),
|
||||
__primarylabel__: 'CalendarYear'
|
||||
}),
|
||||
months: collect(DISTINCT {
|
||||
id: m.unique_id,
|
||||
path: m.path,
|
||||
id: m.uuid_string,
|
||||
path: m.node_storage_path,
|
||||
date: toString(m.date),
|
||||
__primarylabel__: 'CalendarMonth'
|
||||
}),
|
||||
weeks: collect(DISTINCT {
|
||||
id: w.unique_id,
|
||||
path: w.path,
|
||||
id: w.uuid_string,
|
||||
path: w.node_storage_path,
|
||||
date: toString(w.date),
|
||||
__primarylabel__: 'CalendarWeek'
|
||||
}),
|
||||
days: collect(DISTINCT {
|
||||
id: d.unique_id,
|
||||
path: d.path,
|
||||
id: d.uuid_string,
|
||||
path: d.node_storage_path,
|
||||
date: toString(d.date),
|
||||
week_id: w.unique_id,
|
||||
month_id: m.unique_id,
|
||||
week_id: w.uuid_string,
|
||||
month_id: m.uuid_string,
|
||||
__primarylabel__: 'CalendarDay'
|
||||
})
|
||||
} as structure
|
||||
@ -98,11 +98,11 @@ async def get_calendar_days(db_name: str, start_date: str, end_date: str) -> Dic
|
||||
OPTIONAL MATCH (w:CalendarWeek)-[:WEEK_INCLUDES_DAY]->(d)
|
||||
OPTIONAL MATCH (m:CalendarMonth)-[:MONTH_INCLUDES_DAY]->(d)
|
||||
RETURN {
|
||||
id: d.unique_id,
|
||||
path: d.path,
|
||||
id: d.uuid_string,
|
||||
path: d.node_storage_path,
|
||||
date: d.date,
|
||||
week_id: w.unique_id,
|
||||
month_id: m.unique_id,
|
||||
week_id: w.uuid_string,
|
||||
month_id: m.uuid_string,
|
||||
__primarylabel__: 'CalendarDay'
|
||||
} as day
|
||||
ORDER BY d.date
|
||||
@ -132,10 +132,10 @@ async def get_calendar_weeks(db_name: str, start_date: str, end_date: str) -> Di
|
||||
WHERE date(w.date) >= date($start_date) AND date(w.date) <= date($end_date)
|
||||
WITH w, collect(d) as days
|
||||
RETURN {
|
||||
id: w.unique_id,
|
||||
path: w.path,
|
||||
id: w.uuid_string,
|
||||
path: w.node_storage_path,
|
||||
date: w.date,
|
||||
day_ids: [day in days | day.unique_id],
|
||||
day_ids: [day in days | day.uuid_string],
|
||||
__primarylabel__: 'CalendarWeek'
|
||||
} as week
|
||||
ORDER BY w.date
|
||||
@ -165,10 +165,10 @@ async def get_calendar_months(db_name: str, start_date: str, end_date: str) -> D
|
||||
WHERE date(m.date) >= date($start_date) AND date(m.date) <= date($end_date)
|
||||
WITH m, collect(d) as days
|
||||
RETURN {
|
||||
id: m.unique_id,
|
||||
path: m.path,
|
||||
id: m.uuid_string,
|
||||
path: m.node_storage_path,
|
||||
date: m.date,
|
||||
day_ids: [day in days | day.unique_id],
|
||||
day_ids: [day in days | day.uuid_string],
|
||||
__primarylabel__: 'CalendarMonth'
|
||||
} as month
|
||||
ORDER BY m.date
|
||||
@ -197,10 +197,10 @@ async def get_calendar_years(db_name: str) -> Dict[str, Any]:
|
||||
MATCH (y:CalendarYear)-[:YEAR_INCLUDES_MONTH]->(m:CalendarMonth)
|
||||
WITH y, collect(m) as months
|
||||
RETURN {
|
||||
id: y.unique_id,
|
||||
path: y.path,
|
||||
id: y.uuid_string,
|
||||
path: y.node_storage_path,
|
||||
date: y.date,
|
||||
month_ids: [month in months | month.unique_id],
|
||||
month_ids: [month in months | month.uuid_string],
|
||||
__primarylabel__: 'CalendarYear'
|
||||
} as year
|
||||
ORDER BY y.date
|
||||
|
||||
@ -47,7 +47,7 @@ def get_default_node_week(db_name: str) -> Dict[str, Any]:
|
||||
return {
|
||||
"status": "success",
|
||||
"node": {
|
||||
"id": node["unique_id"],
|
||||
"id": node["uuid_string"],
|
||||
"path": node["path"],
|
||||
"type": "CalendarWeek",
|
||||
"label": node.get("title", "Calendar Week"),
|
||||
@ -81,7 +81,7 @@ def get_default_node_month(db_name: str) -> Dict[str, Any]:
|
||||
return {
|
||||
"status": "success",
|
||||
"node": {
|
||||
"id": node["unique_id"],
|
||||
"id": node["uuid_string"],
|
||||
"path": node["path"],
|
||||
"type": "CalendarMonth",
|
||||
"label": node.get("title", "Calendar Month"),
|
||||
@ -89,6 +89,39 @@ def get_default_node_month(db_name: str) -> Dict[str, Any]:
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/debug-list-nodes")
|
||||
async def debug_list_nodes(db_name: str) -> Dict[str, Any]:
|
||||
"""Debug endpoint to list all nodes in a database."""
|
||||
try:
|
||||
with driver_tools.get_session(database=db_name) as session:
|
||||
query = """
|
||||
MATCH (n)
|
||||
RETURN labels(n) as labels, n.uuid_string as uuid, n.user_name as name, n.cc_username as username
|
||||
LIMIT 20
|
||||
"""
|
||||
result = session.run(query)
|
||||
nodes = []
|
||||
for record in result:
|
||||
nodes.append({
|
||||
"labels": list(record["labels"]),
|
||||
"uuid": record["uuid"],
|
||||
"name": record["name"],
|
||||
"username": record["username"]
|
||||
})
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"db_name": db_name,
|
||||
"node_count": len(nodes),
|
||||
"nodes": nodes
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"db_name": db_name,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
@router.get("/get-default-node/{context}")
|
||||
async def get_default_node(context: str, db_name: str, base_context: str | None = None) -> Dict[str, Any]:
|
||||
"""Get the default node for a given context."""
|
||||
@ -244,8 +277,9 @@ async def get_default_node(context: str, db_name: str, base_context: str | None
|
||||
return {
|
||||
"status": "success",
|
||||
"node": {
|
||||
"id": node["unique_id"],
|
||||
"id": node["uuid_string"],
|
||||
"path": node["path"],
|
||||
"node_storage_path": node.get("node_storage_path", node["path"]),
|
||||
"type": list(node.labels)[0],
|
||||
"label": node.get("title", ""),
|
||||
"data": converted_data
|
||||
|
||||
@ -51,10 +51,10 @@ router = APIRouter()
|
||||
|
||||
@router.get("/get_teacher_timetable_events")
|
||||
async def get_teacher_timetable_events(
|
||||
unique_id: str,
|
||||
uuid_string: str,
|
||||
worker_db_name: str
|
||||
):
|
||||
logging.info(f"Getting timetable events for teacher {unique_id} from database {worker_db_name}")
|
||||
logging.info(f"Getting timetable events for teacher {uuid_string} from database {worker_db_name}")
|
||||
neo_driver = driver.get_driver(db_name=worker_db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -62,9 +62,9 @@ async def get_teacher_timetable_events(
|
||||
try:
|
||||
with neo_driver.session(database=worker_db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (t:Teacher {unique_id: $unique_id})-[:TEACHER_HAS_TIMETABLE]->(tt:TeacherTimetable)
|
||||
MATCH (t:Teacher {uuid_string: $uuid_string})-[:TEACHER_HAS_TIMETABLE]->(tt:TeacherTimetable)
|
||||
-[:TIMETABLE_HAS_CLASS]->(sc:SubjectClass)-[:CLASS_HAS_LESSON]->(tl:TimetableLesson)
|
||||
RETURN tl.unique_id as id,
|
||||
RETURN tl.uuid_string as id,
|
||||
tl.period_code as period_code,
|
||||
COALESCE(sc.subject_class_code, 'Untitled Class') as subject_class,
|
||||
tl.date as date,
|
||||
@ -72,7 +72,7 @@ async def get_teacher_timetable_events(
|
||||
tl.end_time as end_time,
|
||||
tl.path as path
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
|
||||
events = []
|
||||
for record in result:
|
||||
@ -92,7 +92,7 @@ async def get_teacher_timetable_events(
|
||||
"path": record['path']
|
||||
}
|
||||
})
|
||||
logging.info(f"Found {len(events)} events for teacher {unique_id}")
|
||||
logging.info(f"Found {len(events)} events for teacher {uuid_string}")
|
||||
return {"status": "success", "events": events}
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching events: {str(e)}")
|
||||
|
||||
@ -25,18 +25,18 @@ from fastapi import APIRouter, HTTPException, Query
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/get-node")
|
||||
async def get_node(unique_id: str = Query(...), db_name: str = Query(...)):
|
||||
logging.info(f"Getting node for {unique_id} from database {db_name}")
|
||||
async def get_node(uuid_string: str = Query(...), db_name: str = Query(...)):
|
||||
logging.info(f"Getting node for {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n {unique_id: $unique_id})
|
||||
MATCH (n {uuid_string: $uuid_string})
|
||||
RETURN n
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
|
||||
if record:
|
||||
@ -47,11 +47,23 @@ async def get_node(unique_id: str = Query(...), db_name: str = Query(...)):
|
||||
try:
|
||||
# Convert node based on its type
|
||||
node_type = node_labels[0] if node_labels else "Unknown"
|
||||
if node_type in globals():
|
||||
node_class = globals()[f"{node_type}Node"]
|
||||
logging.debug(f"Attempting to convert node of type: {node_type}")
|
||||
logging.debug(f"Available node classes: {[name for name in globals() if name.endswith('Node')]}")
|
||||
logging.debug(f"UserNode in globals: {'UserNode' in globals()}")
|
||||
logging.debug(f"UserNode class: {UserNode}")
|
||||
logging.debug(f"UserNode class name: {UserNode.__name__}")
|
||||
|
||||
# Try to find the node class
|
||||
node_class_name = f"{node_type}Node"
|
||||
if node_class_name in globals():
|
||||
node_class = globals()[node_class_name]
|
||||
logging.debug(f"Found node class: {node_class}")
|
||||
node_object = node_class(**node_data)
|
||||
node_dict = node_object.to_dict()
|
||||
logging.debug(f"Successfully converted node to dict: {node_dict}")
|
||||
else:
|
||||
logging.warning(f"No node class found for type: {node_type} (looking for {node_class_name}), using raw data")
|
||||
logging.debug(f"Available classes: {[name for name in globals() if 'Node' in name]}")
|
||||
node_dict = node_data
|
||||
|
||||
return {
|
||||
@ -101,8 +113,8 @@ async def get_user_node(user_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-connected-nodes")
|
||||
async def get_connected_nodes(unique_id: str = Query(...), db_name: str = Query(...)):
|
||||
logging.info(f"Getting connected nodes for {unique_id} from database {db_name}")
|
||||
async def get_connected_nodes(uuid_string: str = Query(...), db_name: str = Query(...)):
|
||||
logging.info(f"Getting connected nodes for {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -110,11 +122,11 @@ async def get_connected_nodes(unique_id: str = Query(...), db_name: str = Query(
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n {unique_id: $unique_id})
|
||||
MATCH (n {uuid_string: $uuid_string})
|
||||
OPTIONAL MATCH (n)-[]-(connected)
|
||||
RETURN n, collect(connected) as connected_nodes
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
main_node = record['n']
|
||||
@ -171,15 +183,15 @@ async def get_connected_nodes(unique_id: str = Query(...), db_name: str = Query(
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-user-connected-nodes")
|
||||
async def get_user_connected_nodes(unique_id: str = Query(...)):
|
||||
logging.info(f"Getting user adjacent nodes for node {unique_id}")
|
||||
async def get_user_connected_nodes(uuid_string: str = Query(...)):
|
||||
logging.info(f"Getting user adjacent nodes for node {uuid_string}")
|
||||
db_name = os.getenv("NEO4J_DB_NAME", "cc.institutes.kevlarai") # TODO: This function needs to be able to take a db_name as a parameter
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
raise HTTPException(status_code=500, detail="Failed to connect to the database")
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
user_node_and_connected_nodes = session.get_node_by_unique_id_and_adjacent_nodes(neo_session, unique_id)
|
||||
user_node_and_connected_nodes = session.get_node_by_uuid_string_and_adjacent_nodes(neo_session, uuid_string)
|
||||
user_node = user_node_and_connected_nodes['node']
|
||||
connected_nodes = user_node_and_connected_nodes['connected_nodes']
|
||||
try:
|
||||
@ -251,15 +263,15 @@ async def get_user_connected_nodes(unique_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-worker-connected-nodes")
|
||||
async def get_worker_connected_nodes(unique_id: str = Query(...)):
|
||||
logging.info(f"Getting worker adjacent nodes for node {unique_id}")
|
||||
async def get_worker_connected_nodes(uuid_string: str = Query(...)):
|
||||
logging.info(f"Getting worker adjacent nodes for node {uuid_string}")
|
||||
db_name = os.getenv("NEO4J_DB_NAME", "cc.institutes.kevlarai") # TODO: This function needs to be able to take a db_name as a parameter
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
raise HTTPException(status_code=500, detail="Failed to connect to the database")
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
node_and_connected_nodes = session.get_node_by_unique_id_and_adjacent_nodes(neo_session, unique_id)
|
||||
node_and_connected_nodes = session.get_node_by_uuid_string_and_adjacent_nodes(neo_session, uuid_string)
|
||||
worker_node = node_and_connected_nodes['node']
|
||||
connected_nodes = node_and_connected_nodes['connected_nodes']
|
||||
try:
|
||||
@ -319,9 +331,9 @@ async def get_worker_connected_nodes(unique_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-calendar-connected-nodes")
|
||||
async def get_calendar_connected_nodes(unique_id: str = Query(...)):
|
||||
async def get_calendar_connected_nodes(uuid_string: str = Query(...)):
|
||||
db_name = os.getenv("NEO4J_DB_NAME", "cc.institutes.kevlarai")
|
||||
logging.info(f"Getting connected nodes for calendar {unique_id} from database {db_name}")
|
||||
logging.info(f"Getting connected nodes for calendar {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -330,11 +342,11 @@ async def get_calendar_connected_nodes(unique_id: str = Query(...)):
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n)
|
||||
WHERE n.unique_id = $unique_id AND (n:Calendar OR n:CalendarYear OR n:CalendarMonth OR n:CalendarWeek OR n:CalendarDay OR n:CalendarTimeChunk)
|
||||
WHERE n.uuid_string = $uuid_string AND (n:Calendar OR n:CalendarYear OR n:CalendarMonth OR n:CalendarWeek OR n:CalendarDay OR n:CalendarTimeChunk)
|
||||
OPTIONAL MATCH (n)-[]-(connected)
|
||||
RETURN n, collect(connected) as connected_nodes
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
calendar_node = record['n']
|
||||
@ -369,9 +381,9 @@ async def get_calendar_connected_nodes(unique_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-teacher-timetable-connected-nodes")
|
||||
async def get_teacher_timetable_connected_nodes(unique_id: str = Query(...)):
|
||||
async def get_teacher_timetable_connected_nodes(uuid_string: str = Query(...)):
|
||||
db_name = os.getenv("NEO4J_DB_NAME", "cc.institutes.kevlarai")
|
||||
logging.info(f"Getting connected nodes for teacher timetable {unique_id} from database {db_name}")
|
||||
logging.info(f"Getting connected nodes for teacher timetable {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -379,11 +391,11 @@ async def get_teacher_timetable_connected_nodes(unique_id: str = Query(...)):
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n:TeacherTimetable {unique_id: $unique_id})
|
||||
MATCH (n:TeacherTimetable {uuid_string: $uuid_string})
|
||||
OPTIONAL MATCH (n)-[]-(connected)
|
||||
RETURN n, collect(connected) as connected_nodes
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
teacher_timetable_node = record['n']
|
||||
@ -422,9 +434,9 @@ async def get_teacher_timetable_connected_nodes(unique_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-school-timetable-connected-nodes")
|
||||
async def get_school_timetable_connected_nodes(unique_id: str = Query(...)):
|
||||
async def get_school_timetable_connected_nodes(uuid_string: str = Query(...)):
|
||||
db_name = os.getenv("NEO4J_DB_NAME", "cc.institutes.kevlarai")
|
||||
logging.info(f"Getting connected nodes for school timetable {unique_id} from database {db_name}")
|
||||
logging.info(f"Getting connected nodes for school timetable {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -432,11 +444,11 @@ async def get_school_timetable_connected_nodes(unique_id: str = Query(...)):
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n:SchoolTimetable {unique_id: $unique_id})
|
||||
MATCH (n:SchoolTimetable {uuid_string: $uuid_string})
|
||||
OPTIONAL MATCH (n)-[]-(connected)
|
||||
RETURN n, collect(connected) as connected_nodes
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
school_timetable_node = record['n']
|
||||
@ -483,9 +495,9 @@ async def get_school_timetable_connected_nodes(unique_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-curriculum-connected-nodes")
|
||||
async def get_curriculum_connected_nodes(unique_id: str = Query(...)):
|
||||
async def get_curriculum_connected_nodes(uuid_string: str = Query(...)):
|
||||
db_name = os.getenv("NEO4J_DB_NAME", "cc.institutes.kevlarai")
|
||||
logging.info(f"Getting connected nodes for curriculum {unique_id} from database {db_name}")
|
||||
logging.info(f"Getting connected nodes for curriculum {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -494,11 +506,11 @@ async def get_curriculum_connected_nodes(unique_id: str = Query(...)):
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n)
|
||||
WHERE n.unique_id = $unique_id AND (n:PastoralStructure OR n:YearGroup OR n:CurriculumStructure OR n:KeyStage OR n:KeyStageSyllabus OR n:YearGroupSyllabus OR n:Subject OR n:Topic OR n:TopicLesson OR n:LearningStatement OR n:ScienceLab)
|
||||
WHERE n.uuid_string = $uuid_string AND (n:PastoralStructure OR n:YearGroup OR n:CurriculumStructure OR n:KeyStage OR n:KeyStageSyllabus OR n:YearGroupSyllabus OR n:Subject OR n:Topic OR n:TopicLesson OR n:LearningStatement OR n:ScienceLab)
|
||||
OPTIONAL MATCH (n)-[]-(connected)
|
||||
RETURN n, collect(connected) as connected_nodes
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
curriculum_node = record['n']
|
||||
@ -533,26 +545,18 @@ async def get_curriculum_connected_nodes(unique_id: str = Query(...)):
|
||||
driver.close_driver(neo_driver)
|
||||
|
||||
@router.get("/get-school-node")
|
||||
async def get_school_node(school_uuid: str = Query(...)):
|
||||
logging.info(f"Getting school node for school {school_uuid}...")
|
||||
db_name = f"cc.institutes.{school_uuid}"
|
||||
async def get_school_node(school_uuid_string: str = Query(...)):
|
||||
logging.info(f"Getting school node for school {school_uuid_string}...")
|
||||
db_name = f"cc.institutes.{school_uuid_string}"
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
nodes = session.find_nodes_by_label_and_properties(neo_session, "School", {"school_uuid": school_uuid})
|
||||
nodes = session.find_nodes_by_label_and_properties(neo_session, "School", {"uuid_string": school_uuid_string})
|
||||
if nodes:
|
||||
school_node = nodes[0]
|
||||
data = SchoolNode(
|
||||
unique_id=school_node["unique_id"],
|
||||
school_uuid=school_node["school_uuid"],
|
||||
school_name=school_node["school_name"],
|
||||
school_website=school_node["school_website"],
|
||||
path=school_node["path"]
|
||||
)
|
||||
school_node_data = data.to_dict()
|
||||
school_node_data = SchoolNode(**nodes[0]).to_dict()
|
||||
return {"status": "success", "school_node": school_node_data, "school_node_raw": nodes}
|
||||
else:
|
||||
return {"status": "not_found", "message": "School node not found"}
|
||||
|
||||
@ -89,8 +89,8 @@ async def get_all_nodes_and_edges():
|
||||
|
||||
|
||||
@router.get("/get-connected-nodes-and-edges")
|
||||
async def get_connected_nodes_and_edges(unique_id: str = Query(...), db_name: str = Query(...)):
|
||||
logging.info(f"Getting connected nodes and edges for {unique_id} from database {db_name}")
|
||||
async def get_connected_nodes_and_edges(uuid_string: str = Query(...), db_name: str = Query(...)):
|
||||
logging.info(f"Getting connected nodes and edges for {uuid_string} from database {db_name}")
|
||||
neo_driver = driver.get_driver(db_name=db_name)
|
||||
if neo_driver is None:
|
||||
return {"status": "error", "message": "Failed to connect to the database"}
|
||||
@ -98,11 +98,11 @@ async def get_connected_nodes_and_edges(unique_id: str = Query(...), db_name: st
|
||||
try:
|
||||
with neo_driver.session(database=db_name) as neo_session:
|
||||
query = """
|
||||
MATCH (n {unique_id: $unique_id})
|
||||
MATCH (n {uuid_string: $uuid_string})
|
||||
OPTIONAL MATCH (n)-[r]-(connected)
|
||||
RETURN n, collect(connected) as connected_nodes, collect(r) as relationships
|
||||
"""
|
||||
result = neo_session.run(query, unique_id=unique_id)
|
||||
result = neo_session.run(query, uuid_string=uuid_string)
|
||||
record = result.single()
|
||||
if record:
|
||||
main_node = record['n']
|
||||
|
||||
@ -34,20 +34,24 @@ async def read_tldraw_user_node_file(user_node: UserNode):
|
||||
|
||||
logging.debug(f"Filesystem root path: {fs.root_path}")
|
||||
|
||||
# Handle path based on environment
|
||||
if os.getenv("DEV_MODE") == "true":
|
||||
# In dev mode, use the full system path from the node
|
||||
if not user_node.path:
|
||||
raise HTTPException(status_code=400, detail="Node path not found")
|
||||
logging.debug(f"Using DEV_MODE path: {user_node.path}")
|
||||
base_path = os.path.normpath(user_node.path)
|
||||
# Use the path directly as provided - it represents the structure from root
|
||||
if not user_node.node_storage_path:
|
||||
raise HTTPException(status_code=400, detail="Node path not found")
|
||||
|
||||
# The path might already contain parts of the filesystem structure
|
||||
# We need to construct the full path carefully
|
||||
if user_node.node_storage_path.startswith("users/"):
|
||||
# If path starts with users/, remove it since filesystem already has users/ structure
|
||||
base_path = user_node.node_storage_path[6:] # Remove "users/" prefix
|
||||
logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}")
|
||||
else:
|
||||
# In prod mode, construct path using formatted email
|
||||
logging.warning(f"Using db_name as base path not ready in prod: {db_name}")
|
||||
base_path = formatted_email
|
||||
base_path = user_node.node_storage_path
|
||||
logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}")
|
||||
|
||||
base_path = os.path.normpath(base_path)
|
||||
logging.debug(f"Using base path: {base_path}")
|
||||
|
||||
# Construct final path including tldraw file
|
||||
logging.debug(f"Base path: {base_path}")
|
||||
file_path = os.path.join(base_path, "tldraw_file.json")
|
||||
logging.debug(f"File path: {file_path}")
|
||||
file_location = os.path.normpath(os.path.join(fs.root_path, file_path))
|
||||
@ -68,8 +72,30 @@ async def read_tldraw_user_node_file(user_node: UserNode):
|
||||
logging.error(f"Error reading file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error reading file")
|
||||
else:
|
||||
logging.debug(f"File does not exist: {file_location}")
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
# Check if directory exists
|
||||
directory_location = os.path.dirname(file_location)
|
||||
if os.path.exists(directory_location):
|
||||
logging.debug(f"Directory exists but file doesn't, creating default tldraw file at: {file_location}")
|
||||
try:
|
||||
# Create default tldraw content
|
||||
default_tldraw_content = create_default_tldraw_content()
|
||||
|
||||
# Ensure directory exists (should already exist, but just in case)
|
||||
os.makedirs(directory_location, exist_ok=True)
|
||||
|
||||
# Write the default file
|
||||
with open(file_location, "w") as file:
|
||||
json.dump(default_tldraw_content, file, indent=4)
|
||||
|
||||
logging.info(f"Default tldraw file created at: {file_location}")
|
||||
return default_tldraw_content
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating default tldraw file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error creating default tldraw file")
|
||||
else:
|
||||
logging.debug(f"Neither directory nor file exists: {directory_location}")
|
||||
raise HTTPException(status_code=404, detail="Directory not found")
|
||||
|
||||
@router.post("/set_tldraw_user_node_file")
|
||||
async def set_tldraw_user_node_file(user_node: UserNode, data: Dict):
|
||||
@ -81,15 +107,22 @@ async def set_tldraw_user_node_file(user_node: UserNode, data: Dict):
|
||||
|
||||
fs = ClassroomCopilotFilesystem(db_name=db_name, init_run_type="user")
|
||||
|
||||
# Handle path based on environment
|
||||
if os.getenv("ENVIRONMENT") == "dev":
|
||||
# In dev mode, use the full system path from the node
|
||||
if not user_node.path:
|
||||
raise HTTPException(status_code=400, detail="Node path not found")
|
||||
base_path = os.path.normpath(user_node.path)
|
||||
# Use the path directly as provided - it represents the structure from root
|
||||
if not user_node.node_storage_path:
|
||||
raise HTTPException(status_code=400, detail="Node path not found")
|
||||
|
||||
# The path might already contain parts of the filesystem structure
|
||||
# We need to construct the full path carefully
|
||||
if user_node.node_storage_path.startswith("users/"):
|
||||
# If path starts with users/, remove it since filesystem already has users/ structure
|
||||
base_path = user_node.node_storage_path[6:] # Remove "users/" prefix
|
||||
logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}")
|
||||
else:
|
||||
# In prod mode, construct path using formatted email
|
||||
base_path = formatted_email
|
||||
base_path = user_node.node_storage_path
|
||||
logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}")
|
||||
|
||||
base_path = os.path.normpath(base_path)
|
||||
logging.debug(f"Using base path: {base_path}")
|
||||
|
||||
# Construct final path including tldraw file
|
||||
file_path = os.path.join(base_path, "tldraw_file.json")
|
||||
@ -99,11 +132,15 @@ async def set_tldraw_user_node_file(user_node: UserNode, data: Dict):
|
||||
|
||||
try:
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(file_location), exist_ok=True)
|
||||
directory_location = os.path.dirname(file_location)
|
||||
os.makedirs(directory_location, exist_ok=True)
|
||||
logging.debug(f"Ensured directory exists: {directory_location}")
|
||||
|
||||
# Write the file
|
||||
with open(file_location, "w") as file:
|
||||
json.dump(data, file)
|
||||
json.dump(data, file, indent=4)
|
||||
|
||||
logging.info(f"tldraw file successfully written to: {file_location}")
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
logging.error(f"Error writing file: {e}")
|
||||
@ -112,29 +149,38 @@ async def set_tldraw_user_node_file(user_node: UserNode, data: Dict):
|
||||
@router.get("/get_tldraw_node_file")
|
||||
async def read_tldraw_node_file(path: str, db_name: str):
|
||||
logging.debug(f"Reading tldraw file for path: {path}")
|
||||
logging.debug(f"Database name: {db_name}")
|
||||
|
||||
fs = ClassroomCopilotFilesystem(db_name=db_name, init_run_type="user")
|
||||
|
||||
logging.debug(f"Filesystem root path: {fs.root_path}")
|
||||
|
||||
# Handle path based on environment
|
||||
if os.getenv("DEV_MODE") == "true":
|
||||
# In dev mode, use the full system path from the node
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
logging.debug(f"Using DEV_MODEpath: {path}")
|
||||
base_path = os.path.normpath(path)
|
||||
# Use the path directly as provided - it represents the structure from root
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
|
||||
# The path might already contain parts of the filesystem structure
|
||||
# We need to construct the full path carefully
|
||||
if path.startswith("users/"):
|
||||
# If path starts with users/, remove it since filesystem already has users/ structure
|
||||
base_path = path[6:] # Remove "users/" prefix
|
||||
logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}")
|
||||
else:
|
||||
# In prod mode, construct path
|
||||
logging.warning(f"Using db_name as base path not ready in prod: {db_name}")
|
||||
base_path = db_name
|
||||
base_path = path
|
||||
logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}")
|
||||
|
||||
base_path = os.path.normpath(base_path)
|
||||
logging.debug(f"Using base path: {base_path}")
|
||||
|
||||
# Construct final path including tldraw file
|
||||
logging.debug(f"Base path: {base_path}")
|
||||
file_path = os.path.join(base_path, "tldraw_file.json")
|
||||
logging.debug(f"File path: {file_path}")
|
||||
file_location = os.path.normpath(os.path.join(fs.root_path, file_path))
|
||||
logging.debug(f"File location: {file_location}")
|
||||
logging.debug(f"Final file location: {file_location}")
|
||||
|
||||
# Debug: Check what directories exist
|
||||
logging.debug(f"Checking if root path exists: {fs.root_path} - {os.path.exists(fs.root_path)}")
|
||||
logging.debug(f"Checking if base path exists: {os.path.join(fs.root_path, base_path)} - {os.path.exists(os.path.join(fs.root_path, base_path))}")
|
||||
|
||||
logging.debug(f"Attempting to read file at: {file_location}")
|
||||
|
||||
@ -151,8 +197,44 @@ async def read_tldraw_node_file(path: str, db_name: str):
|
||||
logging.error(f"Error reading file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error reading file")
|
||||
else:
|
||||
logging.debug(f"File does not exist: {file_location}")
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
# Check if directory exists
|
||||
directory_location = os.path.dirname(file_location)
|
||||
logging.debug(f"Checking if directory exists: {directory_location} - {os.path.exists(directory_location)}")
|
||||
|
||||
if os.path.exists(directory_location):
|
||||
logging.debug(f"Directory exists but file doesn't, creating default tldraw file at: {file_location}")
|
||||
try:
|
||||
# Create default tldraw content
|
||||
default_tldraw_content = create_default_tldraw_content()
|
||||
|
||||
# Ensure directory exists (should already exist, but just in case)
|
||||
os.makedirs(directory_location, exist_ok=True)
|
||||
|
||||
# Write the default file
|
||||
with open(file_location, "w") as file:
|
||||
json.dump(default_tldraw_content, file, indent=4)
|
||||
|
||||
logging.info(f"Default tldraw file created at: {file_location}")
|
||||
return default_tldraw_content
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error creating default tldraw file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error creating default tldraw file")
|
||||
else:
|
||||
logging.debug(f"Neither directory nor file exists: {directory_location}")
|
||||
# List contents of parent directories to help debug
|
||||
parent_dir = os.path.dirname(directory_location)
|
||||
if os.path.exists(parent_dir):
|
||||
logging.debug(f"Parent directory exists: {parent_dir}")
|
||||
try:
|
||||
contents = os.listdir(parent_dir)
|
||||
logging.debug(f"Parent directory contents: {contents}")
|
||||
except Exception as e:
|
||||
logging.debug(f"Could not list parent directory contents: {e}")
|
||||
else:
|
||||
logging.debug(f"Parent directory does not exist: {parent_dir}")
|
||||
|
||||
raise HTTPException(status_code=404, detail="Directory not found")
|
||||
|
||||
@router.post("/set_tldraw_node_file")
|
||||
async def set_tldraw_node_file(path: str, db_name: str, data: Dict):
|
||||
@ -162,22 +244,25 @@ async def set_tldraw_node_file(path: str, db_name: str, data: Dict):
|
||||
|
||||
logging.debug(f"Filesystem root path: {fs.root_path}")
|
||||
|
||||
# Handle path based on environment
|
||||
if os.getenv("DEV_MODE") == "true":
|
||||
# In dev mode, use the full system path from the node
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
logging.debug(f"Using DEV_MODEpath: {path}")
|
||||
base_path = os.path.normpath(path)
|
||||
# Use the path directly as provided - it represents the structure from root
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
|
||||
# The path might already contain parts of the filesystem structure
|
||||
# We need to construct the full path carefully
|
||||
if path.startswith("users/"):
|
||||
# If path starts with users/, remove it since filesystem already has users/ structure
|
||||
base_path = path[6:] # Remove "users/" prefix
|
||||
logging.debug(f"Removed 'users/' prefix, base_path is now: {base_path}")
|
||||
else:
|
||||
# In prod mode, construct path
|
||||
logging.warning(f"Using db_name as base path not ready in prod: {db_name}")
|
||||
base_path = db_name
|
||||
base_path = path
|
||||
logging.debug(f"No 'users/' prefix found, using path as-is: {base_path}")
|
||||
|
||||
base_path = os.path.normpath(base_path)
|
||||
logging.debug(f"Using base path: {base_path}")
|
||||
|
||||
# Construct final path including tldraw file
|
||||
logging.debug(f"Base path: {base_path}")
|
||||
file_path = os.path.join(base_path, "tldraw_file.json")
|
||||
logging.debug(f"File path: {file_path}")
|
||||
file_location = os.path.normpath(os.path.join(fs.root_path, file_path))
|
||||
logging.debug(f"File location: {file_location}")
|
||||
|
||||
@ -185,12 +270,94 @@ async def set_tldraw_node_file(path: str, db_name: str, data: Dict):
|
||||
|
||||
try:
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(file_location), exist_ok=True)
|
||||
directory_location = os.path.dirname(file_location)
|
||||
os.makedirs(directory_location, exist_ok=True)
|
||||
logging.debug(f"Ensured directory exists: {directory_location}")
|
||||
|
||||
# Write the file
|
||||
with open(file_location, "w") as file:
|
||||
json.dump(data, file)
|
||||
json.dump(data, file, indent=4)
|
||||
|
||||
logging.info(f"tldraw file successfully written to: {file_location}")
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
logging.error(f"Error writing file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error writing file")
|
||||
|
||||
def create_default_tldraw_content():
|
||||
"""Create default tldraw content structure."""
|
||||
return {
|
||||
"document": {
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": "",
|
||||
"meta": {},
|
||||
"id": "document:document",
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page:page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"schemaVersion": 2,
|
||||
"sequences": {
|
||||
"com.tldraw.store": 4,
|
||||
"com.tldraw.asset": 1,
|
||||
"com.tldraw.camera": 1,
|
||||
"com.tldraw.document": 2,
|
||||
"com.tldraw.instance": 25,
|
||||
"com.tldraw.instance_page_state": 5,
|
||||
"com.tldraw.page": 1,
|
||||
"com.tldraw.instance_presence": 5,
|
||||
"com.tldraw.pointer": 1,
|
||||
"com.tldraw.shape": 4,
|
||||
"com.tldraw.asset.bookmark": 2,
|
||||
"com.tldraw.asset.image": 5,
|
||||
"com.tldraw.asset.video": 5,
|
||||
"com.tldraw.shape.arrow": 5,
|
||||
"com.tldraw.shape.bookmark": 2,
|
||||
"com.tldraw.shape.draw": 2,
|
||||
"com.tldraw.shape.embed": 4,
|
||||
"com.tldraw.shape.frame": 0,
|
||||
"com.tldraw.shape.geo": 9,
|
||||
"com.tldraw.shape.group": 0,
|
||||
"com.tldraw.shape.highlight": 1,
|
||||
"com.tldraw.shape.image": 4,
|
||||
"com.tldraw.shape.line": 5,
|
||||
"com.tldraw.shape.note": 8,
|
||||
"com.tldraw.shape.text": 2,
|
||||
"com.tldraw.shape.video": 2,
|
||||
"com.tldraw.binding.arrow": 0
|
||||
}
|
||||
},
|
||||
"recordVersions": {
|
||||
"asset": {"version": 1, "subTypeKey": "type", "subTypeVersions": {}},
|
||||
"camera": {"version": 1},
|
||||
"document": {"version": 2},
|
||||
"instance": {"version": 21},
|
||||
"instance_page_state": {"version": 5},
|
||||
"page": {"version": 1},
|
||||
"shape": {"version": 3, "subTypeKey": "type", "subTypeVersions": {}},
|
||||
"instance_presence": {"version": 5},
|
||||
"pointer": {"version": 1}
|
||||
},
|
||||
"rootShapeIds": [],
|
||||
"bindings": [],
|
||||
"assets": []
|
||||
},
|
||||
"session": {
|
||||
"version": 0,
|
||||
"currentPageId": "page:page",
|
||||
"pageStates": [{
|
||||
"pageId": "page:page",
|
||||
"camera": {"x": 0, "y": 0, "z": 1},
|
||||
"selectedShapeIds": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
265
routers/database/tools/tldraw_supabase_storage.py
Normal file
265
routers/database/tools/tldraw_supabase_storage.py
Normal file
@ -0,0 +1,265 @@
|
||||
"""
|
||||
TLDraw Supabase Storage Router
|
||||
=============================
|
||||
|
||||
Handles TLDraw snapshot operations using Supabase Storage instead of local filesystem.
|
||||
This replaces the old filesystem-based tldraw_filesystem.py router.
|
||||
"""
|
||||
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
load_dotenv(find_dotenv())
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from typing import Dict, Any
|
||||
|
||||
from modules.database.supabase.utils.storage import StorageAdmin
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
router = APIRouter()
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
def create_default_tldraw_content():
|
||||
"""Create default tldraw content structure."""
|
||||
return {
|
||||
"document": {
|
||||
"store": {
|
||||
"document:document": {
|
||||
"gridSize": 10,
|
||||
"name": "",
|
||||
"meta": {},
|
||||
"id": "document:document",
|
||||
"typeName": "document"
|
||||
},
|
||||
"page:page": {
|
||||
"meta": {},
|
||||
"id": "page:page",
|
||||
"name": "Page 1",
|
||||
"index": "a1",
|
||||
"typeName": "page"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"schemaVersion": 2,
|
||||
"sequences": {
|
||||
"com.tldraw.store": 4,
|
||||
"com.tldraw.asset": 1,
|
||||
"com.tldraw.camera": 1,
|
||||
"com.tldraw.document": 2,
|
||||
"com.tldraw.instance": 25,
|
||||
"com.tldraw.instance_page_state": 5,
|
||||
"com.tldraw.page": 1,
|
||||
"com.tldraw.instance_presence": 5,
|
||||
"com.tldraw.pointer": 1,
|
||||
"com.tldraw.shape": 4,
|
||||
"com.tldraw.asset.bookmark": 2,
|
||||
"com.tldraw.asset.image": 5,
|
||||
"com.tldraw.asset.video": 5,
|
||||
"com.tldraw.shape.arrow": 5,
|
||||
"com.tldraw.shape.bookmark": 2,
|
||||
"com.tldraw.shape.draw": 2,
|
||||
"com.tldraw.shape.embed": 4,
|
||||
"com.tldraw.shape.frame": 0,
|
||||
"com.tldraw.shape.geo": 9,
|
||||
"com.tldraw.shape.group": 0,
|
||||
"com.tldraw.shape.highlight": 1,
|
||||
"com.tldraw.shape.image": 4,
|
||||
"com.tldraw.shape.line": 5,
|
||||
"com.tldraw.shape.note": 8,
|
||||
"com.tldraw.shape.text": 2,
|
||||
"com.tldraw.shape.video": 2,
|
||||
"com.tldraw.binding.arrow": 0
|
||||
}
|
||||
},
|
||||
"recordVersions": {
|
||||
"asset": {"version": 1, "subTypeKey": "type", "subTypeVersions": {}},
|
||||
"camera": {"version": 1},
|
||||
"document": {"version": 2},
|
||||
"instance": {"version": 21},
|
||||
"instance_page_state": {"version": 5},
|
||||
"page": {"version": 1},
|
||||
"shape": {"version": 3, "subTypeKey": "type", "subTypeVersions": {}},
|
||||
"instance_presence": {"version": 5},
|
||||
"pointer": {"version": 1}
|
||||
},
|
||||
"rootShapeIds": [],
|
||||
"bindings": [],
|
||||
"assets": []
|
||||
},
|
||||
"session": {
|
||||
"version": 0,
|
||||
"currentPageId": "page:page",
|
||||
"pageStates": [{
|
||||
"pageId": "page:page",
|
||||
"camera": {"x": 0, "y": 0, "z": 1},
|
||||
"selectedShapeIds": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/get_tldraw_node_file")
|
||||
async def read_tldraw_node_file_from_supabase(
|
||||
path: str = Query(..., description="Supabase Storage path (e.g., 'cc.public.snapshots/User/user_id')"),
|
||||
db_name: str = Query(..., description="Database name for context")
|
||||
):
|
||||
"""
|
||||
Load TLDraw snapshot from Supabase Storage.
|
||||
|
||||
Args:
|
||||
path: Supabase Storage path in format 'bucket/nodetype/node_id'
|
||||
db_name: Database name for context (used for logging)
|
||||
|
||||
Returns:
|
||||
TLDraw snapshot data
|
||||
"""
|
||||
logger.debug(f"Reading tldraw file from Supabase Storage for path: {path}")
|
||||
logger.debug(f"Database name: {db_name}")
|
||||
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
|
||||
try:
|
||||
# Initialize Supabase Storage
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Parse the path to extract bucket and file path
|
||||
# Expected format: "cc.public.snapshots/User/user_id" or "cc.public.snapshots/Teacher/teacher_id"
|
||||
path_parts = path.split('/')
|
||||
if len(path_parts) < 3:
|
||||
raise HTTPException(status_code=400, detail="Invalid path format. Expected: bucket/nodetype/node_id")
|
||||
|
||||
bucket = path_parts[0] # e.g., "cc.public.snapshots"
|
||||
node_type = path_parts[1] # e.g., "User", "Teacher"
|
||||
node_id = path_parts[2] # e.g., "cbc309e5-4029-4c34-aab7-0aa33c563cd0"
|
||||
|
||||
# Construct the file path in Supabase Storage
|
||||
# Format: nodetype/node_id/tldraw_file.json
|
||||
file_path = f"{node_type}/{node_id}/tldraw_file.json"
|
||||
|
||||
logger.debug(f"Bucket: {bucket}")
|
||||
logger.debug(f"File path: {file_path}")
|
||||
|
||||
try:
|
||||
# Try to download the file from Supabase Storage
|
||||
file_data = storage.download_file(bucket, file_path)
|
||||
|
||||
# Parse JSON data
|
||||
try:
|
||||
snapshot_data = json.loads(file_data.decode('utf-8'))
|
||||
logger.info(f"Successfully loaded tldraw snapshot from Supabase Storage: {file_path}")
|
||||
|
||||
# Ensure the snapshot has the correct structure for TLDraw
|
||||
if isinstance(snapshot_data, dict) and 'document' in snapshot_data and 'session' in snapshot_data:
|
||||
# Check if it has the new format (schemaVersion in document.schema)
|
||||
if 'document' in snapshot_data and isinstance(snapshot_data['document'], dict) and 'schema' in snapshot_data['document']:
|
||||
return snapshot_data
|
||||
# Check if it has the old format (schemaVersion at root level)
|
||||
elif 'schemaVersion' in snapshot_data:
|
||||
return snapshot_data
|
||||
else:
|
||||
# Use default structure if schema is missing
|
||||
logger.warning(f"Snapshot data from {file_path_in_bucket} is missing schemaVersion. Using default structure.")
|
||||
return create_default_tldraw_content()
|
||||
else:
|
||||
# Use default structure if basic structure is missing
|
||||
logger.warning(f"Snapshot data from {file_path_in_bucket} is missing top-level TLDraw keys. Using default structure.")
|
||||
return create_default_tldraw_content()
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse JSON from Supabase Storage file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Invalid JSON in file")
|
||||
|
||||
except Exception as e:
|
||||
# File doesn't exist, create default content
|
||||
logger.info(f"File not found in Supabase Storage, creating default tldraw content: {file_path}")
|
||||
|
||||
# Create default tldraw content
|
||||
default_content = create_default_tldraw_content()
|
||||
|
||||
try:
|
||||
# Upload default content to Supabase Storage
|
||||
json_data = json.dumps(default_content, indent=2).encode('utf-8')
|
||||
storage.upload_file(bucket, file_path, json_data, 'application/json', upsert=True)
|
||||
|
||||
logger.info(f"Default tldraw file created in Supabase Storage: {file_path}")
|
||||
return default_content
|
||||
|
||||
except Exception as upload_error:
|
||||
logger.error(f"Error creating default tldraw file in Supabase Storage: {upload_error}")
|
||||
raise HTTPException(status_code=500, detail="Error creating default tldraw file")
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error loading tldraw file from Supabase Storage: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Error loading file: {str(e)}")
|
||||
|
||||
@router.post("/set_tldraw_node_file")
|
||||
async def set_tldraw_node_file_in_supabase(
|
||||
path: str = Query(..., description="Supabase Storage path (e.g., 'cc.public.snapshots/User/user_id')"),
|
||||
db_name: str = Query(..., description="Database name for context"),
|
||||
data: Dict[str, Any] = None
|
||||
):
|
||||
"""
|
||||
Save TLDraw snapshot to Supabase Storage.
|
||||
|
||||
Args:
|
||||
path: Supabase Storage path in format 'bucket/nodetype/node_id'
|
||||
db_name: Database name for context (used for logging)
|
||||
data: TLDraw snapshot data to save
|
||||
|
||||
Returns:
|
||||
Success status
|
||||
"""
|
||||
logger.debug(f"Saving tldraw file to Supabase Storage for path: {path}")
|
||||
logger.debug(f"Database name: {db_name}")
|
||||
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path not provided")
|
||||
|
||||
if not data:
|
||||
raise HTTPException(status_code=400, detail="Data not provided")
|
||||
|
||||
try:
|
||||
# Initialize Supabase Storage
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Parse the path to extract bucket and file path
|
||||
path_parts = path.split('/')
|
||||
if len(path_parts) < 3:
|
||||
raise HTTPException(status_code=400, detail="Invalid path format. Expected: bucket/nodetype/node_id")
|
||||
|
||||
bucket = path_parts[0] # e.g., "cc.public.snapshots"
|
||||
node_type = path_parts[1] # e.g., "User", "Teacher"
|
||||
node_id = path_parts[2] # e.g., "cbc309e5-4029-4c34-aab7-0aa33c563cd0"
|
||||
|
||||
# Construct the file path in Supabase Storage
|
||||
file_path = f"{node_type}/{node_id}/tldraw_file.json"
|
||||
|
||||
logger.debug(f"Bucket: {bucket}")
|
||||
logger.debug(f"File path: {file_path}")
|
||||
|
||||
# Convert data to JSON
|
||||
try:
|
||||
json_data = json.dumps(data, indent=2).encode('utf-8')
|
||||
except (TypeError, ValueError) as e:
|
||||
logger.error(f"Failed to serialize data to JSON: {e}")
|
||||
raise HTTPException(status_code=400, detail="Invalid data format")
|
||||
|
||||
# Upload to Supabase Storage
|
||||
try:
|
||||
storage.upload_file(bucket, file_path, json_data, 'application/json', upsert=True)
|
||||
logger.info(f"Successfully saved tldraw snapshot to Supabase Storage: {file_path}")
|
||||
return {"status": "success", "message": "File saved successfully"}
|
||||
|
||||
except Exception as upload_error:
|
||||
logger.error(f"Error uploading file to Supabase Storage: {upload_error}")
|
||||
raise HTTPException(status_code=500, detail="Error saving file")
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error saving tldraw file to Supabase Storage: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Error saving file: {str(e)}")
|
||||
@ -29,34 +29,34 @@ async def get_worker_structure(db_name: str) -> Dict[str, Any]:
|
||||
// Collect all nodes
|
||||
RETURN {
|
||||
timetables: collect(DISTINCT {
|
||||
id: tt.unique_id,
|
||||
path: tt.path,
|
||||
id: tt.uuid_string,
|
||||
path: tt.node_storage_path,
|
||||
title: tt.title,
|
||||
type: tt.__primarylabel__,
|
||||
startTime: toString(tt.start_date),
|
||||
endTime: toString(tt.end_date)
|
||||
}),
|
||||
classes: collect(DISTINCT {
|
||||
id: c.unique_id,
|
||||
path: c.path,
|
||||
id: c.uuid_string,
|
||||
path: c.node_storage_path,
|
||||
title: c.title,
|
||||
type: c.__primarylabel__
|
||||
}),
|
||||
lessons: collect(DISTINCT {
|
||||
id: l.unique_id,
|
||||
path: l.path,
|
||||
id: l.uuid_string,
|
||||
path: l.node_storage_path,
|
||||
title: l.title,
|
||||
type: l.__primarylabel__
|
||||
}),
|
||||
journals: collect(DISTINCT {
|
||||
id: j.unique_id,
|
||||
path: j.path,
|
||||
id: j.uuid_string,
|
||||
path: j.node_storage_path,
|
||||
title: j.title,
|
||||
type: j.__primarylabel__
|
||||
}),
|
||||
planners: collect(DISTINCT {
|
||||
id: p.unique_id,
|
||||
path: p.path,
|
||||
id: p.uuid_string,
|
||||
path: p.node_storage_path,
|
||||
title: p.title,
|
||||
type: p.__primarylabel__
|
||||
})
|
||||
@ -106,8 +106,8 @@ async def get_timetables(db_name: str, start_date: str, end_date: str) -> Dict[s
|
||||
MATCH (tt:UserTeacherTimetable)
|
||||
WHERE date(tt.start_date) >= date($start_date) AND date(tt.end_date) <= date($end_date)
|
||||
RETURN {
|
||||
id: tt.unique_id,
|
||||
path: tt.path,
|
||||
id: tt.uuid_string,
|
||||
path: tt.node_storage_path,
|
||||
title: tt.title,
|
||||
type: tt.__primarylabel__,
|
||||
startTime: toString(tt.start_date),
|
||||
@ -138,8 +138,8 @@ async def get_journals(db_name: str) -> Dict[str, Any]:
|
||||
query = """
|
||||
MATCH (j:Journal)
|
||||
RETURN {
|
||||
id: j.unique_id,
|
||||
path: j.path,
|
||||
id: j.uuid_string,
|
||||
path: j.node_storage_path,
|
||||
title: j.title,
|
||||
type: j.__primarylabel__
|
||||
} as journal
|
||||
@ -168,8 +168,8 @@ async def get_planners(db_name: str) -> Dict[str, Any]:
|
||||
query = """
|
||||
MATCH (p:Planner)
|
||||
RETURN {
|
||||
id: p.unique_id,
|
||||
path: p.path,
|
||||
id: p.uuid_string,
|
||||
path: p.node_storage_path,
|
||||
title: p.title,
|
||||
type: p.__primarylabel__
|
||||
} as planner
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from weakref import ref
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
load_dotenv(find_dotenv())
|
||||
import os
|
||||
@ -18,36 +19,21 @@ from langchain_community.graphs import Neo4jGraph
|
||||
from langchain_community.chat_models import ChatOpenAI
|
||||
from langchain.prompts.prompt import PromptTemplate
|
||||
from routers.llm.private.ollama.ollama_wrapper import OllamaWrapper
|
||||
from modules.database.tools.neontology.utils import get_node_types, get_rels_by_type
|
||||
from modules.database.tools.neontology.basenode import BaseNode
|
||||
from modules.database.tools.neontology.baserelationship import BaseRelationship
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Define the schema for nodes and relationships
|
||||
node_types = {
|
||||
"KeyStage": ["merged", "key_stage_name", "unique_id", "created"],
|
||||
"KeyStageSyllabus": ["ks_syllabus_name", "unique_id", "created", "merged", "ks_syllabus_key_stage", "ks_syllabus_subject"],
|
||||
"YearGroup": ["created", "merged", "unique_id", "year_group_name"],
|
||||
"YearGroupSyllabus": ["created", "merged", "yr_syllabus_name", "yr_syllabus_year_group", "yr_syllabus_id", "yr_syllabus_subject"],
|
||||
"Topic": ["topic_type", "topic_assessment_type", "created", "merged", "unique_id", "topic_id", "total_number_of_lessons_for_topic", "topic_title"],
|
||||
"Lesson": ["topic_lesson_id", "topic_lesson_type", "created", "merged", "topic_lesson_title", "topic_lesson_length", "topic_lesson_suggested_activities", "topic_lesson_weblinks", "topic_lesson_skills_learned"],
|
||||
"LearningStatement": ["created", "merged", "lesson_learning_statement", "lesson_learning_statement_id", "lesson_learning_statement_type"]
|
||||
}
|
||||
|
||||
relationship_types = {
|
||||
"KEY_STAGE_INCLUDES_KEY_STAGE_SYLLABUS": ["created", "merged"],
|
||||
"KEY_STAGE_SYLLABUS_INCLUDES_YEAR_GROUP_SYLLABUS": ["created", "merged"],
|
||||
"YEAR_GROUP_FOLLOWS_YEAR_GROUP": ["created", "merged"],
|
||||
"KEY_STAGE_FOLLOWS_KEY_STAGE": ["created", "merged"],
|
||||
"YEAR_SYLLABUS_INCLUDES_TOPIC": ["created", "merged"],
|
||||
"TOPIC_INCLUDES_LESSON": ["created", "merged"],
|
||||
"LESSON_INCLUDES_LEARNING_STATEMENT": ["created", "merged"],
|
||||
"LESSON_FOLLOWS_LESSON": ["created", "merged"]
|
||||
}
|
||||
node_types = get_node_types(BaseNode)
|
||||
relationship_types = get_rels_by_type(BaseRelationship)
|
||||
|
||||
@router.get("/prompt")
|
||||
async def query_graph(
|
||||
database: str, prompt: str, top_k: int = 30, model: str = "gpt-4o", temperature: float = 0,
|
||||
database: str, prompt: str, top_k: int = 30, model: str = "qwen2.5-coder:3b", temperature: float = 0,
|
||||
verbose: bool = False, return_intermediate_steps: bool = False, exclude_types: list = None, include_types: list = None,
|
||||
return_direct: bool = False, validate_cypher: bool = False, model_type: str = "openai"
|
||||
return_direct: bool = False, validate_cypher: bool = False, model_type: str = "ollama"
|
||||
):
|
||||
logging.info(f"Received request with prompt: {prompt}")
|
||||
if exclude_types is None:
|
||||
@ -70,7 +56,9 @@ async def query_graph(
|
||||
url=os.environ['APP_BOLT_URL'],
|
||||
username=os.environ['USER_NEO4J'],
|
||||
password=os.environ['PASSWORD_NEO4J'],
|
||||
database=database
|
||||
database=database,
|
||||
enhanced_schema=True,
|
||||
sanitize=True,
|
||||
)
|
||||
|
||||
logging.info("Refreshing schema...")
|
||||
@ -79,14 +67,18 @@ async def query_graph(
|
||||
schema = graph.schema
|
||||
logging.info(f"Schema: {schema}")
|
||||
|
||||
CYPHER_GENERATION_TEMPLATE = """Task: Generate a Cypher statement to query a graph database for timetable information.
|
||||
CYPHER_GENERATION_TEMPLATE = """Task: Generate a Cypher statement to query a graph database.
|
||||
Role:
|
||||
You are an assistant in a school for teachers, specializing in querying graph databases to find answers to questions.
|
||||
The teacher will ask you questions about their timetable.
|
||||
You are an assistant specializing in querying graph databases to find answers to questions about establishments, schools, and related data.
|
||||
The user will ask you questions about the graph database
|
||||
|
||||
Instructions:
|
||||
1. Use only the provided relationship types and properties in the schema.
|
||||
2. Do not use any other relationship types or properties that are not provided.
|
||||
3. When querying for geographic entities like counties, towns, or countries, use the 'name' property, not 'code'.
|
||||
4. To find relationship types, use: MATCH (n:NodeType)-[r]->(m) RETURN DISTINCT type(r)
|
||||
5. Relationship labels are in uppercase, e.g. LOCATED_IN_COUNTRY
|
||||
6. For broad queries use OPTIONAL MATCH to allow for null results, e.g. OPTIONAL MATCH (n:NodeType) RETURN n.name
|
||||
|
||||
Schema:
|
||||
{schema}
|
||||
@ -95,6 +87,7 @@ async def query_graph(
|
||||
1. Do not include any explanations or apologies in your responses.
|
||||
2. Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
|
||||
3. Do not include any text except the generated Cypher statement.
|
||||
4. Do not include line break characters n other formatting keys.
|
||||
|
||||
The question is:
|
||||
{question}"""
|
||||
@ -105,11 +98,11 @@ async def query_graph(
|
||||
)
|
||||
|
||||
if model_type == "ollama":
|
||||
ollama_host = os.getenv("OLLAMA_URL")
|
||||
ollama_port = os.getenv("OLLAMA_PORT")
|
||||
ollama_host = os.getenv("HOST_OLLAMA")
|
||||
ollama_port = os.getenv("PORT_OLLAMA")
|
||||
if not ollama_host or not ollama_port:
|
||||
raise HTTPException(status_code=500, detail="Ollama host or port not set")
|
||||
client = OllamaWrapper(host=f'http://{ollama_host}:{ollama_port}')
|
||||
client = OllamaWrapper(host=f'{ollama_host}:{ollama_port}', model=model)
|
||||
cypher_llm = client
|
||||
qa_llm = client
|
||||
else:
|
||||
@ -127,7 +120,8 @@ async def query_graph(
|
||||
exclude_types=exclude_types,
|
||||
include_types=include_types,
|
||||
return_direct=return_direct,
|
||||
validate_cypher=validate_cypher
|
||||
validate_cypher=validate_cypher,
|
||||
allow_dangerous_requests=True
|
||||
)
|
||||
|
||||
formatted_prompt = CYPHER_GENERATION_PROMPT.format(schema=schema, question=prompt)
|
||||
@ -150,4 +144,6 @@ async def query_graph(
|
||||
logging.info(f"Cypher chain: \n{chain}\n")
|
||||
logging.info("==================================================")
|
||||
|
||||
return chain(prompt)
|
||||
return chain(prompt)
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@ -47,7 +47,7 @@
|
||||
"import json\n",
|
||||
"\n",
|
||||
"# Define the URL of your FastAPI server\n",
|
||||
"BASE_URL = \"http://localhost:8000\" # Adjust this if your server is running on a different port or host\n",
|
||||
"BASE_URL = \"http://localhost:8001\" # Adjust this if your server is running on a different port or host\n",
|
||||
"\n",
|
||||
"# Define the endpoint\n",
|
||||
"ENDPOINT = f\"{BASE_URL}/api/langchain/interactive_langgraph_query/query\"\n",
|
||||
@ -81,7 +81,7 @@
|
||||
"\n",
|
||||
"def test_followup_queries(model=\"openai\"):\n",
|
||||
" queries = [\n",
|
||||
" \"What is the latest local news from a particular town?\"\n",
|
||||
" \"Tell me everything you can about schools in Medway\"\n",
|
||||
" ]\n",
|
||||
" \n",
|
||||
" print(f\"Testing queries requiring follow-up using {model} model:\")\n",
|
||||
@ -117,7 +117,7 @@
|
||||
"#test_simple_queries(\"ollama\")\n",
|
||||
"\n",
|
||||
"print(\"\\nRunning simple query tests with OpenAI:\\n\")\n",
|
||||
"test_simple_queries(\"openai\")\n",
|
||||
"test_simple_queries(\"ollama\")\n",
|
||||
"\n",
|
||||
"#print(\"\\nRunning follow-up query tests with Ollama:\\n\")\n",
|
||||
"#test_followup_queries(\"ollama\")\n",
|
||||
|
||||
@ -4,14 +4,16 @@ from langchain_core.runnables.base import Runnable
|
||||
from langchain.prompts.base import StringPromptValue
|
||||
|
||||
class OllamaWrapper(Runnable):
|
||||
def __init__(self, host: str):
|
||||
def __init__(self, host: str, model: str = "llama3.2:latest"):
|
||||
self.client = Client(host=host)
|
||||
self.model = model
|
||||
|
||||
def invoke(self, prompt: Any, config: Dict[str, Any] = None, **kwargs: Any) -> str:
|
||||
if isinstance(prompt, StringPromptValue):
|
||||
prompt = prompt.to_string()
|
||||
|
||||
model_name = kwargs.get("model", "llama3")
|
||||
# Use the model from constructor, but allow override via kwargs
|
||||
model_name = kwargs.get("model", self.model)
|
||||
options = {
|
||||
"temperature": kwargs.get("temperature"),
|
||||
"top_p": kwargs.get("top_p"),
|
||||
|
||||
168
routers/maintenance/redis_admin.py
Normal file
168
routers/maintenance/redis_admin.py
Normal file
@ -0,0 +1,168 @@
|
||||
import os
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
import redis
|
||||
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _redis_client() -> redis.Redis:
|
||||
host = os.getenv('REDIS_HOST', '127.0.0.1')
|
||||
port = int(os.getenv('REDIS_PORT', '6379'))
|
||||
return redis.Redis(host=host, port=port, decode_responses=True)
|
||||
|
||||
|
||||
@router.get("/ping")
|
||||
def ping() -> Dict[str, Any]:
|
||||
try:
|
||||
r = _redis_client()
|
||||
pong = r.ping()
|
||||
return {"ok": True, "pong": pong}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis ping failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis ping failed: {e}")
|
||||
|
||||
|
||||
@router.get("/info")
|
||||
def info(section: Optional[str] = Query(None, description="Optional INFO section, e.g. memory, server, clients, keyspace")) -> Dict[str, Any]:
|
||||
try:
|
||||
r = _redis_client()
|
||||
data = r.info(section) if section else r.info()
|
||||
return {"ok": True, "info": data}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis info failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis info failed: {e}")
|
||||
|
||||
|
||||
@router.get("/scan")
|
||||
def scan(cursor: int = 0, pattern: Optional[str] = None, count: int = Query(100, ge=1, le=10000)) -> Dict[str, Any]:
|
||||
try:
|
||||
r = _redis_client()
|
||||
next_cursor, keys = r.scan(cursor=cursor, match=pattern, count=count)
|
||||
return {"ok": True, "cursor": next_cursor, "keys": keys, "count": len(keys)}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis scan failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis scan failed: {e}")
|
||||
|
||||
|
||||
@router.get("/keys")
|
||||
def list_keys(pattern: str = Query("*", description="Glob-style pattern to match keys"), limit: int = Query(200, ge=1, le=5000)) -> Dict[str, Any]:
|
||||
try:
|
||||
r = _redis_client()
|
||||
keys: List[str] = []
|
||||
cursor = 0
|
||||
while True and len(keys) < limit:
|
||||
cursor, batch = r.scan(cursor=cursor, match=pattern, count=min(1000, limit - len(keys)))
|
||||
keys.extend(batch)
|
||||
if cursor == 0:
|
||||
break
|
||||
return {"ok": True, "keys": keys[:limit], "count": len(keys[:limit])}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis keys failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis keys failed: {e}")
|
||||
|
||||
|
||||
@router.get("/key")
|
||||
def get_key(key: str) -> Dict[str, Any]:
|
||||
try:
|
||||
r = _redis_client()
|
||||
t = r.type(key)
|
||||
value: Any = None
|
||||
if t == 'string':
|
||||
value = r.get(key)
|
||||
elif t == 'hash':
|
||||
value = r.hgetall(key)
|
||||
elif t == 'list':
|
||||
value = r.lrange(key, 0, 99)
|
||||
elif t == 'set':
|
||||
value = list(r.smembers(key))
|
||||
elif t == 'zset':
|
||||
value = r.zrange(key, 0, 99, withscores=True)
|
||||
ttl = r.ttl(key)
|
||||
return {"ok": True, "type": t, "ttl": ttl, "value": value}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis get key failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis get key failed: {e}")
|
||||
|
||||
|
||||
class DeleteKeysBody(BaseModel):
|
||||
keys: Optional[List[str]] = None
|
||||
pattern: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/delete")
|
||||
def delete_keys(body: DeleteKeysBody) -> Dict[str, Any]:
|
||||
if not body.keys and not body.pattern:
|
||||
raise HTTPException(status_code=400, detail="Provide 'keys' or 'pattern'")
|
||||
try:
|
||||
r = _redis_client()
|
||||
to_delete: List[str] = body.keys or []
|
||||
if body.pattern:
|
||||
# Resolve pattern to keys via SCAN to avoid blocking
|
||||
keys: List[str] = []
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, batch = r.scan(cursor=cursor, match=body.pattern, count=1000)
|
||||
keys.extend(batch)
|
||||
if cursor == 0:
|
||||
break
|
||||
to_delete.extend(keys)
|
||||
deleted = 0
|
||||
for k in set(to_delete):
|
||||
try:
|
||||
deleted += r.delete(k)
|
||||
except Exception:
|
||||
pass
|
||||
return {"ok": True, "deleted": deleted}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis delete failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis delete failed: {e}")
|
||||
|
||||
|
||||
@router.post("/flushdb")
|
||||
def flush_db(confirm: bool = Query(False, description="Must be true to flush the current DB")) -> Dict[str, Any]:
|
||||
if not confirm:
|
||||
raise HTTPException(status_code=400, detail="Set confirm=true to flush the current Redis DB")
|
||||
try:
|
||||
r = _redis_client()
|
||||
r.flushdb()
|
||||
return {"ok": True, "message": "Flushed current Redis DB"}
|
||||
except Exception as e:
|
||||
logger.error(f"Redis FLUSHDB failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Redis FLUSHDB failed: {e}")
|
||||
|
||||
|
||||
@router.get("/queues")
|
||||
def queue_stats() -> Dict[str, Any]:
|
||||
"""Report queue sizes and basic counters used by DocumentProcessingQueue."""
|
||||
try:
|
||||
r = _redis_client()
|
||||
keys = {
|
||||
"high": "queue:high",
|
||||
"normal": "queue:normal",
|
||||
"low": "queue:low",
|
||||
"processing": "processing",
|
||||
"dead_letter": "dead_letter",
|
||||
"metrics": "metrics",
|
||||
}
|
||||
data = {
|
||||
"queues": {
|
||||
"high": r.llen(keys["high"]),
|
||||
"normal": r.llen(keys["normal"]),
|
||||
"low": r.llen(keys["low"]),
|
||||
},
|
||||
"processing": r.hgetall(keys["processing"]),
|
||||
"dead_letter": r.llen(keys["dead_letter"]),
|
||||
"metrics": r.hgetall(keys["metrics"]),
|
||||
}
|
||||
return {"ok": True, **data}
|
||||
except Exception as e:
|
||||
logger.error(f"Queue stats failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Queue stats failed: {e}")
|
||||
|
||||
53
routers/provisioning.py
Normal file
53
routers/provisioning.py
Normal file
@ -0,0 +1,53 @@
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
from modules.database.services.provisioning_service import ProvisioningService
|
||||
|
||||
router = APIRouter(prefix="/provisioning", tags=["Provisioning"])
|
||||
auth = SupabaseBearer()
|
||||
|
||||
|
||||
class ProvisionUserRequest(BaseModel):
|
||||
user_id: str
|
||||
|
||||
|
||||
class ProvisionUserResponse(BaseModel):
|
||||
user_db_name: str
|
||||
worker_db_name: Optional[str]
|
||||
worker_type: Optional[str]
|
||||
|
||||
|
||||
class ProvisionSchoolRequest(BaseModel):
|
||||
institute_id: str
|
||||
|
||||
|
||||
class ProvisionSchoolResponse(BaseModel):
|
||||
db_name: str
|
||||
curriculum_db_name: str
|
||||
|
||||
|
||||
@router.post("/users", response_model=ProvisionUserResponse)
|
||||
def provision_user(payload: ProvisionUserRequest, token=Depends(auth)):
|
||||
"""Ensure a user's Neo4j resources exist."""
|
||||
# Basic authorization: require matching subject or service role token
|
||||
if token.get('role') not in ('service_role', 'admin') and token.get('sub') != payload.user_id:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
|
||||
service = ProvisioningService()
|
||||
result = service.ensure_user(payload.user_id)
|
||||
return ProvisionUserResponse(**result)
|
||||
|
||||
|
||||
@router.post("/schools", response_model=ProvisionSchoolResponse)
|
||||
def provision_school(payload: ProvisionSchoolRequest, token=Depends(auth)):
|
||||
"""Ensure a school's Neo4j resources exist."""
|
||||
if token.get('role') not in ('service_role', 'admin'):
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
|
||||
service = ProvisioningService()
|
||||
result = service.ensure_school(payload.institute_id)
|
||||
return ProvisionSchoolResponse(db_name=result['db_name'], curriculum_db_name=result['curriculum_db_name'])
|
||||
313
routers/queue_management.py
Normal file
313
routers/queue_management.py
Normal file
@ -0,0 +1,313 @@
|
||||
"""
|
||||
Queue Management API
|
||||
|
||||
Provides endpoints for monitoring and managing the document processing queue.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from typing import Dict, Any, List, Optional
|
||||
from modules.queue_system import get_queue, TaskPriority, ServiceType
|
||||
from modules.task_processors import get_processor
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
import os
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
|
||||
@router.get("/queue/stats")
|
||||
def get_queue_stats(payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get comprehensive queue statistics."""
|
||||
queue = get_queue()
|
||||
stats = queue.get_queue_stats()
|
||||
return stats
|
||||
|
||||
@router.get("/queue/health")
|
||||
def queue_health():
|
||||
"""Check queue health status."""
|
||||
try:
|
||||
queue = get_queue()
|
||||
stats = queue.get_queue_stats()
|
||||
|
||||
# Basic health checks
|
||||
total_processing = stats['total_processing']
|
||||
total_queued = sum(stats['queues'].values())
|
||||
dead_letter_count = stats['dead_letter_count']
|
||||
|
||||
status = "healthy"
|
||||
issues = []
|
||||
|
||||
if dead_letter_count > 10:
|
||||
issues.append(f"High dead letter count: {dead_letter_count}")
|
||||
status = "degraded"
|
||||
|
||||
if total_processing == 0 and total_queued > 0:
|
||||
issues.append("Tasks queued but no workers processing")
|
||||
status = "degraded"
|
||||
|
||||
# Check Redis connectivity
|
||||
queue.redis_client.ping()
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"total_processing": total_processing,
|
||||
"total_queued": total_queued,
|
||||
"dead_letter_count": dead_letter_count,
|
||||
"issues": issues,
|
||||
"timestamp": queue.redis_client.time()[0]
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "unhealthy",
|
||||
"error": str(e),
|
||||
"timestamp": None
|
||||
}
|
||||
|
||||
@router.post("/queue/workers/start")
|
||||
def start_workers(
|
||||
worker_count: int = 1,
|
||||
services: Optional[List[str]] = None,
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""Start queue workers."""
|
||||
if not payload.get('role') == 'service_role': # Admin only
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
processor = get_processor()
|
||||
|
||||
# Convert service names to enums
|
||||
service_enums = []
|
||||
if services:
|
||||
for service_name in services:
|
||||
try:
|
||||
service_enums.append(ServiceType(service_name))
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid service: {service_name}")
|
||||
else:
|
||||
service_enums = list(ServiceType)
|
||||
|
||||
worker_ids = []
|
||||
for i in range(worker_count):
|
||||
worker_id = processor.start_worker(services=service_enums)
|
||||
worker_ids.append(worker_id)
|
||||
|
||||
return {
|
||||
"message": f"Started {worker_count} workers",
|
||||
"worker_ids": worker_ids,
|
||||
"services": [s.value for s in service_enums]
|
||||
}
|
||||
|
||||
@router.post("/queue/workers/stop")
|
||||
def stop_workers(timeout: int = 30, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Stop all queue workers."""
|
||||
if not payload.get('role') == 'service_role': # Admin only
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
processor = get_processor()
|
||||
processor.shutdown(timeout=timeout)
|
||||
|
||||
return {"message": "Workers shutdown initiated"}
|
||||
|
||||
@router.get("/queue/tasks/{task_id}")
|
||||
def get_task_status(task_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get status of a specific task."""
|
||||
queue = get_queue()
|
||||
|
||||
# Get task data from Redis
|
||||
task_key = queue._get_task_key(task_id)
|
||||
task_data = queue.redis_client.hgetall(task_key)
|
||||
|
||||
if not task_data:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
return task_data
|
||||
|
||||
@router.get("/queue/tasks/by-file/{file_id}")
|
||||
def list_tasks_by_file(file_id: str, limit: int = 200, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""List recent queue tasks for a given file_id (best-effort scan)."""
|
||||
queue = get_queue()
|
||||
results: List[Dict[str, Any]] = []
|
||||
count = 0
|
||||
import json
|
||||
try:
|
||||
for key in queue.redis_client.scan_iter(match="task:*"):
|
||||
task_data = queue.redis_client.hgetall(key)
|
||||
if not task_data:
|
||||
continue
|
||||
if task_data.get('file_id') != file_id:
|
||||
continue
|
||||
# Build a brief
|
||||
try:
|
||||
payload_raw = task_data.get('payload') or '{}'
|
||||
payload_obj = json.loads(payload_raw)
|
||||
except Exception:
|
||||
payload_obj = {}
|
||||
results.append({
|
||||
'id': task_data.get('id') or key.split(':', 1)[-1],
|
||||
'service': task_data.get('service'),
|
||||
'task_type': task_data.get('task_type'),
|
||||
'status': task_data.get('status', 'pending'),
|
||||
'priority': task_data.get('priority'),
|
||||
'created_at': float(task_data.get('created_at') or 0),
|
||||
'scheduled_at': float(task_data.get('scheduled_at') or 0),
|
||||
'depends_on': payload_obj.get('depends_on') or []
|
||||
})
|
||||
count += 1
|
||||
if count >= max(1, int(limit)):
|
||||
break
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed scanning tasks: {e}")
|
||||
# Sort by created_at desc
|
||||
results.sort(key=lambda x: x.get('created_at', 0), reverse=True)
|
||||
return {
|
||||
'file_id': file_id,
|
||||
'count': len(results),
|
||||
'tasks': results
|
||||
}
|
||||
|
||||
@router.get("/queue/tasks/{task_id}/dependencies")
|
||||
def get_task_dependencies(task_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Inspect a task's dependency state (direct depends_on only)."""
|
||||
queue = get_queue()
|
||||
task_key = queue._get_task_key(task_id)
|
||||
task_data = queue.redis_client.hgetall(task_key)
|
||||
if not task_data:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
# Parse payload JSON stored in Redis
|
||||
import json
|
||||
try:
|
||||
payload_raw = task_data.get('payload') or '{}'
|
||||
payload_obj = json.loads(payload_raw)
|
||||
except Exception:
|
||||
payload_obj = {}
|
||||
|
||||
depends_on = payload_obj.get('depends_on') or []
|
||||
if not isinstance(depends_on, list):
|
||||
depends_on = []
|
||||
|
||||
details: List[Dict[str, Any]] = []
|
||||
missing: List[str] = []
|
||||
all_completed = True
|
||||
for dep_id in depends_on:
|
||||
if not dep_id:
|
||||
continue
|
||||
dep_key = queue._get_task_key(dep_id)
|
||||
dep_data = queue.redis_client.hgetall(dep_key)
|
||||
if not dep_data:
|
||||
missing.append(dep_id)
|
||||
all_completed = False
|
||||
details.append({
|
||||
'task_id': dep_id,
|
||||
'status': 'missing'
|
||||
})
|
||||
continue
|
||||
status = dep_data.get('status', 'pending')
|
||||
if status != 'completed':
|
||||
all_completed = False
|
||||
details.append({
|
||||
'task_id': dep_id,
|
||||
'status': status,
|
||||
'service': dep_data.get('service'),
|
||||
'task_type': dep_data.get('task_type'),
|
||||
'created_at': dep_data.get('created_at'),
|
||||
'scheduled_at': dep_data.get('scheduled_at')
|
||||
})
|
||||
|
||||
return {
|
||||
'task_id': task_id,
|
||||
'depends_on': depends_on,
|
||||
'all_completed': all_completed,
|
||||
'missing': missing,
|
||||
'details': details
|
||||
}
|
||||
|
||||
@router.delete("/queue/dead-letter/{task_id}")
|
||||
def remove_dead_task(task_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Remove a task from the dead letter queue."""
|
||||
if not payload.get('role') == 'service_role': # Admin only
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
queue = get_queue()
|
||||
|
||||
# Remove from dead letter queue
|
||||
removed = queue.redis_client.lrem(queue.dead_letter_key, 1, task_id)
|
||||
|
||||
if removed == 0:
|
||||
raise HTTPException(status_code=404, detail="Task not found in dead letter queue")
|
||||
|
||||
# Clean up task data
|
||||
queue.redis_client.delete(queue._get_task_key(task_id))
|
||||
|
||||
return {"message": f"Removed task {task_id} from dead letter queue"}
|
||||
|
||||
@router.post("/queue/dead-letter/{task_id}/retry")
|
||||
def retry_dead_task(task_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Retry a task from the dead letter queue."""
|
||||
if not payload.get('role') == 'service_role': # Admin only
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
queue = get_queue()
|
||||
|
||||
# Get task data
|
||||
task_key = queue._get_task_key(task_id)
|
||||
task_data = queue.redis_client.hgetall(task_key)
|
||||
|
||||
if not task_data:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
# Remove from dead letter queue
|
||||
removed = queue.redis_client.lrem(queue.dead_letter_key, 1, task_id)
|
||||
if removed == 0:
|
||||
raise HTTPException(status_code=404, detail="Task not found in dead letter queue")
|
||||
|
||||
# Reset task for retry
|
||||
queue.redis_client.hset(
|
||||
task_key,
|
||||
mapping={
|
||||
'attempts': 0,
|
||||
'status': 'pending',
|
||||
'scheduled_at': queue.redis_client.time()[0]
|
||||
}
|
||||
)
|
||||
|
||||
# Re-queue task
|
||||
priority = TaskPriority(task_data['priority'])
|
||||
queue_key = queue.queue_keys[priority]
|
||||
queue.redis_client.lpush(queue_key, task_id)
|
||||
|
||||
return {"message": f"Task {task_id} requeued for retry"}
|
||||
|
||||
@router.get("/queue/metrics")
|
||||
def get_queue_metrics(hours: int = 1, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get queue processing metrics over time."""
|
||||
queue = get_queue()
|
||||
|
||||
# This is a simplified version - in production you'd want more sophisticated metrics
|
||||
current_time = queue.redis_client.time()[0]
|
||||
start_time = current_time - (hours * 3600)
|
||||
|
||||
# Scan for metric keys in the time range
|
||||
metrics = {}
|
||||
pattern = f"{queue.metrics_key}:*"
|
||||
|
||||
for key in queue.redis_client.scan_iter(match=pattern):
|
||||
# Parse key format: metrics:action:service:priority:timestamp_minute
|
||||
parts = key.split(':')
|
||||
if len(parts) >= 5:
|
||||
action = parts[1]
|
||||
service = parts[2]
|
||||
priority = parts[3]
|
||||
timestamp_minute = int(parts[4])
|
||||
|
||||
if timestamp_minute >= (start_time // 60):
|
||||
count = int(queue.redis_client.get(key) or 0)
|
||||
|
||||
metric_key = f"{action}_{service}_{priority}"
|
||||
if metric_key not in metrics:
|
||||
metrics[metric_key] = 0
|
||||
metrics[metric_key] += count
|
||||
|
||||
return {
|
||||
"time_range_hours": hours,
|
||||
"metrics": metrics
|
||||
}
|
||||
174
routers/queue_monitor.py
Normal file
174
routers/queue_monitor.py
Normal file
@ -0,0 +1,174 @@
|
||||
"""
|
||||
Queue Monitoring Router
|
||||
|
||||
Provides endpoints to monitor queue status, service limits, and processing counts
|
||||
to help debug issues like Docling server overload.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from typing import Dict, Any, List
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
from modules.queue_system import get_queue
|
||||
from modules.redis_manager import get_redis_manager
|
||||
from modules.logger_tool import initialise_logger
|
||||
import os
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
@router.get("/queue/status")
|
||||
def get_queue_status(payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get comprehensive queue status including service limits and current processing counts."""
|
||||
try:
|
||||
queue = get_queue()
|
||||
stats = queue.get_queue_stats()
|
||||
|
||||
# Add environment configuration
|
||||
config = {
|
||||
'QUEUE_DOCLING_LIMIT': int(os.getenv('QUEUE_DOCLING_LIMIT', '2')),
|
||||
'QUEUE_TIKA_LIMIT': int(os.getenv('QUEUE_TIKA_LIMIT', '3')),
|
||||
'QUEUE_LLM_LIMIT': int(os.getenv('QUEUE_LLM_LIMIT', '5')),
|
||||
'BUNDLE_ARCHITECTURE_ENABLED': True,
|
||||
'AUTO_DOCLING_OCR': os.getenv('AUTO_DOCLING_OCR', 'true'),
|
||||
'AUTO_DOCLING_NO_OCR': os.getenv('AUTO_DOCLING_NO_OCR', 'true'),
|
||||
'AUTO_DOCLING_VLM': os.getenv('AUTO_DOCLING_VLM', 'false')
|
||||
}
|
||||
|
||||
return {
|
||||
'queue_statistics': stats,
|
||||
'configuration': config,
|
||||
'redis_host': queue.redis_host,
|
||||
'redis_port': queue.redis_port,
|
||||
'service_limits': dict(queue.service_limits),
|
||||
'rate_limits': dict(queue.rate_limits)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get queue status: {e}")
|
||||
return {
|
||||
'error': str(e),
|
||||
'queue_available': False
|
||||
}
|
||||
|
||||
@router.get("/queue/tasks/by-file/{file_id}")
|
||||
def get_file_queue_tasks(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get all queue tasks for a specific file (for debugging)."""
|
||||
try:
|
||||
# This would require extending the queue system to track tasks by file_id
|
||||
# For now, return a placeholder that indicates this feature needs implementation
|
||||
return {
|
||||
'message': 'File-specific task tracking not yet implemented',
|
||||
'file_id': file_id,
|
||||
'suggestion': 'Check Redis directly or extend queue system with file_id indexing'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get tasks for file {file_id}: {e}")
|
||||
return {
|
||||
'error': str(e),
|
||||
'file_id': file_id
|
||||
}
|
||||
|
||||
@router.get("/queue/health")
|
||||
def get_comprehensive_queue_health(payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get comprehensive queue and Redis health information with environment details."""
|
||||
try:
|
||||
# Determine environment
|
||||
environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'
|
||||
|
||||
# Get Redis manager health
|
||||
redis_manager = get_redis_manager(environment)
|
||||
redis_health = redis_manager.health_check()
|
||||
|
||||
# Get queue stats
|
||||
queue_stats = {}
|
||||
queue_available = False
|
||||
try:
|
||||
queue = get_queue()
|
||||
queue_stats = queue.get_queue_stats()
|
||||
queue_available = True
|
||||
except Exception as e:
|
||||
queue_stats = {'error': str(e)}
|
||||
|
||||
# Environment configuration
|
||||
env_config = {
|
||||
'current_environment': environment,
|
||||
'redis_database': redis_health.get('database', 'unknown'),
|
||||
'persistence_enabled': os.getenv(f'REDIS_PERSIST_{environment.upper()}', 'unknown'),
|
||||
'task_ttl': os.getenv(f'REDIS_TASK_TTL_{environment.upper()}', 'unknown'),
|
||||
'service_limits': {
|
||||
'docling': int(os.getenv('QUEUE_DOCLING_LIMIT', '2')),
|
||||
'tika': int(os.getenv('QUEUE_TIKA_LIMIT', '3')),
|
||||
'llm': int(os.getenv('QUEUE_LLM_LIMIT', '5')),
|
||||
'split_map': int(os.getenv('QUEUE_SPLIT_MAP_LIMIT', '10')),
|
||||
'document_analysis': int(os.getenv('QUEUE_DOCUMENT_ANALYSIS_LIMIT', '5')),
|
||||
'page_images': int(os.getenv('QUEUE_PAGE_IMAGES_LIMIT', '3'))
|
||||
},
|
||||
'workers': int(os.getenv('QUEUE_WORKERS', '1'))
|
||||
}
|
||||
|
||||
# Overall health status
|
||||
overall_status = 'healthy'
|
||||
issues = []
|
||||
|
||||
if redis_health['status'] != 'healthy':
|
||||
overall_status = 'unhealthy'
|
||||
issues.append(f"Redis: {redis_health.get('error', 'Unknown issue')}")
|
||||
|
||||
if not queue_available:
|
||||
overall_status = 'degraded'
|
||||
issues.append(f"Queue system: {queue_stats.get('error', 'Not accessible')}")
|
||||
|
||||
# Check for concerning queue states
|
||||
if queue_available and queue_stats.get('dead_letter_count', 0) > 0:
|
||||
overall_status = 'warning'
|
||||
issues.append(f"Dead letter queue has {queue_stats['dead_letter_count']} failed tasks")
|
||||
|
||||
return {
|
||||
'status': overall_status,
|
||||
'issues': issues,
|
||||
'timestamp': redis_health.get('timestamp'),
|
||||
'environment': env_config,
|
||||
'redis': redis_health,
|
||||
'queue': queue_stats,
|
||||
'recommendations': _get_health_recommendations(redis_health, queue_stats, environment)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get comprehensive queue health: {e}")
|
||||
return {
|
||||
'status': 'error',
|
||||
'error': str(e),
|
||||
'timestamp': None
|
||||
}
|
||||
|
||||
def _get_health_recommendations(redis_health: Dict, queue_stats: Dict, environment: str) -> List[str]:
|
||||
"""Generate health recommendations based on current state."""
|
||||
recommendations = []
|
||||
|
||||
# Redis recommendations
|
||||
if redis_health.get('status') != 'healthy':
|
||||
recommendations.append("Check Redis service status and connectivity")
|
||||
|
||||
# Queue recommendations
|
||||
if queue_stats.get('dead_letter_count', 0) > 5:
|
||||
recommendations.append("High number of failed tasks - check task processing logic")
|
||||
|
||||
# Processing recommendations
|
||||
processing_counts = queue_stats.get('processing', {})
|
||||
for service, count in processing_counts.items():
|
||||
if count < 0:
|
||||
recommendations.append(f"Negative processing counter for {service} - restart server to reset")
|
||||
|
||||
# Environment-specific recommendations
|
||||
if environment == 'dev':
|
||||
recommendations.append("Development mode: Database will be cleared on restart")
|
||||
else:
|
||||
recommendations.append("Production mode: Tasks will be recovered on restart")
|
||||
|
||||
if not recommendations:
|
||||
recommendations.append("System appears healthy - no specific recommendations")
|
||||
|
||||
return recommendations
|
||||
543
routers/simple_upload.py
Normal file
543
routers/simple_upload.py
Normal file
@ -0,0 +1,543 @@
|
||||
"""
|
||||
Simple Upload Router
|
||||
===================
|
||||
|
||||
Handles file and directory uploads without automatic processing.
|
||||
Just stores files in Supabase storage and creates database records.
|
||||
|
||||
Features:
|
||||
- Single file upload
|
||||
- Directory/folder upload with manifest
|
||||
- No automatic processing
|
||||
- Immediate response to users
|
||||
- Directory structure preservation
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import tempfile
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, BackgroundTasks
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from modules.auth.supabase_bearer import SupabaseBearer
|
||||
from modules.database.supabase.utils.client import SupabaseServiceRoleClient
|
||||
from modules.database.supabase.utils.storage import StorageAdmin
|
||||
from modules.logger_tool import initialise_logger
|
||||
|
||||
router = APIRouter()
|
||||
auth = SupabaseBearer()
|
||||
|
||||
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
|
||||
|
||||
def _choose_bucket(scope: str, user_id: str, school_id: Optional[str]) -> str:
|
||||
"""Choose appropriate bucket based on scope - matches old system logic."""
|
||||
scope = (scope or 'teacher').lower()
|
||||
if scope == 'school' and school_id:
|
||||
return f"cc.institutes.{school_id}.private"
|
||||
# teacher / student fall back to users bucket for now
|
||||
return 'cc.users'
|
||||
|
||||
@router.post("/files/upload")
|
||||
async def upload_single_file(
|
||||
cabinet_id: str = Form(...),
|
||||
path: str = Form(...),
|
||||
scope: str = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
Simple single file upload - no automatic processing.
|
||||
Just stores the file and creates a database record.
|
||||
"""
|
||||
|
||||
try:
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=401, detail="User ID required")
|
||||
|
||||
# Read file content
|
||||
file_bytes = await file.read()
|
||||
file_size = len(file_bytes)
|
||||
mime_type = file.content_type or 'application/octet-stream'
|
||||
filename = file.filename or path
|
||||
|
||||
logger.info(f"📤 Simple upload: {filename} ({file_size} bytes) for user {user_id}")
|
||||
|
||||
# Initialize services
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Generate file ID and storage path
|
||||
file_id = str(uuid.uuid4())
|
||||
# Use same bucket logic as old system for consistency
|
||||
bucket = _choose_bucket('teacher', user_id, None) # Default to teacher scope
|
||||
storage_path = f"{cabinet_id}/{file_id}/{filename}"
|
||||
|
||||
# Store file in Supabase storage
|
||||
try:
|
||||
storage.upload_file(bucket, storage_path, file_bytes, mime_type, upsert=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Storage upload failed for {file_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Storage upload failed: {str(e)}")
|
||||
|
||||
# Create database record
|
||||
try:
|
||||
insert_res = client.supabase.table('files').insert({
|
||||
'id': file_id,
|
||||
'name': filename,
|
||||
'cabinet_id': cabinet_id,
|
||||
'bucket': bucket,
|
||||
'path': storage_path,
|
||||
'mime_type': mime_type,
|
||||
'uploaded_by': user_id,
|
||||
'size_bytes': file_size,
|
||||
'source': 'classroomcopilot-web',
|
||||
'is_directory': False,
|
||||
'processing_status': 'uploaded',
|
||||
'relative_path': filename # For single files, relative path is just the filename
|
||||
}).execute()
|
||||
|
||||
if not insert_res.data:
|
||||
# Clean up storage on DB failure
|
||||
try:
|
||||
storage.delete_file(bucket, storage_path)
|
||||
except:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail="Failed to create file record")
|
||||
|
||||
file_record = insert_res.data[0]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Database insert failed for {file_id}: {e}")
|
||||
# Clean up storage
|
||||
try:
|
||||
storage.delete_file(bucket, storage_path)
|
||||
except:
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
|
||||
|
||||
logger.info(f"✅ Simple upload completed: {file_id}")
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': 'File uploaded successfully',
|
||||
'file': file_record,
|
||||
'processing_required': False, # No automatic processing
|
||||
'next_steps': 'File is ready for manual processing if needed'
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Upload error: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
|
||||
|
||||
@router.post("/files/upload-directory")
|
||||
async def upload_directory(
|
||||
cabinet_id: str = Form(...),
|
||||
scope: str = Form(...),
|
||||
directory_name: str = Form(...),
|
||||
files: List[UploadFile] = File(...),
|
||||
file_paths: str = Form(...), # JSON string of relative paths
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
Upload a complete directory/folder with all files.
|
||||
Preserves directory structure and creates a manifest.
|
||||
"""
|
||||
|
||||
try:
|
||||
user_id = payload.get('sub') or payload.get('user_id')
|
||||
if not user_id:
|
||||
raise HTTPException(status_code=401, detail="User ID required")
|
||||
|
||||
# Parse file paths
|
||||
try:
|
||||
relative_paths = json.loads(file_paths)
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(status_code=400, detail="Invalid file_paths JSON")
|
||||
|
||||
if len(files) != len(relative_paths):
|
||||
raise HTTPException(status_code=400, detail="Files and paths count mismatch")
|
||||
|
||||
logger.info(f"📁 Directory upload: {directory_name} ({len(files)} files) for user {user_id}")
|
||||
|
||||
# Initialize services
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Generate session ID for this directory upload
|
||||
upload_session_id = str(uuid.uuid4())
|
||||
directory_id = str(uuid.uuid4())
|
||||
# Use same bucket logic as old system for consistency
|
||||
bucket = _choose_bucket('teacher', user_id, None)
|
||||
|
||||
# Calculate total size and build manifest
|
||||
total_size = 0
|
||||
directory_structure = {}
|
||||
uploaded_files = []
|
||||
directory_records = {} # Track created directories: {relative_path: directory_id}
|
||||
|
||||
# First, analyze all paths to determine directory structure
|
||||
all_directories = set()
|
||||
for relative_path in relative_paths:
|
||||
# Get all parent directories for this file
|
||||
path_parts = relative_path.split('/')
|
||||
for i in range(len(path_parts) - 1): # Exclude the filename
|
||||
dir_path = '/'.join(path_parts[:i+1])
|
||||
all_directories.add(dir_path)
|
||||
|
||||
logger.info(f"📁 Creating directory structure with {len(all_directories)} directories: {sorted(all_directories)}")
|
||||
|
||||
try:
|
||||
# Create directory records for all directories (sorted to create parents first)
|
||||
sorted_directories = sorted(all_directories, key=lambda x: (len(x.split('/')), x))
|
||||
|
||||
for dir_path in sorted_directories:
|
||||
dir_id = str(uuid.uuid4())
|
||||
path_parts = dir_path.split('/')
|
||||
dir_name = path_parts[-1] # Last part is the directory name
|
||||
|
||||
# Determine parent directory
|
||||
parent_id = None
|
||||
if len(path_parts) > 1:
|
||||
parent_path = '/'.join(path_parts[:-1])
|
||||
parent_id = directory_records.get(parent_path)
|
||||
|
||||
directory_record = {
|
||||
'id': dir_id,
|
||||
'name': dir_name,
|
||||
'cabinet_id': cabinet_id,
|
||||
'bucket': bucket,
|
||||
'path': f"{cabinet_id}/{dir_id}/",
|
||||
'mime_type': 'inode/directory',
|
||||
'uploaded_by': user_id,
|
||||
'size_bytes': 0,
|
||||
'source': 'classroomcopilot-web',
|
||||
'is_directory': True,
|
||||
'parent_directory_id': parent_id,
|
||||
'upload_session_id': upload_session_id,
|
||||
'processing_status': 'uploaded',
|
||||
'relative_path': dir_path
|
||||
}
|
||||
|
||||
directory_records[dir_path] = dir_id
|
||||
|
||||
# Insert directory record
|
||||
result = client.supabase.table('files').insert(directory_record).execute()
|
||||
logger.info(f"📁 Created directory: {dir_path} (ID: {dir_id}, Parent: {parent_id})")
|
||||
|
||||
# Process each file
|
||||
for i, (file, relative_path) in enumerate(zip(files, relative_paths)):
|
||||
try:
|
||||
# Read file content
|
||||
file_bytes = await file.read()
|
||||
file_size = len(file_bytes)
|
||||
mime_type = file.content_type or 'application/octet-stream'
|
||||
filename = file.filename or f"file_{i}"
|
||||
|
||||
total_size += file_size
|
||||
|
||||
# Generate file ID and determine parent directory
|
||||
file_id = str(uuid.uuid4())
|
||||
|
||||
# Find the correct parent directory for this file
|
||||
path_parts = relative_path.split('/')
|
||||
if len(path_parts) > 1:
|
||||
parent_dir_path = '/'.join(path_parts[:-1])
|
||||
parent_directory_id = directory_records.get(parent_dir_path)
|
||||
else:
|
||||
parent_directory_id = None # File is in root cabinet
|
||||
|
||||
# Use parent directory ID for storage path if exists, otherwise use a generated path
|
||||
if parent_directory_id:
|
||||
storage_path = f"{cabinet_id}/{parent_directory_id}/{path_parts[-1]}"
|
||||
else:
|
||||
storage_path = f"{cabinet_id}/{file_id}"
|
||||
|
||||
# Store file in Supabase storage
|
||||
storage.upload_file(bucket, storage_path, file_bytes, mime_type, upsert=True)
|
||||
|
||||
# Create file record
|
||||
file_record = {
|
||||
'id': file_id,
|
||||
'name': filename,
|
||||
'cabinet_id': cabinet_id,
|
||||
'bucket': bucket,
|
||||
'path': storage_path,
|
||||
'mime_type': mime_type,
|
||||
'uploaded_by': user_id,
|
||||
'size_bytes': file_size,
|
||||
'source': 'classroomcopilot-web',
|
||||
'is_directory': False,
|
||||
'parent_directory_id': parent_directory_id,
|
||||
'relative_path': relative_path,
|
||||
'upload_session_id': upload_session_id,
|
||||
'processing_status': 'uploaded'
|
||||
}
|
||||
|
||||
uploaded_files.append(file_record)
|
||||
|
||||
# Build directory structure for manifest
|
||||
_add_to_directory_structure(directory_structure, relative_path, {
|
||||
'size': file_size,
|
||||
'mime_type': mime_type,
|
||||
'file_id': file_id
|
||||
})
|
||||
|
||||
logger.info(f"📄 Uploaded file {i+1}/{len(files)}: {relative_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upload file {relative_path}: {e}")
|
||||
# Continue with other files, don't fail entire upload
|
||||
continue
|
||||
|
||||
# Create directory manifest
|
||||
directory_manifest = {
|
||||
'total_files': len(uploaded_files),
|
||||
'total_size_bytes': total_size,
|
||||
'directory_structure': directory_structure,
|
||||
'upload_timestamp': '2024-09-23T12:00:00Z', # TODO: Use actual timestamp
|
||||
'upload_method': 'directory_picker',
|
||||
'upload_session_id': upload_session_id
|
||||
}
|
||||
|
||||
# Update root directory with manifest and total size (if root directory exists)
|
||||
root_directory_id = directory_records.get(sorted_directories[0]) if sorted_directories else None
|
||||
if root_directory_id:
|
||||
update_res = client.supabase.table('files').update({
|
||||
'size_bytes': total_size,
|
||||
'directory_manifest': directory_manifest
|
||||
}).eq('id', root_directory_id).execute()
|
||||
|
||||
if not update_res.data:
|
||||
logger.warning("Failed to update root directory with manifest")
|
||||
|
||||
# Insert all file records in batch
|
||||
if uploaded_files:
|
||||
files_insert_res = client.supabase.table('files').insert(uploaded_files).execute()
|
||||
|
||||
if not files_insert_res.data:
|
||||
logger.warning("Some file records failed to insert")
|
||||
|
||||
logger.info(f"✅ Directory upload completed: {root_directory_id} ({len(uploaded_files)} files, {len(sorted_directories)} directories)")
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': f'Directory uploaded successfully with {len(uploaded_files)} files in {len(sorted_directories)} directories',
|
||||
'directories_created': len(sorted_directories),
|
||||
'files_count': len(uploaded_files),
|
||||
'total_size_bytes': total_size,
|
||||
'root_directory_id': root_directory_id,
|
||||
'upload_session_id': upload_session_id,
|
||||
'processing_required': False, # No automatic processing
|
||||
'next_steps': 'Files are ready for manual processing if needed'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Directory upload failed: {e}")
|
||||
# TODO: Implement cleanup of partially uploaded files
|
||||
raise HTTPException(status_code=500, detail=f"Directory upload failed: {str(e)}")
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Directory upload error: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
|
||||
|
||||
def _add_to_directory_structure(structure: Dict, relative_path: str, file_info: Dict):
|
||||
"""Add a file to the directory structure manifest."""
|
||||
path_parts = relative_path.split('/')
|
||||
current_level = structure
|
||||
|
||||
# Navigate/create directory structure
|
||||
for i, part in enumerate(path_parts):
|
||||
if i == len(path_parts) - 1:
|
||||
# This is the file itself
|
||||
current_level[part] = file_info
|
||||
else:
|
||||
# This is a directory
|
||||
if part not in current_level:
|
||||
current_level[part] = {}
|
||||
current_level = current_level[part]
|
||||
|
||||
@router.get("/files")
|
||||
def list_files(
|
||||
cabinet_id: str,
|
||||
include_directories: bool = True,
|
||||
parent_directory_id: Optional[str] = None,
|
||||
page: int = 1,
|
||||
per_page: int = 20,
|
||||
search: Optional[str] = None,
|
||||
sort_by: str = 'created_at',
|
||||
sort_order: str = 'desc',
|
||||
payload: Dict[str, Any] = Depends(auth)
|
||||
):
|
||||
"""
|
||||
List files with pagination, search, and sorting support.
|
||||
|
||||
Args:
|
||||
cabinet_id: Cabinet to list files from
|
||||
include_directories: Whether to include directory entries
|
||||
parent_directory_id: Filter by parent directory
|
||||
page: Page number (1-based)
|
||||
per_page: Items per page (max 100)
|
||||
search: Search term for filename
|
||||
sort_by: Field to sort by (name, size_bytes, created_at, processing_status)
|
||||
sort_order: Sort order (asc, desc)
|
||||
"""
|
||||
try:
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
# Validate pagination parameters
|
||||
page = max(1, page)
|
||||
per_page = min(max(1, per_page), 100) # Limit to 100 items per page
|
||||
offset = (page - 1) * per_page
|
||||
|
||||
# Validate sort parameters
|
||||
valid_sort_fields = ['name', 'size_bytes', 'created_at', 'processing_status', 'mime_type']
|
||||
if sort_by not in valid_sort_fields:
|
||||
sort_by = 'created_at'
|
||||
|
||||
if sort_order.lower() not in ['asc', 'desc']:
|
||||
sort_order = 'desc'
|
||||
|
||||
# Build base query
|
||||
query = client.supabase.table('files').select('*').eq('cabinet_id', cabinet_id)
|
||||
count_query = client.supabase.table('files').select('id', count='exact').eq('cabinet_id', cabinet_id)
|
||||
|
||||
# Apply filters
|
||||
if parent_directory_id:
|
||||
query = query.eq('parent_directory_id', parent_directory_id)
|
||||
count_query = count_query.eq('parent_directory_id', parent_directory_id)
|
||||
elif not include_directories:
|
||||
query = query.eq('is_directory', False)
|
||||
count_query = count_query.eq('is_directory', False)
|
||||
|
||||
# Apply search filter
|
||||
if search:
|
||||
search_term = f"%{search}%"
|
||||
query = query.ilike('name', search_term)
|
||||
count_query = count_query.ilike('name', search_term)
|
||||
|
||||
# Get total count
|
||||
count_res = count_query.execute()
|
||||
total_count = count_res.count if hasattr(count_res, 'count') else len(count_res.data or [])
|
||||
|
||||
# Apply sorting and pagination
|
||||
query = query.order(sort_by, desc=(sort_order.lower() == 'desc'))
|
||||
query = query.range(offset, offset + per_page - 1)
|
||||
|
||||
res = query.execute()
|
||||
files = res.data or []
|
||||
|
||||
# Calculate pagination metadata
|
||||
total_pages = (total_count + per_page - 1) // per_page
|
||||
has_next = page < total_pages
|
||||
has_prev = page > 1
|
||||
|
||||
return {
|
||||
'files': files,
|
||||
'pagination': {
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'total_count': total_count,
|
||||
'total_pages': total_pages,
|
||||
'has_next': has_next,
|
||||
'has_prev': has_prev,
|
||||
'offset': offset
|
||||
},
|
||||
'filters': {
|
||||
'search': search,
|
||||
'sort_by': sort_by,
|
||||
'sort_order': sort_order,
|
||||
'include_directories': include_directories,
|
||||
'parent_directory_id': parent_directory_id
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"List files error: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/files/{file_id}")
|
||||
def get_file_details(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Get detailed information about a file or directory."""
|
||||
try:
|
||||
client = SupabaseServiceRoleClient()
|
||||
|
||||
res = client.supabase.table('files').select('*').eq('id', file_id).single().execute()
|
||||
|
||||
if not res.data:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
file_data = res.data
|
||||
|
||||
# If it's a directory, also get its contents
|
||||
if file_data.get('is_directory'):
|
||||
contents_res = client.supabase.table('files').select('*').eq('parent_directory_id', file_id).execute()
|
||||
file_data['contents'] = contents_res.data or []
|
||||
|
||||
return file_data
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Get file details error: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete("/files/{file_id}")
|
||||
def delete_file(file_id: str, payload: Dict[str, Any] = Depends(auth)):
|
||||
"""Delete a file or directory and its contents."""
|
||||
try:
|
||||
client = SupabaseServiceRoleClient()
|
||||
storage = StorageAdmin()
|
||||
|
||||
# Get file info
|
||||
res = client.supabase.table('files').select('*').eq('id', file_id).single().execute()
|
||||
|
||||
if not res.data:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
file_data = res.data
|
||||
|
||||
# If it's a directory, delete all contents first
|
||||
if file_data.get('is_directory'):
|
||||
contents_res = client.supabase.table('files').select('*').eq('parent_directory_id', file_id).execute()
|
||||
|
||||
# Delete each file in the directory
|
||||
for content_file in contents_res.data or []:
|
||||
try:
|
||||
# Delete from storage
|
||||
storage.delete_file(content_file['bucket'], content_file['path'])
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete file from storage: {content_file['path']}: {e}")
|
||||
|
||||
# Delete all directory contents from database
|
||||
client.supabase.table('files').delete().eq('parent_directory_id', file_id).execute()
|
||||
else:
|
||||
# Delete single file from storage
|
||||
try:
|
||||
storage.delete_file(file_data['bucket'], file_data['path'])
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete file from storage: {file_data['path']}: {e}")
|
||||
|
||||
# Delete the main record
|
||||
delete_res = client.supabase.table('files').delete().eq('id', file_id).execute()
|
||||
|
||||
logger.info(f"🗑️ Deleted {'directory' if file_data.get('is_directory') else 'file'}: {file_id}")
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'message': f"{'Directory' if file_data.get('is_directory') else 'File'} deleted successfully"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Delete file error: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user