File size: 12,960 Bytes
a420b85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
"""

Command-line interface for AutoAttendance system.

Provides a professional CLI with subcommands.

"""

import argparse
import sys
import os
from pathlib import Path
from datetime import datetime

from logger import setup_logger, get_logger, log_system_event
from database import AttendanceDatabase
from face_recognition import FaceRecognitionModule
from config import (
    DATABASE_PATH,
    FACE_DATA_DIR,
    ATTENDANCE_DIR,
    MODELS_DIR,
    CAMERA_ID,
)

# Setup logger
setup_logger()
logger = get_logger()


class Colors:
    """ANSI color codes for terminal output."""
    HEADER = "\033[95m"
    BLUE = "\033[94m"
    CYAN = "\033[96m"
    GREEN = "\033[92m"
    WARNING = "\033[93m"
    RED = "\033[91m"
    ENDC = "\033[0m"
    BOLD = "\033[1m"


def print_header(text: str) -> None:
    """Print a formatted header."""
    print(f"\n{Colors.HEADER}{'=' * 60}{Colors.ENDC}")
    print(f"{Colors.BOLD}{text:^60}{Colors.ENDC}")
    print(f"{Colors.HEADER}{'=' * 60}{Colors.ENDC}\n")


def print_success(text: str) -> None:
    """Print success message."""
    print(f"{Colors.GREEN}{text}{Colors.ENDC}")


def print_error(text: str) -> None:
    """Print error message."""
    print(f"{Colors.RED}{text}{Colors.ENDC}")


def print_warning(text: str) -> None:
    """Print warning message."""
    print(f"{Colors.WARNING}{text}{Colors.ENDC}")


def print_info(text: str) -> None:
    """Print info message."""
    print(f"{Colors.CYAN}{text}{Colors.ENDC}")


def cmd_collect(args) -> int:
    """Collect face samples for training."""
    from data_collection import DataCollectionModule
    
    print_header("Face Data Collection")
    
    collection = DataCollectionModule()
    person_name = args.name
    
    if not person_name:
        person_name = input(f"{Colors.CYAN}Enter person's name: {Colors.ENDC}").strip()
    
    if not person_name:
        print_error("Name cannot be empty")
        return 1
    
    num_samples = args.samples or 80
    print_info(f"Capturing {num_samples} samples for '{person_name}'...")
    
    collection.capture_face_samples(person_name, num_samples=num_samples, camera_id=CAMERA_ID)
    
    print_success(f"Data collection complete for {person_name}")
    log_system_event("info", "Face data collected", person=person_name, samples=num_samples)
    return 0


def cmd_train(args) -> int:
    """Train the face recognition model."""
    print_header("Model Training")
    
    recognizer = FaceRecognitionModule()
    
    print_info("Registering face embeddings...")
    people_count, embedding_count = recognizer.train_from_directory()
    
    if embedding_count == 0:
        print_error("No usable face images found!")
        print_info("Run 'python cli.py collect --name <name>' first")
        return 1
    
    print_success(f"Training complete!")
    print(f"  People registered: {Colors.GREEN}{people_count}{Colors.ENDC}")
    print(f"  Embeddings created: {Colors.GREEN}{embedding_count}{Colors.ENDC}")
    
    log_system_event("info", "Model trained", people=people_count, embeddings=embedding_count)
    return 0


def cmd_run(args) -> int:
    """Run the attendance system."""
    from main import AttendanceSystem
    
    print_header("Starting Attendance System")
    
    system = AttendanceSystem()
    
    print_info("Press 'q' to quit, 's' to export report")
    print_info(f"Camera ID: {CAMERA_ID}")
    print_info(f"Database: {DATABASE_PATH}")
    
    system.run()
    
    print_success("System shutdown complete")
    log_system_event("info", "System stopped")
    return 0


