import csv import io import json import os import uuid from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from rest_framework.authentication import SessionAuthentication from rest_framework.decorators import api_view, authentication_classes, permission_classes from rest_framework.permissions import IsAdminUser from rest_framework_simplejwt.authentication import JWTAuthentication from namecreate.models import TrainingJob from namecreate.tasks import train_model_task def _parse_json_data(request): """ JSON body'den veriyi çeker. Beklenen format: { "features": [[1.2, 3.4, 5.6, 7.8], [2.1, 4.3, 6.5, 8.7], ...], "labels": [0, 1, 2, ...] } """ body = json.loads(request.body) features = body.get('features') labels = body.get('labels') if not features or not labels: raise ValueError("'features' ve 'labels' alanları zorunludur.") if len(features) != len(labels): raise ValueError("'features' ve 'labels' eleman sayısı eşit olmalıdır.") if len(features) < 10: raise ValueError("En az 10 eğitim örneği gönderiniz.") # Tip güvenliği: tüm değerler sayısal olmalı for i, row in enumerate(features): if not all(isinstance(v, (int, float)) for v in row): raise ValueError(f"features[{i}] içinde sayısal olmayan değer var.") if not all(isinstance(v, (int, float)) for v in labels): raise ValueError("labels listesi sadece sayısal değer içermelidir.") return features, [int(v) for v in labels] def _parse_csv_data(file): """ CSV dosyasından veriyi çeker. Beklenen format — son sütun label, geri kalanlar feature: 1.2,3.4,5.6,7.8,0 2.1,4.3,6.5,8.7,1 ... Header satırı varsa otomatik atlanır. """ content = file.read().decode('utf-8') reader = csv.reader(io.StringIO(content)) features, labels = [], [] for lineno, row in enumerate(reader, start=1): if not row: continue # Header satırını atla try: values = [float(v) for v in row] except ValueError: if lineno == 1: continue # Başlık satırı raise ValueError(f"CSV satır {lineno}: sayısal olmayan değer.") if len(values) < 2: raise ValueError(f"CSV satır {lineno}: en az 2 sütun (özellik + etiket) gerekli.") features.append(values[:-1]) labels.append(int(values[-1])) if len(features) < 10: raise ValueError("En az 10 eğitim örneği gönderiniz.") return features, labels @csrf_exempt def train_and_export_model(request): """ Modeli arka planda eğitmek için Celery task'ı başlatır. Veri gönderme yöntemleri: 1) JSON body: POST /api/v1/ml/train-model/ Content-Type: application/json { "features": [[1.2, 3.4, 5.6, 7.8], ...], "labels": [0, 1, 2, ...] } 2) CSV dosyası: POST /api/v1/ml/train-model/ Content-Type: multipart/form-data Form alanı: data= (Son sütun label, geri kalanlar feature) 3) Veri göndermezseniz yerleşik Iris demo verisi kullanılır. """ if request.method != 'POST': return JsonResponse({'error': 'Sadece POST destekleniyor.'}, status=405) try: features, labels = None, None source = 'demo' # --- Yöntem 1: JSON body --- ct = request.content_type or '' if 'application/json' in ct and request.body: features, labels = _parse_json_data(request) source = 'json' # --- Yöntem 2: CSV dosyası --- elif 'data' in request.FILES: features, labels = _parse_csv_data(request.FILES['data']) source = 'csv' # --- Yöntem 3: Demo (Iris) --- # features ve labels None kalır, task default veriyi kullanır feature_count = len(features[0]) if features else None sample_count = len(features) if features else None task_id = str(uuid.uuid4()) TrainingJob.objects.create( task_id=task_id, status='pending', features=features, labels=labels, feature_count=feature_count, sample_count=sample_count, ) celery_task = train_model_task.delay(task_id) return JsonResponse({ 'status': 'queued', 'message': 'Model eğitim görevi başlatıldı.', 'task_id': task_id, 'celery_task_id': celery_task.id, 'data_source': source, 'sample_count': sample_count, 'feature_count': feature_count, }) except (ValueError, KeyError) as e: return JsonResponse({'status': 'error', 'message': str(e)}, status=400) except Exception as e: return JsonResponse({'status': 'error', 'message': str(e)}, status=500) def get_training_status(request): """Task'ın durumunu sorgular.""" task_id = request.GET.get('task_id') if not task_id: return JsonResponse({ "error": "task_id gerekli" }, status=400) try: job = TrainingJob.objects.get(task_id=task_id) response = { "task_id": job.task_id, "status": job.status, "created_at": job.created_at.isoformat() if job.created_at else None, "started_at": job.started_at.isoformat() if job.started_at else None, "completed_at": job.completed_at.isoformat() if job.completed_at else None, } # Task tamamlandığında metrikleri ekle if job.status == 'completed': response.update({ "model_version": job.model_version.isoformat(), "metrics": { "accuracy": job.accuracy, "precision": job.precision, "recall": job.recall, "f1_score": job.f1_score, } }) # Task başarısız olmuşsa hata mesajı ekle if job.status == 'failed': response["error_message"] = job.error_message return JsonResponse(response) except TrainingJob.DoesNotExist: return JsonResponse({ "error": "Task bulunamadı" }, status=404) @api_view(['GET']) @authentication_classes([SessionAuthentication, JWTAuthentication]) @permission_classes([IsAdminUser]) def list_training_jobs(request): """TrainingJob kayıtlarını listeler.""" status_filter = request.GET.get('status') limit = request.GET.get('limit', '50') try: limit = max(1, min(int(limit), 200)) except ValueError: return JsonResponse({"error": "limit sayısal olmalıdır"}, status=400) jobs = TrainingJob.objects.all().order_by('-created_at') if status_filter: jobs = jobs.filter(status=status_filter) items = [] for job in jobs[:limit]: model_exists = bool(job.model_path and os.path.exists(job.model_path)) items.append({ 'id': job.pk, 'task_id': job.task_id, 'status': job.status, 'model_type': job.model_type, 'created_at': job.created_at.isoformat() if job.created_at else None, 'started_at': job.started_at.isoformat() if job.started_at else None, 'completed_at': job.completed_at.isoformat() if job.completed_at else None, 'sample_count': job.sample_count, 'feature_count': job.feature_count, 'model_path': job.model_path, 'model_exists': model_exists, 'error_message': job.error_message, }) return JsonResponse({ 'count': len(items), 'results': items, })