본문 바로가기

Flask

[Flask] Nutritiguide 백엔드 서버 코드 리뷰

안녕하세요

 

오늘은 졸업작품 중 파이썬으로 작성한

 

Flask 서버 코드를 리뷰해보려고 합니다.

 

from flask import Flask, request, jsonify
import openai
import pandas as pd
import numpy as np
from flask_cors import CORS
import math
app = Flask(__name__)
CORS(app)
openai.api_key = "비밀키"

 

먼저 상단의 기본 코드입니다.

 

 

from flask import Flask, request, jsonify: Flask

 

프레임워크에서 웹 애플리케이션을 구축하는 데 필요한 클래스와 함수를 가져옵니다.

 

import openai

 

OpenAI Python SDK를 가져옵니다. OpenAI는 자연어 처리 모델인 GPT를 다루는 서비스입니다.

 

import pandas as pd

 

데이터 조작 및 분석을 위한 pandas 라이브러리를 가져옵니다.

 

import numpy as np

 

수치 연산을 위한 numpy 라이브러리를 가져옵니다.

 

from flask_cors import CORS

 

 Flask 확장 라이브러리인 CORS(Cross-Origin Resource Sharing)를 가져와서 다른 출처에서의 요청 처리를 가능하게 합니다.

 

ChatGPT API를 일정 금액을 지불하면 키를 발급받아서 챗 지피티의 기능을 구현할 수 있습니다.

 

 

pill_data = pd.read_csv('Final_Pill_Standardization_Content_Dataset.csv', header=0, encoding='cp949') #영양제 영양소 함량 데이터셋
food_data = pd.read_csv('food_dataset.csv', encoding='cp949') #평균 섭취량을 구하기 위한 음식 데이터셋
Final_Pill_Dataset = pd.read_csv('Final_Pill_Dataset.csv',header=0, encoding='cp949')
Final_Pill_Dataset_KIDS = pd.read_csv('Final_Pill_Dataset+KIDS.csv',header=0, encoding='cp949')
food_data_recommand = pd.read_csv('MinMax_food_data.csv', encoding='cp949') #평균 섭취량을 구하기 위한 음식 데이터셋

 

다음은 데이터셋을 변수로 넣는 코드입니다. 각각, 음식 데이터셋, 영양제 정보 데이터셋, 영양제 함량 데이터셋이 있고,

 

유아와 성인의 영양제 데이터셋은 구분되어 있습니다.

 

# 평균 섭취량 계산 함수
def calculate_average_intake(daily_intakes):
    total_intake = {nutrient: 0.0 for nutrient in daily_intakes[0]}
    count = len(daily_intakes)
   
    for daily_intake in daily_intakes:
        for nutrient, value in daily_intake.items():
            try:
                total_intake[nutrient] += float(value)
            except ValueError:
                # 숫자로 변환할 수 없는 값이 포함된 경우에 대한 예외 처리
                print(f"Could not convert value to float: {value}. Skipping...")
   
    # 각 영양소의 평균을 계산
    average_intake = {nutrient: round(total / count, 1) for nutrient, total in total_intake.items()}
   
    return average_intake

 

저희 서버는 식단을 입력받아서 평균섭취량을 구한 뒤 평균 섭취량과

 

사람들의 권장 영양소 섭취량을 비교해서

 

부족한 영양소와 가장 가까운 영양제를 추천해주는 방식입니다.

 

위는 평균 섭취량을 산출해내는 코드입니다.

 

파이썬의 in 함수를 활용해서 식단이 여러개일 경우 모든 식단의 평균을 구해서 

 

평균 섭취량을 리턴합니다.

 

def find_deficient_nutrients(diet, recommended_intake):
    deficient_nutrients = {}
    for nutrient, intake in recommended_intake.items():
        if nutrient in diet:
            diet_value = diet[nutrient]
            print(f"Nutrient: {nutrient}, Diet Value: {diet_value}, Recommended Intake: {intake}")  # 디버깅용 출력
            diff = intake - diet_value
            if diff > 0:
                deficient_nutrients[nutrient] = diff
    print(f"Deficient Nutrients: {deficient_nutrients}")  # 디버깅용 출력
    return deficient_nutrients

 