def cmd_status(args) -> int:
    """Show system status and statistics."""
    print_header("System Status")
    
    db = AttendanceDatabase()
    
    # Student statistics
    students = db.list_students()
    print(f"{Colors.BOLD}Students:{Colors.ENDC} {len(students)}")
    
    for student in students:
        name = student.get("name", "Unknown")
        embeddings = student.get("embedding_count", 0)
        status = student.get("status", "unknown")
        status_color = Colors.GREEN if status == "active" else Colors.WARNING
        print(f"  • {name}: {embeddings} embeddings [{status_color}{status}{Colors.ENDC}]")
    
    # Today's attendance
    today = datetime.now().strftime("%Y-%m-%d")
    attendance = db.list_attendance(date=today, limit=1000)
    print(f"\n{Colors.BOLD}Today's Attendance ({today}):{Colors.ENDC} {len(attendance)}")
    
    for record in attendance:
        name = record.get("student_name", "Unknown")
        time = record.get("time", "N/A")
        print(f"  • {name} at {time}")
    
    # Recent alerts
    alerts = db.list_alerts(limit=5)
    print(f"\n{Colors.BOLD}Recent Alerts:{Colors.ENDC} {len(alerts)}")
    
    if alerts:
        for alert in alerts:
            alert_type = alert.get("alert_type", "unknown")
            message = alert.get("message", "")[:50]
            created = alert.get("created_at", "")[:19]
            print(f"  • [{alert_type}] {message}... ({created})")
    else:
        print("  No recent alerts")
    
    # Storage info
    db_path = Path(DATABASE_PATH)
    if db_path.exists():
        size_mb = db_path.stat().st_size / (1024 * 1024)
        print(f"\n{Colors.BOLD}Database Size:{Colors.ENDC} {size_mb:.2f} MB")
    
    log_system_event("info", "Status checked")
    return 0


def cmd_export(args) -> int:
    """Export attendance data."""
    print_header("Exporting Attendance Data")
    
    db = AttendanceDatabase()
    date = args.date or datetime.now().strftime("%Y-%m-%d")
    
    attendance = db.list_attendance(date=date, limit=10000)
    
    if not attendance:
        print_warning(f"No attendance records for {date}")
        return 1
    
    # Export to CSV
    import pandas as pd
    output_dir = Path(ATTENDANCE_DIR)
    output_dir.mkdir(parents=True, exist_ok=True)
    
    filename = f"attendance_export_{date}.csv"
    filepath = output_dir / filename
    
    df = pd.DataFrame(attendance)
    df.to_csv(filepath, index=False)
    
    print_success(f"Exported {len(attendance)} records")
    print(f"  File: {filepath}")
    
    log_system_event("info", "Attendance exported", date=date, records=len(attendance))
    return 0


def cmd_api(args) -> int:
    """Start the API server."""
    import uvicorn
    
    print_header("Starting API Server")
    
    host = args.host or "0.0.0.0"
    port = args.port or 8000
    
    print_info(f"Server starting at http://{host}:{port}")
    print_info("Dashboard: http://localhost:8000/")
    print_info("API docs: http://localhost:8000/docs")
    print_info("Press Ctrl+C to stop")
    
    log_system_event("info", "API server starting", host=host, port=port)
    
    uvicorn.run(
        "api:app",
        host=host,
        port=port,
        reload=args.reload,
        log_level="info"
    )
    
    return 0


def cmd_setup(args) -> int:
    """Run system setup wizard."""
    import subprocess
    
    print_header("Running Setup")
    
    try:
        result = subprocess.run([sys.executable, "setup.py"], capture_output=False)
        return result.returncode
    except Exception as e:
        print_error(f"Setup failed: {e}")
        return 1


