Update app.py: enhanced location_accuracy
This commit is contained in:
@@ -6,10 +6,12 @@ from datetime import datetime, date, time, timedelta
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from user_agents import parse
|
from user_agents import parse
|
||||||
import io, os, base64, re, uuid, requests, json, qrcode, math
|
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
|
from dotenv import load_dotenv
|
||||||
# Import the logging handler
|
# Import the logging handler
|
||||||
from logger_handler import AppLogger, log_user_activity, log_database_operations
|
from logger_handler import AppLogger, log_user_activity, log_database_operations
|
||||||
from PIL import Image, ImageDraw
|
|
||||||
|
|
||||||
# Load environment variables in .env
|
# Load environment variables in .env
|
||||||
load_dotenv()
|
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
|
ENHANCED location accuracy calculation comparing QR address with check-in location
|
||||||
This function provides improved precision and better error handling
|
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:
|
Returns:
|
||||||
- Distance in miles between QR location and check-in location
|
- Distance in miles between QR location and check-in location
|
||||||
"""
|
"""
|
||||||
@@ -614,14 +609,19 @@ def calculate_location_accuracy_enhanced(qr_address, checkin_address, checkin_la
|
|||||||
|
|
||||||
# Step 1: Get coordinates for QR address using enhanced geocoding
|
# Step 1: Get coordinates for QR address using enhanced geocoding
|
||||||
print(f"\n📍 Step 1: Geocoding QR address...")
|
print(f"\n📍 Step 1: Geocoding QR address...")
|
||||||
qr_lat, qr_lng, qr_accuracy = get_coordinates_from_address_enhanced(qr_address)
|
try:
|
||||||
|
qr_lat, qr_lng, qr_accuracy = get_coordinates_from_address_enhanced(qr_address)
|
||||||
if qr_lat is None or qr_lng is None:
|
print(f" Geocoding result: lat={qr_lat}, lng={qr_lng}, accuracy={qr_accuracy}")
|
||||||
print(f"❌ Could not geocode QR address: {qr_address}")
|
|
||||||
|
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
|
return None
|
||||||
|
|
||||||
print(f"✅ QR location coordinates: {qr_lat:.10f}, {qr_lng:.10f} (accuracy: {qr_accuracy})")
|
|
||||||
|
|
||||||
# Step 2: Determine check-in coordinates
|
# Step 2: Determine check-in coordinates
|
||||||
print(f"\n📱 Step 2: Determining 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}")
|
print(f"✅ Using GPS coordinates: {lat_val:.10f}, {lng_val:.10f}")
|
||||||
else:
|
else:
|
||||||
print(f"⚠️ Invalid GPS coordinates: {lat_val}, {lng_val}")
|
print(f"⚠️ Invalid GPS coordinates: {lat_val}, {lng_val}")
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError) as e:
|
||||||
print(f"⚠️ Could not parse GPS coordinates")
|
print(f"⚠️ Could not parse GPS coordinates: {e}")
|
||||||
|
|
||||||
# Priority 2: Fallback to geocoding check-in address
|
# Priority 2: Fallback to geocoding check-in address
|
||||||
if checkin_coords_lat is None and checkin_address:
|
if checkin_coords_lat is None and checkin_address:
|
||||||
print(f"🌍 Falling back to geocoding check-in address...")
|
print(f"🌍 Falling back to geocoding check-in address...")
|
||||||
checkin_coords_lat, checkin_coords_lng, checkin_accuracy = get_coordinates_from_address_enhanced(checkin_address)
|
try:
|
||||||
if checkin_coords_lat is not None:
|
checkin_coords_lat, checkin_coords_lng, checkin_accuracy = get_coordinates_from_address_enhanced(checkin_address)
|
||||||
checkin_source = "address"
|
print(f" Checkin geocoding result: lat={checkin_coords_lat}, lng={checkin_coords_lng}, accuracy={checkin_accuracy}")
|
||||||
print(f"✅ Using geocoded coordinates: {checkin_coords_lat:.10f}, {checkin_coords_lng:.10f} (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
|
# Check if we have valid coordinates for both locations
|
||||||
if checkin_coords_lat is None or checkin_coords_lng is None:
|
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}")
|
print(f" Address: {checkin_address}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Step 3: Calculate enhanced distance
|
# Step 3: Calculate distance
|
||||||
print(f"\n📏 Step 3: Calculating enhanced distance...")
|
print(f"\n📏 Step 3: Calculating distance...")
|
||||||
distance = calculate_distance_miles(qr_lat, qr_lng, checkin_coords_lat, checkin_coords_lng)
|
try:
|
||||||
|
print(f" QR coordinates: {qr_lat:.10f}, {qr_lng:.10f}")
|
||||||
if distance is not None:
|
print(f" Check-in coordinates: {checkin_coords_lat:.10f}, {checkin_coords_lng:.10f}")
|
||||||
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" Source: {checkin_source}")
|
||||||
print(f" Distance: {distance:.4f} miles")
|
|
||||||
print(f" Accuracy Level: {get_location_accuracy_level_enhanced(distance)}")
|
distance = calculate_distance_miles(qr_lat, qr_lng, checkin_coords_lat, checkin_coords_lng)
|
||||||
else:
|
print(f" Distance calculation result: {distance}")
|
||||||
print(f"❌ Failed to calculate distance")
|
|
||||||
|
if distance is not None:
|
||||||
return distance
|
accuracy_level = get_location_accuracy_level_enhanced(distance)
|
||||||
|
print(f"✅ Enhanced location accuracy calculated successfully!")
|
||||||
|
print(f" Distance: {distance:.4f} miles")
|
||||||
|
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):
|
def generate_qr_url(name, qr_id):
|
||||||
"""Generate a unique URL for QR code destination"""
|
"""Generate a unique URL for QR code destination"""
|
||||||
@@ -713,50 +728,6 @@ def get_client_ip():
|
|||||||
else:
|
else:
|
||||||
return request.environ['HTTP_X_FORWARDED_FOR']
|
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):
|
def get_location_accuracy_level_enhanced(location_accuracy):
|
||||||
"""
|
"""
|
||||||
Enhanced function to categorize location accuracy with more granular levels
|
Enhanced function to categorize location accuracy with more granular levels
|
||||||
@@ -1931,7 +1902,7 @@ def user_stats_api():
|
|||||||
@app.route('/api/roles/permissions')
|
@app.route('/api/roles/permissions')
|
||||||
@admin_required
|
@admin_required
|
||||||
def role_permissions_api():
|
def role_permissions_api():
|
||||||
"""NEW: API endpoint to get role permissions data"""
|
"""API endpoint to get role permissions data"""
|
||||||
try:
|
try:
|
||||||
permissions_data = {}
|
permissions_data = {}
|
||||||
for role in VALID_ROLES:
|
for role in VALID_ROLES:
|
||||||
@@ -3023,16 +2994,6 @@ def edit_qr_code(qr_id):
|
|||||||
@log_database_operations('qr_code_deletion')
|
@log_database_operations('qr_code_deletion')
|
||||||
def delete_qr_code(qr_id):
|
def delete_qr_code(qr_id):
|
||||||
"""Permanently delete QR code (Admin only) - Hard delete - PRESERVING EXACT ROUTE"""
|
"""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:
|
try:
|
||||||
qr_code = QRCode.query.get_or_404(qr_id)
|
qr_code = QRCode.query.get_or_404(qr_id)
|
||||||
print(f"✅ Found QR Code: {qr_code.name}")
|
print(f"✅ Found QR Code: {qr_code.name}")
|
||||||
@@ -3182,10 +3143,10 @@ def qr_checkin(qr_url):
|
|||||||
else:
|
else:
|
||||||
print(f"✅ First check-in today for {employee_id}")
|
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)
|
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', '')
|
user_agent_string = request.headers.get('User-Agent', '')
|
||||||
device_info = detect_device_info(user_agent_string)
|
device_info = detect_device_info(user_agent_string)
|
||||||
client_ip = get_client_ip()
|
client_ip = get_client_ip()
|
||||||
@@ -3194,7 +3155,7 @@ def qr_checkin(qr_url):
|
|||||||
print(f"🌐 IP Address: {client_ip}")
|
print(f"🌐 IP Address: {client_ip}")
|
||||||
print(f"📍 Location Data: {location_data}")
|
print(f"📍 Location Data: {location_data}")
|
||||||
|
|
||||||
# PRESERVED: Create attendance record
|
# Create attendance record
|
||||||
print(f"\n💾 CREATING ATTENDANCE RECORD:")
|
print(f"\n💾 CREATING ATTENDANCE RECORD:")
|
||||||
|
|
||||||
attendance = AttendanceData(
|
attendance = AttendanceData(
|
||||||
@@ -3211,41 +3172,97 @@ def qr_checkin(qr_url):
|
|||||||
accuracy=location_data['accuracy'],
|
accuracy=location_data['accuracy'],
|
||||||
altitude=location_data['altitude'],
|
altitude=location_data['altitude'],
|
||||||
location_source=location_data['source'],
|
location_source=location_data['source'],
|
||||||
address=location_data['address'], # This now includes converted address
|
address=location_data['address'],
|
||||||
status='present'
|
status='present'
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"✅ Created base attendance record")
|
print(f"✅ Created base attendance record")
|
||||||
|
|
||||||
# PRESERVED: Calculate location accuracy
|
# ENHANCED DEBUG: Calculate location accuracy with detailed logging
|
||||||
print(f"\n🎯 CALCULATING LOCATION ACCURACY...")
|
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
|
location_accuracy = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
location_accuracy = calculate_location_accuracy_enhanced(
|
# Check if we have the required data
|
||||||
qr_address=qr_code.location_address,
|
if not qr_code.location_address:
|
||||||
checkin_address=location_data['address'],
|
print(f"❌ QR code location_address is empty or None")
|
||||||
checkin_lat=location_data['latitude'],
|
print(f" QR Code location_address value: '{qr_code.location_address}'")
|
||||||
checkin_lng=location_data['longitude']
|
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']}'")
|
||||||
if location_accuracy is not None:
|
print(f" Check-in coords: {location_data['latitude']}, {location_data['longitude']}")
|
||||||
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})")
|
|
||||||
else:
|
else:
|
||||||
print(f"⚠️ Could not calculate location accuracy")
|
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'],
|
||||||
|
checkin_lat=location_data['latitude'],
|
||||||
|
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 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 - calculation returned None")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error in location accuracy calculation: {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:
|
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.add(attendance)
|
||||||
db.session.commit()
|
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(
|
today_checkin_count = AttendanceData.query.filter_by(
|
||||||
qr_code_id=qr_code.id,
|
qr_code_id=qr_code.id,
|
||||||
employee_id=employee_id.upper(),
|
employee_id=employee_id.upper(),
|
||||||
@@ -3256,13 +3273,16 @@ def qr_checkin(qr_url):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Database error: {e}")
|
print(f"❌ Database error: {e}")
|
||||||
|
import traceback
|
||||||
|
print(f"❌ Full traceback: {traceback.format_exc()}")
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
logger_handler.log_database_error('checkin_save', e)
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Database error occurred.'
|
'message': 'Database error occurred.'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
# ENHANCED: Return success response with sequence information
|
# Return success response with sequence information
|
||||||
response_data = {
|
response_data = {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': f'Check-in successful! {checkin_sequence_text} for today.',
|
'message': f'Check-in successful! {checkin_sequence_text} for today.',
|
||||||
@@ -4772,8 +4792,8 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
# Initialize database and logging
|
# Initialize database and logging
|
||||||
create_tables()
|
create_tables()
|
||||||
update_existing_qr_codes()
|
# update_existing_qr_codes()
|
||||||
add_qr_customization_columns()
|
# add_qr_customization_columns()
|
||||||
|
|
||||||
# Log application startup
|
# Log application startup
|
||||||
logger_handler.logger.info("QR Attendance Management System started successfully")
|
logger_handler.logger.info("QR Attendance Management System started successfully")
|
||||||
|
|||||||
Reference in New Issue
Block a user