Updated location address accuracy

This commit is contained in:
2025-08-07 21:29:50 -04:00
parent 75291274c3
commit 80a72d3458
3 changed files with 761 additions and 588 deletions
+82 -15
View File
@@ -663,10 +663,55 @@ def process_location_data(location_data):
return processed
def reverse_geocode_coordinates(latitude, longitude):
"""
Convert GPS coordinates to human-readable address using reverse geocoding
Returns address string or None if failed
"""
if not latitude or not longitude:
return None
try:
print(f"🌍 Reverse geocoding coordinates: {latitude}, {longitude}")
# Using Nominatim (OpenStreetMap) reverse geocoding service
url = "https://nominatim.openstreetmap.org/reverse"
params = {
'lat': latitude,
'lon': longitude,
'format': 'json',
'addressdetails': 1,
'zoom': 18 # High detail level
}
headers = {
'User-Agent': 'QR-Attendance-System/1.0'
}
response = requests.get(url, params=params, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
if data and 'display_name' in data:
address = data['display_name']
print(f"✅ Reverse geocoded address: {address}")
return address
else:
print(f"⚠️ No address found for coordinates")
return None
else:
print(f"⚠️ Reverse geocoding API returned status: {response.status_code}")
return None
except Exception as e:
print(f"❌ Error in reverse geocoding: {e}")
return None
def process_location_data_enhanced(form_data):
"""
Enhanced processing of location data from form submission
Validates and cleans location data for storage
Validates and cleans location data for storage, including reverse geocoding
"""
processed = {
'latitude': None,
@@ -707,13 +752,33 @@ def process_location_data_enhanced(form_data):
alt = float(form_data['altitude'])
processed['altitude'] = alt
# Process address (limit length for database storage)
# Process address - First check if address was provided
if form_data.get('address'):
address = form_data['address'].strip()
if address and address not in ['null', '', 'undefined']:
# Check if the address is just coordinates (like "38.8104192000, -77.1850240000")
if re.match(r'^-?\d+\.\d+,?\s*-?\d+\.\d+$', address.replace(' ', '')):
print(f"🔍 Detected coordinate-format address: {address}")
# This is just coordinates, we need to reverse geocode
processed['address'] = None # Reset so reverse geocoding will trigger
else:
# This is a real address
processed['address'] = address[:500] # Limit to 500 characters
print(f"✅ Using provided address: {processed['address'][:100]}...")
print(f"📍 Processed location data:")
# CRITICAL: If we have coordinates but no real address, perform reverse geocoding
if (processed['latitude'] is not None and processed['longitude'] is not None
and not processed['address']):
print(f"🌍 Performing reverse geocoding for coordinates: {processed['latitude']}, {processed['longitude']}")
reverse_geocoded_address = reverse_geocode_coordinates(processed['latitude'], processed['longitude'])
if reverse_geocoded_address:
processed['address'] = reverse_geocoded_address[:500]
print(f"✅ Reverse geocoded address: {processed['address']}")
else:
print(f"⚠️ Could not reverse geocode coordinates, keeping coordinates as fallback")
processed['address'] = f"{processed['latitude']:.10f}, {processed['longitude']:.10f}"
print(f"📍 Final processed location data:")
print(f" Coordinates: {processed['latitude']}, {processed['longitude']}")
print(f" GPS Accuracy: {processed['accuracy']}m")
print(f" Source: {processed['source']}")
@@ -1932,6 +1997,7 @@ def qr_checkin(qr_url):
"""
Enhanced staff check-in with location accuracy calculation
Allows multiple check-ins with minimum 30-minute intervals between them
PRESERVES coordinate-to-address conversion functionality
"""
try:
print(f"\n🚀 STARTING ENHANCED CHECK-IN PROCESS")
@@ -1980,23 +2046,23 @@ def qr_checkin(qr_url):
# Check if 30 minutes have passed since the last check-in
if recent_checkin_datetime > thirty_minutes_ago:
minutes_remaining = 30 - int((current_time - recent_checkin_datetime).total_seconds() / 60)
print(f"⚠️ Too soon for another submission for {employee_id}")
print(f" Last submission: {recent_checkin.check_in_time.strftime('%H:%M')}")
print(f"⚠️ Too soon for another check-in for {employee_id}")
print(f" Last check-in: {recent_checkin.check_in_time.strftime('%H:%M')}")
print(f" Minutes remaining: {minutes_remaining}")
return jsonify({
'success': False,
'message': f'You can submit again in {minutes_remaining} minutes. Last submission was at {recent_checkin.check_in_time.strftime("%H:%M")}.'
'message': f'You can check in again in {minutes_remaining} minutes. Last check-in was at {recent_checkin.check_in_time.strftime("%H:%M")}.'
}), 400
else:
print(f"✅ 30-minute interval satisfied. Allowing new check-in for {employee_id}")
else:
print(f"✅ First submission today for {employee_id}")
print(f"✅ First check-in today for {employee_id}")
# Process location data
# PRESERVED: Process location data with coordinate-to-address conversion
location_data = process_location_data_enhanced(request.form)
# Get device and network info
# PRESERVED: Get device and network info
user_agent_string = request.headers.get('User-Agent', '')
device_info = detect_device_info(user_agent_string)
client_ip = get_client_ip()
@@ -2005,7 +2071,7 @@ def qr_checkin(qr_url):
print(f"🌐 IP Address: {client_ip}")
print(f"📍 Location Data: {location_data}")
# Create attendance record
# PRESERVED: Create attendance record
print(f"\n💾 CREATING ATTENDANCE RECORD:")
attendance = AttendanceData(
@@ -2022,13 +2088,13 @@ def qr_checkin(qr_url):
accuracy=location_data['accuracy'],
altitude=location_data['altitude'],
location_source=location_data['source'],
address=location_data['address'],
address=location_data['address'], # This now includes converted address
status='present'
)
print(f"✅ Created base attendance record")
# Calculate location accuracy
# PRESERVED: Calculate location accuracy
print(f"\n🎯 CALCULATING LOCATION ACCURACY...")
location_accuracy = None
@@ -2050,13 +2116,13 @@ def qr_checkin(qr_url):
except Exception as e:
print(f"❌ Error in location accuracy calculation: {e}")
# Save to database
# PRESERVED: Save to database
try:
db.session.add(attendance)
db.session.commit()
print(f"✅ Successfully saved attendance record with ID: {attendance.id}")
# Count total check-ins for today for this employee at this location
# NEW: Count total check-ins for today for this employee at this location
today_checkin_count = AttendanceData.query.filter_by(
qr_code_id=qr_code.id,
employee_id=employee_id.upper(),
@@ -2073,7 +2139,7 @@ def qr_checkin(qr_url):
'message': 'Database error occurred.'
}), 500
# Return success response
# ENHANCED: Return success response with sequence information
response_data = {
'success': True,
'message': f'Check-in successful! {checkin_sequence_text} for today.',
@@ -2101,6 +2167,7 @@ def qr_checkin(qr_url):
print(f" Employee: {attendance.employee_id}")
print(f" Time: {attendance.check_in_time}")
print(f" Location: {attendance.location_name}")
print(f" Address: {attendance.address}")
print(f" Today's count: {today_checkin_count}")
return jsonify(response_data), 200
+27 -4
View File
@@ -45,7 +45,7 @@ const translations = {
error: "Error en el registro. Por favor intente de nuevo.",
duplicate: "Ya se ha registrado hoy.",
tooSoon: "Por favor espere antes de registrarse nuevamente.",
multipleSuccess: "Submitted successful!!",
multipleSuccess: "Submitted successfully!!",
invalidId: "Por favor ingrese un ID de empleado válido.",
locationError: "No se pudo obtener datos de ubicación.",
networkError: "Error de red. Verifique su conexión.",
@@ -225,7 +225,7 @@ function handleCheckinResponse(data) {
// ENHANCED SUCCESS HANDLING WITH MULTIPLE CHECK-IN INFO
function handleCheckinSuccess(data) {
console.log("✅ Submitted successful!");
console.log("✅ Submitted successfully!");
const responseData = data.data || data || {};
const checkinCount = responseData.checkin_count_today || 1;
@@ -515,10 +515,33 @@ function handleLocationError(error) {
}
function reverseGeocode(lat, lng) {
// This would typically use a geocoding service
// For now, just set a placeholder
console.log(`🌍 Starting reverse geocoding for: ${lat}, ${lng}`);
// Using Nominatim (OpenStreetMap) reverse geocoding service
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&addressdetails=1&zoom=18`;
fetch(url, {
method: "GET",
headers: {
"User-Agent": "QR-Attendance-System/1.0",
},
})
.then((response) => response.json())
.then((data) => {
if (data && data.display_name) {
userLocation.address = data.display_name;
console.log(`✅ Reverse geocoded address: ${userLocation.address}`);
} else {
console.log(`⚠️ No address found, using coordinates as fallback`);
userLocation.address = `${lat.toFixed(10)}, ${lng.toFixed(10)}`;
}
})
.catch((error) => {
console.error(`❌ Reverse geocoding error:`, error);
// Fallback to coordinates if reverse geocoding fails
userLocation.address = `${lat.toFixed(10)}, ${lng.toFixed(10)}`;
});
}
// Language functionality remains unchanged
function initializeLanguage() {
+119 -36
View File
@@ -1,10 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ qr_code.location_event }} - Check In</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
rel="stylesheet"
/>
<style>
/* PRESERVED: All existing styles remain unchanged */
:root {
@@ -30,7 +33,7 @@
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 1rem;
@@ -93,7 +96,11 @@
.header-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
background: linear-gradient(
135deg,
var(--primary-color),
var(--primary-hover)
);
border-radius: 50%;
display: flex;
align-items: center;
@@ -333,9 +340,17 @@
}
@keyframes successPulse {
0% { transform: scale(0.8); opacity: 0; }
50% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
0% {
transform: scale(0.8);
opacity: 0;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.success-title {
@@ -380,7 +395,11 @@
/* NEW: Enhanced styles for multiple check-in information */
.checkin-sequence {
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
background: linear-gradient(
135deg,
var(--primary-color),
var(--primary-hover)
);
color: white;
padding: 0.5rem 1rem;
border-radius: 1rem;
@@ -470,7 +489,12 @@
<div class="destination-container">
<!-- Language Selector -->
<div class="language-selector">
<button id="languageToggle" class="language-toggle" type="button" aria-label="Switch Language">
<button
id="languageToggle"
class="language-toggle"
type="button"
aria-label="Switch Language"
>
<i class="fas fa-globe"></i>
<span id="languageText">EN</span>
</button>
@@ -501,26 +525,43 @@
<div class="checkin-header">
<h2>
<i class="fas fa-user-check"></i>
<span data-en="Attendance Tracking" data-es="Seguimiento de Asistencia">Attendance Tracking</span>
<span
data-en="Attendance Tracking"
data-es="Seguimiento de Asistencia"
>Attendance Tracking</span
>
</h2>
<p data-en="Please enter your Employee ID to check in/out" data-es="Por favor ingrese su ID de empleado para registrar su entrada/salida">Please enter your Employee ID to check in/out</p>
<p
data-en="Please enter your Employee ID to check in/out"
data-es="Por favor ingrese su ID de empleado para registrar su entrada/salida"
>
Please enter your Employee ID to check in/out
</p>
</div>
<form id="checkinForm" class="checkin-form">
<!-- HIDDEN LOCATION FIELDS -->
<input type="hidden" id="latitude" name="latitude">
<input type="hidden" id="longitude" name="longitude">
<input type="hidden" id="accuracy" name="accuracy">
<input type="hidden" id="altitude" name="altitude">
<input type="hidden" id="locationSource" name="location_source" value="manual">
<input type="hidden" id="address" name="address">
<input type="hidden" id="latitude" name="latitude" />
<input type="hidden" id="longitude" name="longitude" />
<input type="hidden" id="accuracy" name="accuracy" />
<input type="hidden" id="altitude" name="altitude" />
<input
type="hidden"
id="locationSource"
name="location_source"
value="manual"
/>
<input type="hidden" id="address" name="address" />
<div class="form-group">
<label for="employee_id">
<i class="fas fa-id-badge"></i>
<span data-en="Employee ID" data-es="ID de Empleado">Employee ID</span>
<span data-en="Employee ID" data-es="ID de Empleado"
>Employee ID</span
>
</label>
<input type="text"
<input
type="text"
id="employee_id"
name="employee_id"
class="form-control"
@@ -528,7 +569,8 @@
data-es-placeholder="Ingrese su ID de Empleado"
placeholder="Enter your Employee ID"
autocomplete="off"
required>
required
/>
</div>
<button type="submit" id="submitCheckin" class="btn btn-primary">
@@ -545,10 +587,18 @@
<i class="fas fa-check"></i>
</div>
<h2 class="success-title">
<span data-en="Submitted Successful!" data-es="Enviado exitosamente!">Submitted Successful!</span>
<span
data-en="Submitted Successfully!"
data-es="Enviado exitosamente!"
>Submitted Successfully!</span
>
</h2>
<p class="success-subtitle">
<span data-en="Your attendance has been recorded" data-es="Su asistencia ha sido registrada">Your attendance has been recorded</span>
<span
data-en="Your attendance has been recorded"
data-es="Su asistencia ha sido registrada"
>Your attendance has been recorded</span
>
</p>
</div>
@@ -559,39 +609,72 @@
<div class="success-details">
<div class="detail-row">
<span class="detail-label" data-en="Employee ID" data-es="ID Empleado">Employee ID</span>
<span
class="detail-label"
data-en="Employee ID"
data-es="ID Empleado"
>Employee ID</span
>
<span class="detail-value" id="successEmployeeId">-</span>
</div>
<div class="detail-row">
<span class="detail-label" data-en="Location/Building" data-es="Ubicación/Edificio">Location/Building</span>
<span
class="detail-label"
data-en="Location/Building"
data-es="Ubicación/Edificio"
>Location/Building</span
>
<span class="detail-value" id="successLocation">-</span>
</div>
<div class="detail-row">
<span class="detail-label" data-en="Event" data-es="Evento">Event</span>
<span class="detail-label" data-en="Event" data-es="Evento"
>Event</span
>
<span class="detail-value" id="successEvent">-</span>
</div>
<div class="detail-row">
<span class="detail-label" data-en="Submission Time" data-es="Hora de presentación">Submission Time</span>
<span
class="detail-label"
data-en="Submission Time"
data-es="Hora de presentación"
>Submission Time</span
>
<span class="detail-value" id="successCheckInTime">-</span>
</div>
<div class="detail-row">
<span class="detail-label" data-en="Date" data-es="Fecha">Date</span>
<span class="detail-label" data-en="Date" data-es="Fecha"
>Date</span
>
<span class="detail-value" id="successCheckInDate">-</span>
</div>
<div class="detail-row" style="display: none;" id="deviceInfoRow">
<span class="detail-label" data-en="Device" data-es="Dispositivo">Device</span>
<div class="detail-row" style="display: none" id="deviceInfoRow">
<span class="detail-label" data-en="Device" data-es="Dispositivo"
>Device</span
>
<span class="detail-value" id="successDeviceInfo">-</span>
</div>
<div class="detail-row" style="display: none;" id="coordinatesRow">
<span class="detail-label" data-en="Coordinates" data-es="Coordenadas">Coordinates</span>
<div class="detail-row" style="display: none" id="coordinatesRow">
<span
class="detail-label"
data-en="Coordinates"
data-es="Coordenadas"
>Coordinates</span
>
<span class="detail-value" id="successCoordinates">-</span>
</div>
<div class="detail-row" style="display: none;" id="addressRow">
<span class="detail-label" data-en="Address" data-es="Dirección">Address</span>
<div class="detail-row" style="display: none" id="addressRow">
<span class="detail-label" data-en="Address" data-es="Dirección"
>Address</span
>
<span class="detail-value" id="successAddress">-</span>
</div>
<div class="detail-row" style="display: none;" id="accuracyRow">
<span class="detail-label" data-en="Location Accuracy" data-es="Precisión de Ubicación">Location Accuracy</span>
<div class="detail-row" style="display: none" id="accuracyRow">
<span
class="detail-label"
data-en="Location Accuracy"
data-es="Precisión de Ubicación"
>Location Accuracy</span
>
<span class="detail-value" id="successLocationAccuracy">-</span>
</div>
</div>