그 다음은 평균 섭취량과 권장 섭취량을 비교하여 부족한 섭취량을

 

구하는 코드입니다.

 

평균섭취량과 권장섭취량을 빼서 부족한 영양소를 리턴합니다.

 

 
   recommended_intake = {
        '루테인(mg)': 0,
        '비타민A(ug)': 0,
        '비타민D(ug)': 0,
        '비타민E(mg)': 15,
        '비타민K(ug)': 120,
        '비타민C(mg)': 90,
        '비타민B2(mg)': 1.3,
        '아연(mg)': 11,
        '셀렌(ug)': 55,
        '철(mg)': 8,
        '마그네슘(mg)': 400,
        'EPA(mg)': 0,
        '프로바이오틱스(CFU)': 0,
        '실리마린(mg)': 0,
        '나이아신(mg)': 16,
        '올리고당(g)': 0,
        '칼슘(mg)': 1000,
        '비타민B6(mg)': 1.7,
        '베타카로틴(mg)': 900,
        '판토텐산(mg)': 5,
        '비오틴(ug)': 30,
        '망간(mg)': 2.3,
        '크롬(ug)': 0,
        '엽산(ug)': 400,
        '구리(mg)': 0.9,
        '몰리브덴(ug)': 55
    }

 

권장 섭취량은 영양소의 종류가 많아서 

 

모든 영양소를 충족시키는 데이터셋을 찾을 수 없어서

 

하드코딩으로 대체했습니다.

 

def recommend_pill(daily_intakes, age, gender):
   

    # 부족한 영양소 확인
    deficient_nutrients = find_deficient_nutrients(daily_intakes, recommended_intake)
 
    pill_distance_list = []
    for i, pill in pill_data.iterrows():
        pill_distance = 0.0
        for nutrient, weight in nutrient_weights.items():
            pill_nutrient_value = pill.get(nutrient, 0)
            diet_nutrient_value = deficient_nutrients.get(nutrient, 0)

            # 각 영양소의 차이를 제곱하여 거리를 계산
            nutrient_diff = abs(pill_nutrient_value - diet_nutrient_value)  # 절댓값으로 계산
            if nutrient_diff > 0:  # 부족한 영양소만 고려
                pill_distance += weight * nutrient_diff  # 부족한 영양소만 고려하여 거리 계산
       
        pill_distance = math.sqrt(pill_distance)  # 유클리드 거리 계산
     
        pill_distance_list.append((i, pill_distance))
       
    sorted_pill_distance_list = sorted(pill_distance_list, key=lambda x: x[1])

    # 나이에 따라 다른 데이터셋 선택
    if age > 20:
        pill_dataset = Final_Pill_Dataset
    else:
        pill_dataset = Final_Pill_Dataset_KIDS

    recommendation = {}
    for idx, distance in sorted_pill_distance_list[:3]:  # 상위 3개의 영양제를 추천
        recommendation[idx] = {
            '영양제명': pill_dataset.loc[idx, '영양제명'],
            '거리': distance,
            'IFTKN_ATNT_MATR_CN': pill_dataset.loc[idx, 'IFTKN_ATNT_MATR_CN'],
            'STDR_STND': pill_dataset.loc[idx, 'STDR_STND'],
            'PRIMARY_FNCLTY': pill_dataset.loc[idx, 'PRIMARY_FNCLTY']
        }

    return recommendation

 

다음은 추천 로직을 포함한 코드입니다.

 

부족한 영양소와 유클리드 거리가 가장 가까운 

 

영양제를 영양제 데이터셋에서 찾아서 추천 영양제 3개의 정보를 리턴합니다.

 

# 거리 계산 메소드
def distance(x, y):
    x_values = x.values
    y_values = np.array(list(y.values()) + [0.0] * (len(x_values) - len(y)))
    a = np.linalg.norm(x_values - y_values)
    return a

 

유클리드 거리를 구하는 코드입니다.

 

