diff --git a/app.py b/app.py index edf6700..3af0987 100644 --- a/app.py +++ b/app.py @@ -2298,7 +2298,7 @@ def toggle_qr_status_api(qr_id): @app.route('/attendance') # @admin_required def attendance_report(): - """Safe attendance report with backward compatibility for location_accuracy""" + """Safe attendance report with backward compatibility for location_accuracy and fixed datetime handling""" try: print("š Loading attendance report...") @@ -2393,13 +2393,15 @@ def attendance_report(): print(f"ā Found {len(attendance_records)} attendance records") - # Process records to add calculated fields + # FIXED: Process records to add calculated fields with proper datetime handling processed_records = [] for record in attendance_records: # Safe attribute access with fallbacks location_accuracy = getattr(record, 'location_accuracy', None) gps_accuracy = getattr(record, 'gps_accuracy', None) qr_address = getattr(record, 'qr_address', None) + + # Handle location accuracy for address display logic if location_accuracy is not None and location_accuracy != "None": try: accuracy_value = float(location_accuracy) if isinstance(location_accuracy, str) else location_accuracy @@ -2408,11 +2410,47 @@ def attendance_report(): checked_in_address = getattr(record, 'checked_in_address', None) else: checked_in_address = getattr(record, 'checked_in_address', None) + + # CRITICAL FIX: Properly handle check_in_time formatting + check_in_time_value = record.check_in_time + + # Handle different possible types for check_in_time + if isinstance(check_in_time_value, timedelta): + # Convert timedelta to time object + total_seconds = int(check_in_time_value.total_seconds()) + hours = total_seconds // 3600 + minutes = (total_seconds % 3600) // 60 + seconds = total_seconds % 60 + formatted_time = time(hours % 24, minutes, seconds) + print(f"ā ļø Converted timedelta to time: {check_in_time_value} -> {formatted_time}") + elif isinstance(check_in_time_value, time): + # Already a time object, use as-is + formatted_time = check_in_time_value + elif isinstance(check_in_time_value, datetime): + # Extract time component from datetime + formatted_time = check_in_time_value.time() + elif isinstance(check_in_time_value, str): + # Try to parse string to time + try: + formatted_time = datetime.strptime(check_in_time_value, '%H:%M:%S').time() + except ValueError: + try: + formatted_time = datetime.strptime(check_in_time_value, '%H:%M').time() + except ValueError: + # Fallback to current time if parsing fails + formatted_time = datetime.now().time() + print(f"ā ļø Could not parse time string: {check_in_time_value}, using current time") + else: + # Fallback to current time for any other type + formatted_time = datetime.now().time() + print(f"ā ļø Unexpected check_in_time type: {type(check_in_time_value)}, using current time") + + # Create the record dictionary with properly formatted time record_dict = { 'id': record.id, 'employee_id': record.employee_id, 'check_in_date': record.check_in_date, - 'check_in_time': record.check_in_time, + 'check_in_time': formatted_time, # Now guaranteed to be a time object 'location_name': record.location_name, 'location_event': getattr(record, 'location_event', ''), 'qr_address': qr_address or 'Not available', diff --git a/calculate_location_accuracy.py b/calculate_location_accuracy.py deleted file mode 100644 index ad11c58..0000000 --- a/calculate_location_accuracy.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/env python3 -""" -Location Accuracy Calculator Script for Existing Database Records - -This script calculates location accuracy for all existing attendance records -in the database where location_accuracy is NULL or needs recalculation. - -Usage: - python calculate_location_accuracy.py [options] - -Options: - --dry-run Show what would be updated without making changes - --force-recalc Recalculate accuracy for all records (even existing ones) - --batch-size N Process records in batches of N (default: 100) - --specific-date Process only records from specific date (YYYY-MM-DD) - --help Show this help message - -Author: Attendance System Location Enhancement -Version: 1.0 -""" - -import sys -import os -import argparse -from datetime import datetime, date -import time - -# Add your app directory to path for imports -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -# Import your Flask app and models -try: - from app import app, db, AttendanceData, QRCode - from app import ( - calculate_location_accuracy_enhanced, - get_location_accuracy_level_enhanced, - get_coordinates_from_address, - calculate_distance_miles - ) -except ImportError as e: - print(f"ā Error importing app modules: {e}") - print("Make sure this script is in the same directory as your app.py file") - sys.exit(1) - -# Configuration -DEFAULT_BATCH_SIZE = 100 -GEOCODING_DELAY = 0.1 # Delay between geocoding requests to avoid rate limits - -class LocationAccuracyCalculator: - """Main class for calculating location accuracy for existing records""" - - def __init__(self, dry_run=False, force_recalc=False, batch_size=DEFAULT_BATCH_SIZE): - self.dry_run = dry_run - self.force_recalc = force_recalc - self.batch_size = batch_size - self.stats = { - 'total_records': 0, - 'processed': 0, - 'updated': 0, - 'skipped': 0, - 'errors': 0, - 'accuracy_calculated': 0, - 'accuracy_failed': 0 - } - - def run(self, specific_date=None): - """Main execution method""" - print("š LOCATION ACCURACY CALCULATOR - STARTING") - print("=" * 60) - print(f"Mode: {'DRY RUN' if self.dry_run else 'LIVE UPDATE'}") - print(f"Force Recalculation: {'YES' if self.force_recalc else 'NO'}") - print(f"Batch Size: {self.batch_size}") - if specific_date: - print(f"Target Date: {specific_date}") - print("=" * 60) - - with app.app_context(): - try: - # Get records to process - records = self._get_records_to_process(specific_date) - self.stats['total_records'] = len(records) - - if not records: - print("ā¹ļø No records found to process") - return - - print(f"š Found {len(records)} records to process") - - # Process records in batches - self._process_records_in_batches(records) - - # Final database commit to ensure all changes are saved - if not self.dry_run: - try: - db.session.commit() - print(f"\nš¾ Final database commit completed successfully") - except Exception as e: - print(f"ā Final commit error: {e}") - db.session.rollback() - - # Print final statistics - self._print_final_stats() - - except Exception as e: - print(f"ā Fatal error: {e}") - import traceback - traceback.print_exc() - - def _get_records_to_process(self, specific_date=None): - """Get attendance records that need location accuracy calculation""" - query = db.session.query(AttendanceData).join(QRCode) - - if specific_date: - query = query.filter(AttendanceData.check_in_date == specific_date) - - if not self.force_recalc: - # Only get records where location_accuracy is NULL - query = query.filter(AttendanceData.location_accuracy.is_(None)) - - # Order by date and time for consistent processing - query = query.order_by( - AttendanceData.check_in_date.desc(), - AttendanceData.check_in_time.desc() - ) - - return query.all() - - def _process_records_in_batches(self, records): - """Process records in configurable batches""" - total_batches = (len(records) + self.batch_size - 1) // self.batch_size - - for batch_num in range(total_batches): - start_idx = batch_num * self.batch_size - end_idx = min(start_idx + self.batch_size, len(records)) - batch_records = records[start_idx:end_idx] - - print(f"\nš¦ BATCH {batch_num + 1}/{total_batches} - Processing records {start_idx + 1}-{end_idx}") - print("-" * 50) - - self._process_batch(batch_records, batch_num + 1) - - # Small delay between batches to avoid overwhelming external services - if batch_num < total_batches - 1: - time.sleep(0.5) - - def _process_batch(self, records, batch_num): - """Process a single batch of records""" - batch_updates = [] - records_to_update = [] - - for idx, record in enumerate(records, 1): - try: - result = self._process_single_record(record, batch_num, idx) - if result: - batch_updates.append(result) - records_to_update.append(record) - - except Exception as e: - print(f"ā Error processing record {record.id}: {e}") - self.stats['errors'] += 1 - - # Save updates to database - if batch_updates and not self.dry_run: - try: - # Update each record in the database - for i, update_data in enumerate(batch_updates): - record = records_to_update[i] - record.location_accuracy = update_data['accuracy'] - - # Mark the record as modified - db.session.merge(record) - - # Commit all changes in this batch - db.session.commit() - print(f"ā Successfully saved {len(batch_updates)} location accuracy updates to database") - - except Exception as e: - print(f"ā Database commit error: {e}") - db.session.rollback() - self.stats['errors'] += len(batch_updates) - # Reset the updated count since commit failed - self.stats['updated'] -= len(batch_updates) - self.stats['accuracy_calculated'] -= len(batch_updates) - - def _process_single_record(self, record, batch_num, record_idx): - """Process a single attendance record""" - self.stats['processed'] += 1 - - # Skip if already has accuracy and not forcing recalculation - if record.location_accuracy is not None and not self.force_recalc: - print(f"āļø [{batch_num}.{record_idx}] Record {record.id}: Already has accuracy ({record.location_accuracy:.4f} mi)") - self.stats['skipped'] += 1 - return None - - # Get QR code information - qr_code = record.qr_code - if not qr_code: - print(f"ā ļø [{batch_num}.{record_idx}] Record {record.id}: No QR code found") - self.stats['skipped'] += 1 - return None - - print(f"š [{batch_num}.{record_idx}] Processing: Employee {record.employee_id} | {record.check_in_date} | {qr_code.location}") - - # Calculate location accuracy - location_accuracy = self._calculate_accuracy_for_record(record, qr_code) - - if location_accuracy is not None: - accuracy_level = get_location_accuracy_level_enhanced(location_accuracy) - - # Always update the record object, database save happens in batch processing - if not self.dry_run: - # Update the record's location_accuracy field - record.location_accuracy = location_accuracy - print(f"ā [{batch_num}.{record_idx}] Accuracy set: {location_accuracy:.4f} miles ({accuracy_level}) - Will save to database") - else: - print(f"ā [{batch_num}.{record_idx}] Accuracy calculated: {location_accuracy:.4f} miles ({accuracy_level}) - DRY RUN") - - self.stats['updated'] += 1 - self.stats['accuracy_calculated'] += 1 - - return { - 'record_id': record.id, - 'accuracy': location_accuracy, - 'level': accuracy_level - } - else: - print(f"ā ļø [{batch_num}.{record_idx}] Could not calculate accuracy") - self.stats['accuracy_failed'] += 1 - return None - - def _calculate_accuracy_for_record(self, record, qr_code): - """Calculate location accuracy for a specific record""" - try: - # Add small delay to avoid overwhelming geocoding services - time.sleep(GEOCODING_DELAY) - - return calculate_location_accuracy_enhanced( - qr_address=qr_code.location_address, - checkin_address=record.address, - checkin_lat=record.latitude, - checkin_lng=record.longitude - ) - except Exception as e: - print(f" ā Calculation error: {e}") - return None - - def _print_final_stats(self): - """Print comprehensive final statistics""" - print("\n" + "=" * 60) - print("š FINAL STATISTICS") - print("=" * 60) - print(f"Total Records Found: {self.stats['total_records']:,}") - print(f"Records Processed: {self.stats['processed']:,}") - print(f"Records Updated: {self.stats['updated']:,}") - print(f"Records Skipped: {self.stats['skipped']:,}") - print(f"Errors Encountered: {self.stats['errors']:,}") - print("-" * 40) - print(f"Accuracy Calculated: {self.stats['accuracy_calculated']:,}") - print(f"Accuracy Failed: {self.stats['accuracy_failed']:,}") - - if self.stats['processed'] > 0: - success_rate = (self.stats['accuracy_calculated'] / self.stats['processed']) * 100 - print(f"Success Rate: {success_rate:.1f}%") - - print("=" * 60) - - if self.dry_run: - print("š DRY RUN COMPLETED - No changes were made to the database") - else: - print("ā LIVE UPDATE COMPLETED - Database has been updated") - - # Verify database updates - self._verify_database_updates() - - print("=" * 60) - - def _verify_database_updates(self): - """Verify that the database updates were actually saved""" - try: - print("\nš VERIFYING DATABASE UPDATES...") - - # Count records with location_accuracy that were just updated - updated_count = db.session.query(AttendanceData).filter( - AttendanceData.location_accuracy.isnot(None) - ).count() - - print(f"š Database verification:") - print(f" Total records with location_accuracy: {updated_count:,}") - - if self.stats['updated'] > 0: - print(f" Expected updates from this run: {self.stats['updated']:,}") - - # Get a sample of recently updated records to verify - sample_records = db.session.query(AttendanceData).filter( - AttendanceData.location_accuracy.isnot(None) - ).limit(3).all() - - if sample_records: - print(f" Sample updated records:") - for record in sample_records: - print(f" ⢠Record {record.id}: {record.location_accuracy:.4f} miles") - else: - print(" ā ļø No sample records found - verification inconclusive") - - print("ā Database verification completed") - - except Exception as e: - print(f"ā Error during database verification: {e}") - - -def main(): - """Main entry point with command line argument parsing""" - parser = argparse.ArgumentParser( - description="Calculate location accuracy for existing attendance records", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - python calculate_location_accuracy.py --dry-run - python calculate_location_accuracy.py --force-recalc --batch-size 50 - python calculate_location_accuracy.py --specific-date 2025-01-15 - python calculate_location_accuracy.py --dry-run --specific-date 2025-01-15 - """ - ) - - parser.add_argument( - '--dry-run', - action='store_true', - help='Show what would be updated without making changes' - ) - - parser.add_argument( - '--force-recalc', - action='store_true', - help='Recalculate accuracy for all records (even existing ones)' - ) - - parser.add_argument( - '--batch-size', - type=int, - default=DEFAULT_BATCH_SIZE, - help=f'Process records in batches of N (default: {DEFAULT_BATCH_SIZE})' - ) - - parser.add_argument( - '--specific-date', - type=str, - help='Process only records from specific date (YYYY-MM-DD format)' - ) - - args = parser.parse_args() - - # Validate specific date if provided - specific_date = None - if args.specific_date: - try: - specific_date = datetime.strptime(args.specific_date, '%Y-%m-%d').date() - except ValueError: - print("ā Invalid date format. Use YYYY-MM-DD (e.g., 2025-01-15)") - sys.exit(1) - - # Validate batch size - if args.batch_size < 1: - print("ā Batch size must be at least 1") - sys.exit(1) - - # Create and run calculator - calculator = LocationAccuracyCalculator( - dry_run=args.dry_run, - force_recalc=args.force_recalc, - batch_size=args.batch_size - ) - - try: - calculator.run(specific_date=specific_date) - except KeyboardInterrupt: - print("\nā¹ļø Operation cancelled by user") - sys.exit(0) - except Exception as e: - print(f"\nā Unexpected error: {e}") - sys.exit(1) - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/location_accuracy_utils.py b/location_accuracy_utils.py deleted file mode 100644 index fb0fa3b..0000000 --- a/location_accuracy_utils.py +++ /dev/null @@ -1,372 +0,0 @@ -#!/usr/bin/env python3 -""" -Location Accuracy Database Utilities - -Additional utility functions for managing location accuracy data -in your attendance system database. - -Usage: - python location_accuracy_utils.py [command] [options] - -Commands: - check-schema Verify database schema for location accuracy - add-field Add location_accuracy field to attendance_data table - stats Show location accuracy statistics - export-report Export detailed location accuracy report - verify-data Verify data integrity for location accuracy - -Author: Attendance System Enhancement Team -""" - -import sys -import os -import csv -from datetime import datetime, date -import json - -# Add your app directory to path -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -try: - from app import app, db, AttendanceData, QRCode - from app import get_location_accuracy_level_enhanced - from sqlalchemy import text, inspect -except ImportError as e: - print(f"ā Error importing modules: {e}") - sys.exit(1) - -class LocationAccuracyUtils: - """Utility class for location accuracy database management""" - - def __init__(self): - self.app = app - - def check_database_schema(self): - """Check if the database schema supports location accuracy""" - print("š CHECKING DATABASE SCHEMA") - print("=" * 50) - - with self.app.app_context(): - inspector = inspect(db.engine) - - # Check attendance_data table - try: - columns = inspector.get_columns('attendance_data') - column_names = [col['name'] for col in columns] - - print("š Attendance Data Table Columns:") - required_fields = [ - 'id', 'qr_code_id', 'employee_id', 'check_in_date', 'check_in_time', - 'latitude', 'longitude', 'address', 'location_accuracy' - ] - - for field in required_fields: - status = "ā " if field in column_names else "ā" - print(f" {status} {field}") - - # Check for location_accuracy field specifically - has_accuracy_field = 'location_accuracy' in column_names - - if has_accuracy_field: - # Get field details - accuracy_col = next((col for col in columns if col['name'] == 'location_accuracy'), None) - if accuracy_col: - print(f"\nš location_accuracy field details:") - print(f" Type: {accuracy_col['type']}") - print(f" Nullable: {accuracy_col['nullable']}") - print(f" Default: {accuracy_col.get('default', 'None')}") - - return has_accuracy_field - - except Exception as e: - print(f"ā Error checking schema: {e}") - return False - - def add_location_accuracy_field(self): - """Add location_accuracy field to attendance_data table if missing""" - print("š§ ADDING LOCATION ACCURACY FIELD") - print("=" * 50) - - with self.app.app_context(): - try: - # Check if field already exists - if self.check_database_schema(): - print("ā¹ļø location_accuracy field already exists") - return True - - print("ā Adding location_accuracy field...") - - # Add the field - db.session.execute(text(""" - ALTER TABLE attendance_data - ADD COLUMN location_accuracy FLOAT - """)) - - db.session.commit() - print("ā Successfully added location_accuracy field") - - # Verify addition - if self.check_database_schema(): - print("ā Field addition verified") - return True - else: - print("ā Field addition verification failed") - return False - - except Exception as e: - print(f"ā Error adding field: {e}") - db.session.rollback() - return False - - def show_location_accuracy_stats(self): - """Show comprehensive statistics about location accuracy data""" - print("š LOCATION ACCURACY STATISTICS") - print("=" * 60) - - with self.app.app_context(): - try: - # Basic counts - total_records = db.session.query(AttendanceData).count() - records_with_location = db.session.query(AttendanceData).filter( - AttendanceData.latitude.isnot(None), - AttendanceData.longitude.isnot(None) - ).count() - - records_with_accuracy = db.session.query(AttendanceData).filter( - AttendanceData.location_accuracy.isnot(None) - ).count() - - print(f"Total Records: {total_records:,}") - print(f"Records with GPS Data: {records_with_location:,}") - print(f"Records with Accuracy: {records_with_accuracy:,}") - - if total_records > 0: - gps_percentage = (records_with_location / total_records) * 100 - accuracy_percentage = (records_with_accuracy / total_records) * 100 - print(f"GPS Coverage: {gps_percentage:.1f}%") - print(f"Accuracy Coverage: {accuracy_percentage:.1f}%") - - # Accuracy level distribution - if records_with_accuracy > 0: - print("\nš ACCURACY LEVEL DISTRIBUTION") - print("-" * 40) - - accuracy_records = db.session.query(AttendanceData.location_accuracy).filter( - AttendanceData.location_accuracy.isnot(None) - ).all() - - levels = {} - total_distance = 0 - - for record in accuracy_records: - accuracy = record[0] - level = get_location_accuracy_level_enhanced(accuracy) - levels[level] = levels.get(level, 0) + 1 - total_distance += accuracy - - for level in ['excellent', 'very_good', 'good', 'fair', 'poor', 'very_poor']: - count = levels.get(level, 0) - percentage = (count / records_with_accuracy) * 100 if records_with_accuracy > 0 else 0 - print(f"{level.replace('_', ' ').title():12} {count:6,} ({percentage:5.1f}%)") - - avg_accuracy = total_distance / len(accuracy_records) - print(f"\nAverage Distance: {avg_accuracy:.4f} miles") - - # Recent activity - print("\nš RECENT ACTIVITY (Last 7 Days)") - print("-" * 40) - - seven_days_ago = date.today().replace(day=date.today().day - 7) if date.today().day > 7 else date.today().replace(month=date.today().month - 1, day=date.today().day + 30 - 7) - - recent_records = db.session.query(AttendanceData).filter( - AttendanceData.check_in_date >= seven_days_ago - ).count() - - recent_with_accuracy = db.session.query(AttendanceData).filter( - AttendanceData.check_in_date >= seven_days_ago, - AttendanceData.location_accuracy.isnot(None) - ).count() - - print(f"Recent Check-ins: {recent_records:,}") - print(f"Recent with Accuracy: {recent_with_accuracy:,}") - - if recent_records > 0: - recent_percentage = (recent_with_accuracy / recent_records) * 100 - print(f"Recent Accuracy Rate: {recent_percentage:.1f}%") - - except Exception as e: - print(f"ā Error generating statistics: {e}") - - def export_location_accuracy_report(self, output_file=None): - """Export detailed location accuracy report to CSV""" - if not output_file: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - output_file = f"location_accuracy_report_{timestamp}.csv" - - print(f"š EXPORTING LOCATION ACCURACY REPORT") - print(f"Output File: {output_file}") - print("=" * 60) - - with self.app.app_context(): - try: - # Query all records with their QR code information - query = db.session.query(AttendanceData, QRCode).join( - QRCode, AttendanceData.qr_code_id == QRCode.id - ).order_by(AttendanceData.check_in_date.desc(), AttendanceData.check_in_time.desc()) - - records = query.all() - - print(f"Found {len(records)} records to export") - - # Write CSV file - with open(output_file, 'w', newline='', encoding='utf-8') as csvfile: - fieldnames = [ - 'attendance_id', 'employee_id', 'check_in_date', 'check_in_time', - 'location_name', 'qr_address', 'checkin_address', - 'checkin_latitude', 'checkin_longitude', 'gps_accuracy_meters', - 'location_accuracy_miles', 'accuracy_level', - 'has_gps_data', 'has_location_accuracy', 'device_info' - ] - - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - writer.writeheader() - - for attendance, qr_code in records: - accuracy_level = 'unknown' - if attendance.location_accuracy: - accuracy_level = get_location_accuracy_level_enhanced(attendance.location_accuracy) - - writer.writerow({ - 'attendance_id': attendance.id, - 'employee_id': attendance.employee_id, - 'check_in_date': attendance.check_in_date.isoformat(), - 'check_in_time': attendance.check_in_time.isoformat(), - 'location_name': attendance.location_name, - 'qr_address': qr_code.location_address, - 'checkin_address': attendance.address or 'Not captured', - 'checkin_latitude': attendance.latitude, - 'checkin_longitude': attendance.longitude, - 'gps_accuracy_meters': attendance.accuracy, - 'location_accuracy_miles': attendance.location_accuracy, - 'accuracy_level': accuracy_level, - 'has_gps_data': attendance.latitude is not None and attendance.longitude is not None, - 'has_location_accuracy': attendance.location_accuracy is not None, - 'device_info': attendance.device_info or 'Unknown' - }) - - print(f"ā Report exported successfully to {output_file}") - return output_file - - except Exception as e: - print(f"ā Error exporting report: {e}") - return None - - def verify_data_integrity(self): - """Verify data integrity for location accuracy calculations""" - print("š VERIFYING DATA INTEGRITY") - print("=" * 50) - - with self.app.app_context(): - try: - issues = [] - - # Check for records with coordinates but no address - no_address_with_coords = db.session.query(AttendanceData).filter( - AttendanceData.latitude.isnot(None), - AttendanceData.longitude.isnot(None), - AttendanceData.address.is_(None) - ).count() - - if no_address_with_coords > 0: - issues.append(f"{no_address_with_coords} records have GPS coordinates but no address") - - # Check for invalid coordinates - invalid_coords = db.session.query(AttendanceData).filter( - db.or_( - AttendanceData.latitude < -90, - AttendanceData.latitude > 90, - AttendanceData.longitude < -180, - AttendanceData.longitude > 180 - ) - ).count() - - if invalid_coords > 0: - issues.append(f"{invalid_coords} records have invalid GPS coordinates") - - # Check for QR codes without addresses - qr_no_address = db.session.query(QRCode).filter( - db.or_( - QRCode.location_address.is_(None), - QRCode.location_address == '' - ) - ).count() - - if qr_no_address > 0: - issues.append(f"{qr_no_address} QR codes have no address defined") - - # Check for impossible accuracy values - extreme_accuracy = db.session.query(AttendanceData).filter( - db.or_( - AttendanceData.location_accuracy < 0, - AttendanceData.location_accuracy > 1000 # More than 1000 miles seems extreme - ) - ).count() - - if extreme_accuracy > 0: - issues.append(f"{extreme_accuracy} records have extreme location accuracy values") - - # Report results - if issues: - print("ā ļø Data integrity issues found:") - for issue in issues: - print(f" ⢠{issue}") - else: - print("ā No data integrity issues found") - - return len(issues) == 0 - - except Exception as e: - print(f"ā Error during verification: {e}") - return False - - -def main(): - """Main entry point for utility commands""" - if len(sys.argv) < 2: - print(""" -Usage: python location_accuracy_utils.py [command] - -Commands: - check-schema Check database schema - add-field Add location_accuracy field - stats Show statistics - export-report Export CSV report - verify-data Verify data integrity - help Show this help - """) - sys.exit(1) - - command = sys.argv[1].lower() - utils = LocationAccuracyUtils() - - if command == 'check-schema': - utils.check_database_schema() - elif command == 'add-field': - utils.add_location_accuracy_field() - elif command == 'stats': - utils.show_location_accuracy_stats() - elif command == 'export-report': - output_file = sys.argv[2] if len(sys.argv) > 2 else None - utils.export_location_accuracy_report(output_file) - elif command == 'verify-data': - utils.verify_data_integrity() - elif command == 'help': - print(__doc__) - else: - print(f"ā Unknown command: {command}") - print("Use 'help' to see available commands") - sys.exit(1) - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/static/css/attendance.css b/static/css/attendance.css index f03adca..9aaeaa7 100644 --- a/static/css/attendance.css +++ b/static/css/attendance.css @@ -1024,7 +1024,7 @@ border: 1px solid rgba(220, 38, 38, 0.3); } -.location-accuracy-badge.accuracy-unaccurate { +.location-accuracy-badge.accuracy-inaccurate { background: var(--danger-light); color: var(--danger-color); border: 1px solid rgba(220, 38, 38, 0.3); diff --git a/static/js/attendance_report.js b/static/js/attendance_report.js index 5a1ecae..4ac9fd7 100644 --- a/static/js/attendance_report.js +++ b/static/js/attendance_report.js @@ -599,7 +599,7 @@ function extractLocationAccuracy(cell) { function extractLocationAccuracyLevel(cell) { const text = cell.textContent; if (text.includes("excellent") || text.includes("good")) return "accurate"; - if (text.includes("fair") || text.includes("poor")) return "unaccurate"; + if (text.includes("fair") || text.includes("poor")) return "inaccurate"; return "unknown"; } @@ -793,7 +793,7 @@ function updateFilterStats() { function getAccuracyLevelColor(level) { const colors = { accurate: "#059669", // green - unaccurate: "#dc2626", // red + inaccurate: "#dc2626", // red unknown: "#6b7280", // gray }; return colors[level] || colors["unknown"]; diff --git a/templates/attendance_report.html b/templates/attendance_report.html index df64a94..fb981fd 100644 --- a/templates/attendance_report.html +++ b/templates/attendance_report.html @@ -188,7 +188,25 @@