def cmd_test(args) -> int:
    """Run system diagnostics."""
    print_header("System Diagnostics")
    
    errors = []
    warnings = []
    
    # Check Python version
    print_info("Checking Python version...")
    version = sys.version_info
    if version.major < 3 or (version.major == 3 and version.minor < 8):
        errors.append("Python 3.8+ required")
    else:
        print_success(f"Python {version.major}.{version.minor}.{version.micro}")
    
    # Check dependencies
    print_info("Checking dependencies...")
    required = {
        "cv2": "opencv-python",
        "numpy": "numpy",
        "pandas": "pandas",
        "insightface": "insightface",
        "fastapi": "fastapi",
        "uvicorn": "uvicorn",
    }
    
    for import_name, package_name in required.items():
        try:
            __import__(import_name)
            print_success(f"{package_name}")
        except ImportError:
            errors.append(f"{package_name} not installed")
            print_error(f"{package_name} NOT installed")
    
    # Check directories
    print_info("Checking directories...")
    dirs = [
        ("data/faces", "Face data"),
        ("data/attendance", "Attendance records"),
        ("models", "Model storage"),
    ]
    
    for path, desc in dirs:
        if os.path.isdir(path):
            print_success(f"{desc}: {path}")
        else:
            warnings.append(f"{desc} directory missing: {path}")
            print_warning(f"{desc}: {path} (missing)")
    
    # Check database
    print_info("Checking database...")
    db_path = Path(DATABASE_PATH)
    if db_path.exists():
        print_success(f"Database exists: {db_path}")
        
        db = AttendanceDatabase()
        students = db.list_students()
        print(f"  {len(students)} registered students")
    else:
        warnings.append("Database not initialized")
        print_warning("Database not initialized (run 'python train_model.py')")
    
    # Summary
    print("\n" + "=" * 60)
    if errors:
        print(f"{Colors.RED}Errors: {len(errors)}{Colors.ENDC}")
        for e in errors:
            print(f"  • {e}")
    
    if warnings:
        print(f"{Colors.WARNING}Warnings: {len(warnings)}{Colors.ENDC}")
        for w in warnings:
            print(f"  • {w}")
    
    if not errors and not warnings:
        print_success("All checks passed!")
        return 0
    elif not errors:
        return 0
    else:
        return 1


def main():
    """Main CLI entry point."""
    parser = argparse.ArgumentParser(
        prog="autoattendance",
        description="Face Recognition Attendance System CLI",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""

Examples:

  python cli.py status              Show system status

  python cli.py collect --name John Collect face data

  python cli.py train              Train the model

  python cli.py run                Start attendance system

  python cli.py api                Start API server

  python cli.py test               Run diagnostics

        """
    )
    
    subparsers = parser.add_subparsers(dest="command", help="Available commands")
    
    # Collect command
    collect_parser = subparsers.add_parser("collect", help="Collect face samples")
    collect_parser.add_argument("--name", "-n", help="Person's name")
    collect_parser.add_argument("--samples", "-s", type=int, help="Number of samples (default: 80)")
    collect_parser.set_defaults(func=cmd_collect)
    
    # Train command
    train_parser = subparsers.add_parser("train", help="Train face recognition model")
    train_parser.set_defaults(func=cmd_train)
    
    # Run command
    run_parser = subparsers.add_parser("run", help="Run the attendance system")
    run_parser.set_defaults(func=cmd_run)
    
    # Status command
    status_parser = subparsers.add_parser("status", help="Show system status")
    status_parser.set_defaults(func=cmd_status)
    
    # Export command
    export_parser = subparsers.add_parser("export", help="Export attendance data")
    export_parser.add_argument("--date", "-d", help="Date (YYYY-MM-DD, default: today)")
    export_parser.set_defaults(func=cmd_export)
    
    # API command
    api_parser = subparsers.add_parser("api", help="Start API server")
    api_parser.add_argument("--host", default="0.0.0.0", help="Host address")
    api_parser.add_argument("--port", "-p", type=int, default=8000, help="Port number")
    api_parser.add_argument("--reload", action="store_true", help="Enable auto-reload")
    api_parser.set_defaults(func=cmd_api)
    
    # Setup command
    setup_parser = subparsers.add_parser("setup", help="Run setup wizard")
    setup_parser.set_defaults(func=cmd_setup)
    
    # Test command
    test_parser = subparsers.add_parser("test", help="Run system diagnostics")
    test_parser.set_defaults(func=cmd_test)
    
    # Parse arguments
    args = parser.parse_args()
    
    if args.command is None:
        parser.print_help()
        return 0
    
    # Execute command
    try:
        return args.func(args)
    except KeyboardInterrupt:
        print("\n\nOperation cancelled by user.")
        return 130
    except Exception as e:
        print_error(f"Error: {e}")
        logger.exception("CLI error")
        return 1


if __name__ == "__main__":
    sys.exit(main())