# flask_api_v2.py (어제 코드 기반으로 수정)
from flask import Flask, request, jsonify
import joblib
import numpy as np
import logging # 로깅 추가
app = Flask(__name__)
# 로깅 설정 (간단 설정)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')
# 모델 로드
try:
model = joblib.load('iris_binary_model.pkl')
app.logger.info("모델 로드 성공: iris_binary_model.pkl")
# scaler = joblib.load('iris_scaler.pkl')
except FileNotFoundError:
model = None
# scaler = None
app.logger.error("저장된 모델 파일을 찾을 수 없습니다. 'train_model.py'를 먼저 실행하세요.")
except Exception as e:
model = None
# scaler = None
app.logger.error(f"모델 로드 중 오류 발생: {e}")
MODEL_VERSION = "1.0.0" # 모델 버전 정보
@app.route('/')
def home():
return f"Iris Binary Classification API (v{MODEL_VERSION})"
@app.route('/health', methods=['GET']) # 헬스 체크 엔드포인트 추가
def health_check():
if model:
return jsonify({'status': 'ok', 'message': 'API is healthy and model is loaded.'}), 200
else:
return jsonify({'status': 'error', 'message': 'API is unhealthy, model not loaded.'}), 500
@app.route('/predict_iris', methods=['POST'])
def predict_iris():
if model is None:
app.logger.error("'/predict_iris' 호출 시 모델이 로드되지 않음.")
return jsonify({'error_type': 'ModelNotLoadedError', 'message': '모델이 로드되지 않았습니다. 서버 로그를 확인하세요.'}), 500
if not request.is_json:
app.logger.warning("'/predict_iris' 호출 시 JSON 형식이 아닌 요청 받음.")
return jsonify({'error_type': 'InvalidContentTypeError', 'message': 'Request Content-Type must be application/json'}), 400
try:
data = request.get_json()
app.logger.info(f"요청 데이터: {data}")
# --- 입력 데이터 유효성 검사 시작 ---
if 'features' not in data:
app.logger.warning("요청 데이터에 'features' 키가 없음.")
return jsonify({'error_type': 'MissingFieldError', 'message': "'features' 필드가 요청에 포함되어야 합니다."}), 400
features = data['features']
if not isinstance(features, list):
app.logger.warning("'features' 필드가 리스트 타입이 아님.")
return jsonify({'error_type': 'InvalidInputTypeError', 'message': "'features' 필드는 리스트여야 합니다."}), 400
if len(features) != 4:
app.logger.warning(f"'features' 리스트 길이가 4가 아님: {len(features)}")
return jsonify({'error_type': 'InvalidInputLengthError', 'message': f"'features' 리스트는 4개의 요소를 가져야 합니다. (현재: {len(features)}개)"}), 400
if not all(isinstance(f, (int, float)) for f in features):
app.logger.warning("'features' 리스트에 숫자가 아닌 값이 포함됨.")
return jsonify({'error_type': 'InvalidFeatureTypeError', 'message': "'features' 리스트의 모든 요소는 숫자여야 합니다."}), 400
# --- 입력 데이터 유효성 검사 끝 ---
input_array = np.array(features).reshape(1, -1)
# if scaler:
# input_array_scaled = scaler.transform(input_array)
# prediction_proba = model.predict_proba(input_array_scaled)[0]
# prediction = model.predict(input_array_scaled)[0]
# else:
prediction_proba = model.predict_proba(input_array)[0]
prediction = model.predict(input_array)[0]
class_names = ['setosa', 'versicolor']
response_data = {
'input_features': features,
'predicted_class_id': int(prediction),
'predicted_class_name': class_names[int(prediction)],
'probabilities': {'setosa': round(float(prediction_proba[0]), 4),
'versicolor': round(float(prediction_proba[1]), 4)},
'model_version': MODEL_VERSION
}
app.logger.info(f"예측 결과: {response_data}")
return jsonify(response_data), 200
except Exception as e:
app.logger.error(f"예측 중 예외 발생: {str(e)}", exc_info=True) # exc_info=True로 스택 트레이스 로깅
return jsonify({'error_type': 'InternalPredictionError', 'message': f'예측 중 내부 오류가 발생했습니다: {str(e)}'}), 500
if __name__ == '__main__':
# 운영 환경에서는 Gunicorn, uWSGI 같은 WSGI 서버 사용 권장
app.run(debug=False, host='0.0.0.0', port=5001) # debug=False로 변경