Update app.py: enhanced location_accuracy

This commit is contained in:
Nguyen Ngo
2025-08-18 16:26:02 -04:00
parent 5102c132b4
commit 8c1a3f8a38
+108 -88
View File
@@ -6,10 +6,12 @@ from datetime import datetime, date, time, timedelta
from sqlalchemy import text
from user_agents import parse
import io, os, base64, re, uuid, requests, json, qrcode, math
from PIL import Image, ImageDraw
from math import radians, sin, cos, asin, sqrt
from dotenv import load_dotenv
# Import the logging handler
from logger_handler import AppLogger, log_user_activity, log_database_operations
from PIL import Image, ImageDraw
# Load environment variables in .env
load_dotenv()
@@ -591,13 +593,6 @@ def calculate_location_accuracy_enhanced(qr_address, checkin_address, checkin_la
"""
ENHANCED location accuracy calculation comparing QR address with check-in location
This function provides improved precision and better error handling
Parameters:
- qr_address: Address associated with the QR code
- checkin_address: Address where user checked in (from reverse geocoding)
- checkin_lat: GPS latitude from check-in (if available)
- checkin_lng: GPS longitude from check-in (if available)
Returns:
- Distance in miles between QR location and check-in location
"""
@@ -614,13 +609,18 @@ def calculate_location_accuracy_enhanced(qr_address, checkin_address, checkin_la
# Step 1: Get coordinates for QR address using enhanced geocoding
print(f"\n📍 Step 1: Geocoding QR address...")
try:
qr_lat, qr_lng, qr_accuracy = get_coordinates_from_address_enhanced(qr_address)
print(f" Geocoding result: lat={qr_lat}, lng={qr_lng}, accuracy={qr_accuracy}")
if qr_lat is None or qr_lng is None:
print(f"❌ Could not geocode QR address: {qr_address}")
return None
print(f"✅ QR location coordinates: {qr_lat:.10f}, {qr_lng:.10f} (accuracy: {qr_accuracy})")
except Exception as e:
print(f"❌ Error geocoding QR address: {e}")
return None
# Step 2: Determine check-in coordinates
print(f"\n📱 Step 2: Determining check-in coordinates...")
@@ -643,16 +643,21 @@ def calculate_location_accuracy_enhanced(qr_address, checkin_address, checkin_la
print(f"✅ Using GPS coordinates: {lat_val:.10f}, {lng_val:.10f}")
else:
print(f"⚠️ Invalid GPS coordinates: {lat_val}, {lng_val}")
except (ValueError, TypeError):
print(f"⚠️ Could not parse GPS coordinates")
except (ValueError, TypeError) as e:
print(f"⚠️ Could not parse GPS coordinates: {e}")
# Priority 2: Fallback to geocoding check-in address
if checkin_coords_lat is None and checkin_address:
print(f"🌍 Falling back to geocoding check-in address...")
try:
checkin_coords_lat, checkin_coords_lng, checkin_accuracy = get_coordinates_from_address_enhanced(checkin_address)
print(f" Checkin geocoding result: lat={checkin_coords_lat}, lng={checkin_coords_lng}, accuracy={checkin_accuracy}")
if checkin_coords_lat is not None:
checkin_source = "address"
print(f"✅ Using geocoded coordinates: {checkin_coords_lat:.10f}, {checkin_coords_lng:.10f} (accuracy: {checkin_accuracy})")
except Exception as e:
print(f"❌ Error geocoding check-in address: {e}")
# Check if we have valid coordinates for both locations
if checkin_coords_lat is None or checkin_coords_lng is None:
@@ -661,21 +666,31 @@ def calculate_location_accuracy_enhanced(qr_address, checkin_address, checkin_la
print(f" Address: {checkin_address}")
return None
# Step 3: Calculate enhanced distance
print(f"\n📏 Step 3: Calculating enhanced distance...")
# Step 3: Calculate distance
print(f"\n📏 Step 3: Calculating distance...")
try:
print(f" QR coordinates: {qr_lat:.10f}, {qr_lng:.10f}")
print(f" Check-in coordinates: {checkin_coords_lat:.10f}, {checkin_coords_lng:.10f}")
print(f" Source: {checkin_source}")
distance = calculate_distance_miles(qr_lat, qr_lng, checkin_coords_lat, checkin_coords_lng)
print(f" Distance calculation result: {distance}")
if distance is not None:
accuracy_level = get_location_accuracy_level_enhanced(distance)
print(f"✅ Enhanced location accuracy calculated successfully!")
print(f" QR Location: {qr_lat:.10f}, {qr_lng:.10f}")
print(f" Check-in Location: {checkin_coords_lat:.10f}, {checkin_coords_lng:.10f}")
print(f" Source: {checkin_source}")
print(f" Distance: {distance:.4f} miles")
print(f" Accuracy Level: {get_location_accuracy_level_enhanced(distance)}")
else:
print(f"❌ Failed to calculate distance")
print(f" Accuracy Level: {accuracy_level}")
return distance
else:
print(f"❌ Distance calculation returned None")
return None
except Exception as e:
print(f"❌ Error calculating distance: {e}")
import traceback
print(f"❌ Distance calculation traceback: {traceback.format_exc()}")
return None
def generate_qr_url(name, qr_id):
"""Generate a unique URL for QR code destination"""
@@ -713,50 +728,6 @@ def get_client_ip():
else:
return request.environ['HTTP_X_FORWARDED_FOR']
def calculate_distance_miles(lat1, lng1, lat2, lng2):
"""
Calculate the great circle distance between two points on Earth in miles
Using the Haversine formula
"""
if any(coord is None for coord in [lat1, lng1, lat2, lng2]):
return None
try:
# Convert decimal degrees to radians
lat1, lng1, lat2, lng2 = map(radians, [lat1, lng1, lat2, lng2])
# Haversine formula
dlng = lng2 - lng1
dlat = lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlng/2)**2
c = 2 * asin(sqrt(a))
# Radius of Earth in miles
r_miles = 3959
# Calculate the result
distance = c * r_miles
print(f"📏 Calculated distance: {distance:.3f} miles")
return round(distance, 3)
except Exception as e:
print(f"❌ Error calculating distance: {e}")
return None
def get_location_accuracy_level(location_accuracy):
"""Get human-readable location accuracy level based on distance"""
if not location_accuracy:
return 'unknown'
elif location_accuracy <= 0.1: # Within 0.1 mile (528 feet)
return 'excellent'
elif location_accuracy <= 0.5: # Within 0.5 mile
return 'good'
elif location_accuracy <= 1.0: # Within 1 mile
return 'fair'
else:
return 'poor'
def get_location_accuracy_level_enhanced(location_accuracy):
"""
Enhanced function to categorize location accuracy with more granular levels
@@ -1931,7 +1902,7 @@ def user_stats_api():
@app.route('/api/roles/permissions')
@admin_required
def role_permissions_api():
"""NEW: API endpoint to get role permissions data"""
"""API endpoint to get role permissions data"""
try:
permissions_data = {}
for role in VALID_ROLES:
@@ -3023,16 +2994,6 @@ def edit_qr_code(qr_id):
@log_database_operations('qr_code_deletion')
def delete_qr_code(qr_id):
"""Permanently delete QR code (Admin only) - Hard delete - PRESERVING EXACT ROUTE"""
# OBVIOUS DEBUGGING - You MUST see this in console
print("\n" + "="*60)
print("🔥 DELETE ROUTE WAS CALLED! 🔥")
print(f"🔥 QR ID: {qr_id}")
print(f"🔥 Method: {request.method}")
print(f"🔥 User: {session.get('username', 'NO_USER')}")
print(f"🔥 Role: {session.get('role', 'NO_ROLE')}")
print("="*60 + "\n")
try:
qr_code = QRCode.query.get_or_404(qr_id)
print(f"✅ Found QR Code: {qr_code.name}")
@@ -3182,10 +3143,10 @@ def qr_checkin(qr_url):
else:
print(f"✅ First check-in today for {employee_id}")
# PRESERVED: Process location data with coordinate-to-address conversion
# Process location data with coordinate-to-address conversion
location_data = process_location_data_enhanced(request.form)
# PRESERVED: Get device and network info
# 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()
@@ -3194,7 +3155,7 @@ def qr_checkin(qr_url):
print(f"🌐 IP Address: {client_ip}")
print(f"📍 Location Data: {location_data}")
# PRESERVED: Create attendance record
# Create attendance record
print(f"\n💾 CREATING ATTENDANCE RECORD:")
attendance = AttendanceData(
@@ -3211,17 +3172,44 @@ def qr_checkin(qr_url):
accuracy=location_data['accuracy'],
altitude=location_data['altitude'],
location_source=location_data['source'],
address=location_data['address'], # This now includes converted address
address=location_data['address'],
status='present'
)
print(f"✅ Created base attendance record")
# PRESERVED: Calculate location accuracy
print(f"\n🎯 CALCULATING LOCATION ACCURACY...")
# ENHANCED DEBUG: Calculate location accuracy with detailed logging
print(f"\n🎯 CALCULATING LOCATION ACCURACY WITH ENHANCED DEBUG...")
print(f" 📊 QR Code Details:")
print(f" ID: {qr_code.id}")
print(f" Name: {qr_code.name}")
print(f" Location: {qr_code.location}")
print(f" Location Address: {qr_code.location_address}")
print(f" Has location_address: {qr_code.location_address is not None}")
print(f" Location Address Length: {len(qr_code.location_address) if qr_code.location_address else 0}")
print(f" 📍 Check-in Data:")
print(f" Latitude: {location_data['latitude']}")
print(f" Longitude: {location_data['longitude']}")
print(f" GPS Accuracy: {location_data['accuracy']}")
print(f" Address: {location_data['address']}")
print(f" Address Length: {len(location_data['address']) if location_data['address'] else 0}")
print(f" Source: {location_data['source']}")
location_accuracy = None
try:
# Check if we have the required data
if not qr_code.location_address:
print(f"❌ QR code location_address is empty or None")
print(f" QR Code location_address value: '{qr_code.location_address}'")
elif not location_data['address'] and not (location_data['latitude'] and location_data['longitude']):
print(f"❌ No check-in address or coordinates available")
print(f" Check-in address: '{location_data['address']}'")
print(f" Check-in coords: {location_data['latitude']}, {location_data['longitude']}")
else:
print(f"✅ Required data available, proceeding with calculation...")
location_accuracy = calculate_location_accuracy_enhanced(
qr_address=qr_code.location_address,
checkin_address=location_data['address'],
@@ -3229,23 +3217,52 @@ def qr_checkin(qr_url):
checkin_lng=location_data['longitude']
)
print(f"📐 Location accuracy calculation result: {location_accuracy}")
if location_accuracy is not None:
attendance.location_accuracy = location_accuracy
accuracy_level = get_location_accuracy_level_enhanced(location_accuracy)
print(f"✅ Location accuracy set: {location_accuracy:.4f} miles ({accuracy_level})")
print(f"✅ Location accuracy set successfully: {location_accuracy:.4f} miles ({accuracy_level})")
print(f"📊 Final attendance.location_accuracy value: {attendance.location_accuracy}")
else:
print(f"⚠️ Could not calculate location accuracy")
print(f"⚠️ Could not calculate location accuracy - calculation returned None")
except Exception as e:
print(f"❌ Error in location accuracy calculation: {e}")
import traceback
print(f"❌ Full traceback: {traceback.format_exc()}")
# PRESERVED: Save to database
# ENHANCED DEBUG: Save to database with verification
try:
print(f"\n💾 SAVING TO DATABASE...")
print(f" Attendance object before save:")
print(f" Employee ID: {attendance.employee_id}")
print(f" Location: {attendance.location_name}")
print(f" Latitude: {attendance.latitude}")
print(f" Longitude: {attendance.longitude}")
print(f" Address: {attendance.address}")
print(f" Location Accuracy: {attendance.location_accuracy}")
db.session.add(attendance)
db.session.commit()
print(f"✅ Successfully saved attendance record with ID: {attendance.id}")
# NEW: Count total check-ins for today for this employee at this location
# VERIFICATION: Read back from database
saved_record = AttendanceData.query.get(attendance.id)
print(f"✅ Successfully saved attendance record with ID: {attendance.id}")
print(f"📊 Verification - location accuracy in database: {saved_record.location_accuracy}")
if saved_record.location_accuracy != attendance.location_accuracy:
print(f"⚠️ WARNING: Database value differs from object value!")
print(f" Object value: {attendance.location_accuracy}")
print(f" Database value: {saved_record.location_accuracy}")
# Add enhanced logging for location accuracy save
if attendance.location_accuracy is not None:
logger_handler.logger.info(f"Location accuracy calculated and saved: {attendance.location_accuracy:.4f} miles for employee {attendance.employee_id}")
else:
logger_handler.logger.warning(f"Location accuracy could not be calculated for employee {attendance.employee_id} at QR {qr_code.name}")
# 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(),
@@ -3256,13 +3273,16 @@ def qr_checkin(qr_url):
except Exception as e:
print(f"❌ Database error: {e}")
import traceback
print(f"❌ Full traceback: {traceback.format_exc()}")
db.session.rollback()
logger_handler.log_database_error('checkin_save', e)
return jsonify({
'success': False,
'message': 'Database error occurred.'
}), 500
# ENHANCED: Return success response with sequence information
# Return success response with sequence information
response_data = {
'success': True,
'message': f'Check-in successful! {checkin_sequence_text} for today.',
@@ -4772,8 +4792,8 @@ if __name__ == '__main__':
try:
# Initialize database and logging
create_tables()
update_existing_qr_codes()
add_qr_customization_columns()
# update_existing_qr_codes()
# add_qr_customization_columns()
# Log application startup
logger_handler.logger.info("QR Attendance Management System started successfully")