app.route('/ask', methods=['POST'])
def ask_question():
    data = request.get_json()
    user_question = data['question']

    # Use the user_question as the prompt for OpenAI Completion
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": user_question}
        ],
        temperature=0.7,
        max_tokens=2000
    )

    # Extract the generated answer from the OpenAI response
    answer = response.choices[0].message['content'].strip()

    return jsonify({'answer': answer})

 

다음은 ChatGPT API를 활용하여 Post 요청을 클라이언트로 보내는 코드입니다.

 

위 코드는 예시로 ChatGPT에서 주어진 코드를 활용했습니다.

 

pp.route('/recommend_pill', methods=['POST'])
def recommend_pill_route():
    data = request.json
   
    # 식단 정보 추출
    food_names = data['food_names']
    age = int(data['age'])  # 문자열을 정수로 변환
    gender = data['gender']
   
    # 나이와 성별에 따른 권장 섭취량 가져오기
    recommended_intake = {
    '루테인(mg)': 0,
    '비타민A(ug)': 0,
    '비타민D(ug)': 0,
    '비타민E(mg)': 15,
    '비타민K(ug)': 120,
    '비타민C(mg)': 90,
    '비타민B2(mg)': 1.3,
    '아연(mg)': 11,
    '셀렌(ug)': 55,
    '철(mg)': 8,
    '마그네슘(mg)': 400,
    'EPA(mg)': 0,
    '프로바이오틱스(CFU)': 0,
    '실리마린(mg)': 0,
    '나이아신(mg)': 16,
    '올리고당(g)': 0,
    '칼슘(mg)': 1000,
    '비타민B6(mg)': 1.7,
    '베타카로틴(mg)': 900,
    '판토텐산(mg)': 5,
    '비오틴(ug)': 30,
    '망간(mg)': 2.3,
    '크롬(ug)': 0,
    '엽산(ug)': 400,
    '구리(mg)': 0.9,
    '몰리브덴(ug)': 55
}

 
    # 신체 정보 추출
    weight = int(data['weight'])
    height = int(data['height'])
   
    # BMI 계산
    BMI = calc_bmi(weight, height)
   
    # 식단 정보를 기반으로 영양소 정보 추출
    daily_intakes = [get_nutrient_info(food_name) for food_name in food_names]
    average_intake = calculate_average_intake(daily_intakes)  # 수정된 부분
    deficient_nutrients = find_deficient_nutrients(average_intake, recommended_intake)
   
    # 영양제 추천
    recommendation = recommend_pill(average_intake, age, gender)
   
       # 디버깅을 위한 코드 추가
    for pill_index, pill_info in recommendation.items():
        print(f"Pill Index: {pill_index}")
        print(f"Pill Info: {pill_info}")
   
    # 응답 구성
    response = {
        "recommendation": recommendation,
        "BMI": BMI,
        "average-intake": average_intake,  # 수정된 부분
        "deficient-nutritions": deficient_nutrients
    }
    return jsonify(response)

 

클라이언트로 추천 로직을 진행하여 얻은 결과를

 

POST요청을 통해서 보내는 코드입니다.

 

 response = {
        "recommendation": recommendation,
        "BMI": BMI,
        "average-intake": average_intake,  # 수정된 부분
        "deficient-nutritions": deficient_nutrients
    }

 

response에 해당하는 부분이 클라이언트로 전송되고,

 

recommendation[idx] = {
            '영양제명': pill_dataset.loc[idx, '영양제명'],
            '거리': distance,
            'IFTKN_ATNT_MATR_CN': pill_dataset.loc[idx, 'IFTKN_ATNT_MATR_CN'],
            'STDR_STND': pill_dataset.loc[idx, 'STDR_STND'],
            'PRIMARY_FNCLTY': pill_dataset.loc[idx, 'PRIMARY_FNCLTY']
        }

 

response에 있는 recommendation 변수 안에 영양제의 정보와 거리 등을 포함하여

 

전송하게 됩니다.

 

 

오늘은 서버 코드를 리뷰해보았는데요.

 

코드 자체는 전체적으로 어려워 보이지 않지만

 

데이터셋과 추천 로직을 연동시키기 위해

 

시간을 많이 썼던 것 같습니다.

 

읽어주셔서 감사합니다!

 

 

 

 

'Flask' 카테고리의 다른 글