Programmings

Pandas DataFrame to MySQL Database using iris Data

개요

  • 이전 강의에 이어서 진행한다. (MySQL Select Clause via Python)
  • 임의의 Pandas 데이터 프레임에서 MySQL DB로 추가하는 코드를 작성한다.

주요 라이브러리 설치

  • 아래와 같이 주요 라이브러리를 설치한다.
    • MySQL과 관련된 주요 Python 라이브러리를 설치한다.
pip install mysql-connector mysql-connector-python pymysql SQLAlchemy seaborn pandas

코드 작성(mysql-connector)

  • 아래와 같이 코드를 작성한다.
# 파일명 : db.py
import mysql.connector
import pandas as pd
import seaborn as sns

mydb = mysql.connector.connect(
    host = "localhost", 
    user = "root", 
    passwd = "evan",
    database = "muldb"
)

print(mydb)

iris_df = sns.load_dataset('iris')

my_cursor = mydb.cursor()

create_table_query  = """
   CREATE TABLE IF NOT EXISTS iris (
    id INT AUTO_INCREMENT PRIMARY KEY,
    sepal_length FLOAT,
    sepal_width FLOAT,
    petal_length FLOAT,
    petal_width FLOAT,
    species VARCHAR(255)
    ); 
"""

my_cursor.execute(create_table_query)

insert_query = """
    INSERT INTO iris (sepal_length, sepal_width, petal_length, petal_width, species) 
    VALUES (%s, %s, %s, %s, %s)
"""

data = [tuple(x) for x in iris_df.to_numpy()]

for row in data:
    my_cursor.execute(insert_query, row)

# DB Commit
mydb.commit()

# 종료
my_cursor.close()
mydb.close()
  • 이번에는 Python 파일을 실행한다.
$ python db.py 
<mysql.connector.connection_cext.CMySQLConnection object at 0x0000025FFC155BD0>
  • 이번에는 MySQL Workbench에서 확인한다.

Untitled

MySQL Select Clause via Python

개요

데이터 조회

  • 다음 코드를 작성한다.
import mysql.connector

mydb = mysql.connector.connect(
    host = "localhost", 
    user = "root", 
    passwd = "evan", 
    database = "mulcampdb"
)

print(mydb)

my_cursor = mydb.cursor()

query = """
   SELECT * FROM users;
"""

my_cursor.execute(query)

result = my_cursor.fetchall()
for row in result:
    print(row)
    
print("완료")
  • 파일을 실행한다.
$ python database.py 
<mysql.connector.connection_cext.CMySQLConnection object at 0x000001FE5A985F10>
('Evan', 'Evan@gmail.com', 30, 1)
('Evan', 'Evan@gmail.com', 30, 2)
('Mary', 'Mary@gmail.com', 20, 3)
('Sara', 'Sara@gmail.com', 25, 4)
완료

pandas 데이터프레임으로 변환

  • 주어진 결괏값으로 pandas 데이터프레임으로 변환한다.
  • 먼저 라이브러리를 설치한다.
$ pip install pandas
  • 데이터 같이 코드를 작성한다.
import mysql.connector
import pandas as pd 

mydb = mysql.connector.connect(
    host = "localhost", 
    user = "root", 
    passwd = "evan", 
    database = "mulcampdb"
)

print(mydb)

my_cursor = mydb.cursor(dictionary=True)

query = """
   SELECT * FROM users;
"""

my_cursor.execute(query)

result = my_cursor.fetchall()
for row in result:
    print(row)

df = pd.DataFrame(result)
print(df)
print("완료")
  • 파일을 실행한다.
$ python database.py 
<mysql.connector.connection_cext.CMySQLConnection object at 0x000002C4C47D5490>
{'name': 'Evan', 'email': 'Evan@gmail.com', 'age': 30, 'user_id': 1}
{'name': 'Evan', 'email': 'Evan@gmail.com', 'age': 30, 'user_id': 2}
{'name': 'Mary', 'email': 'Mary@gmail.com', 'age': 20, 'user_id': 3}
{'name': 'Sara', 'email': 'Sara@gmail.com', 'age': 25, 'user_id': 4}
   name           email  age  user_id
0  Evan  Evan@gmail.com   30        1
1  Evan  Evan@gmail.com   30        2
2  Mary  Mary@gmail.com   20        3
3  Sara  Sara@gmail.com   25        4
완료

코드 복기

  • 커서를 생성하는 코드를 다시 확인해본다.
    • 이 코드는 dictionary=True는 조회 결과를 Dictionary 형태로 받기 위한 설정이다.
my_cursor = mydb.cursor(dictionary=True)
  • 이 코드로 인해, pandas DataFrame으로 변환하는 것이 매우 쉬워진다.
result = my_cursor.fetchall()
df = pd.DataFrame(result)
print(df)

MySQL Table Creation and Insert Data via Python

개요

테이블 생성

  • 아래 코드를 작성하면 테이블이 생성된다.
import mysql.connector

mydb = mysql.connector.connect(
    host = "localhost", 
    user = "root", 
    passwd = "evan", 
    database = "mulcampdb"
)

print(mydb)

my_cursor = mydb.cursor()
query = """
    CREATE TABLE users (
        name VARCHAR(255)
        , email VARCHAR(255)
        , age INTEGER(10)
        , user_id INTEGER AUTO_INCREMENT PRIMARY KEY
    );
"""
my_cursor.execute(query)
my_cursor.execute("SHOW TABLES;")
for table in my_cursor:
    print(table[0])
  • 파일을 실행한다.
$ python database.py 
<mysql.connector.connection_cext.CMySQLConnection object at 0x0000028942314F90>
users
  • MySQL Workbench에 생성된 테이블이 만들어지는지 확인한다.

Untitled

Connect To Database in Python

개요

  • Python과 MySQL을 연동하도록 한다.
  • 프로젝트 폴더에 가상환경이 설치가 되어 있는 것으로 가정한다.
  • MySQL은 기 설치가 되어 있는 것으로 가정한다.

라이브러리 설치

  • Python과 MySQL을 연동해주는 라이브러리 종류는 다양하게 있다.
$ pip install mysql-connector mysql-connector-python

파일 작성

  • 간단하게 파일을 작성한다.
import mysql.connector

mydb = mysql.connector.connect(
    host = "localhost", 
    user = "root", 
    passwd = "evan"
)

print(mydb)
  • 파일을 실행한다.
$ python database.py 
<mysql.connector.connection_cext.CMySQLConnection object at 0x000002BF4E606090>
(venv) 

Python 코드 활용하여 DB 생성

  • 이번에는 코드를 활용하여 Schema를 생성한다.
import mysql.connector

mydb = mysql.connector.connect(
    host = "localhost", 
    user = "root", 
    passwd = "evan"
)

print(mydb)

my_cursor = mydb.cursor()
query = """
    CREATE DATABASE mulcampdb
"""

my_cursor.execute(query)

print("완료")
  • 파일을 실행한다.
$ python database.py 
<mysql.connector.connection_cext.CMySQLConnection object at 0x0000020BB6504F50>
완료
  • MySQL Workbench에서 DB가 생성되었는지 확인한다.

Untitled

MySQL 삭제, 재설치 가이드 on M1

개요

  • M1에서 MySQL을 설치 하고 Workbench에 접속하는 과정을 설명한다.
  • 데이터 로드 시, ASCII 에러 과정 해결하는 방법도 살펴본다. (임시방편)

사전학습

  • brew 명령어를 알고 있는 분에 한해 작성을 하였다.

주의

  • 아래 코드 복사할 시, $ 는 제외 후 복사한다.

MySQL 실행 확인 후 프로세스 Kill

  • 먼저 MySQL이 실행중인지를 확인한다.
$ brew services list
Name  Status  User File
mysql started evan ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
  • 서비스를 강제 종료한다.
$ brew services stop mysql
Stopping `mysql`... (might take a while)
==> Successfully stopped `mysql` (label: homebrew.mxcl.mysql)

관련 파일 삭제

  • 재 설치를 위해서는 기존에 설치된 파일 목록 등을 모두 제거한다.
$ which mysql
/opt/homebrew/bin/mysql
  • Homebrew로 기존에 설치했다면 아래 명령어를 실행한다.
$ brew uninstall --force mysql
Uninstalling mysql... (323 files, 312.8MB)
  • 다음 라인을 한줄씩 실행한다.
sudo rm -rf /usr/local/mysql
sudo rm -rf /usr/local/bin/mysql
sudo rm -rf /usr/local/var/mysql
sudo rm -rf /usr/local/Cellar/mysql
sudo rm -rf /usr/local/mysql*
sudo rm -rf /tmp/mysql.sock.lock
sudo rm -rf /tmp/mysqlx.sock.lock
sudo rm -rf /tmp/mysql.sock
sudo rm -rf /tmp/mysqlx.sock
sudo rm ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist
sudo rm -rf /Library/StartupItems/MySQLCOM
sudo rm -rf /Library/PreferencePanes/My*

MySQLWorkbench 삭제

  • Applications 폴더에서 해당 MySQLWorkbench 파일을 찾아 삭제한다.

Screenshot 2024-04-02 at 1.20.31 PM.png

Github Actions Hello World From Python Script

개요

  • Python Script를 활용하여 Hell World를 출력한다.

강의소개

  • 인프런에서 Streamlit 관련 강의를 진행하고 있습니다.

이전 게시글

main.py 작성

  • 간단하게 아래 코드를 작성한다.
  • 코드 작성은 Github에서도 가능하다.
import sys

print(sys.version)
print("Hello, World")
  • Add file > Create new file 버튼을 클릭한다.

Untitled

Github Actions Hello World

개요

  • Github Actions 에서 Hello World를 출력하도록 한다.

강의소개

  • 인프런에서 Streamlit 관련 강의를 진행하고 있습니다.

사전준비

  • Github에 적당한 Repo를 준비한다.

메뉴선택

  • 아래 그림에서 Actions 메뉴를 선택한다.

Untitled

  • 아래 그림에서 set up a workflow yourself 선택

Untitled

YAML 파일 수정

  • .github/workflows/main.yaml 파일 선택 후 수정

Untitled

Streamlit ML Multiclass Classification Model Prediction Sample (feat. Pipeline)

개요

  • Kaggle 데이터셋을 활용하여 Streamlit ML Multiclass Classification Model을 배포한다.
  • 각 코드에 대한 자세한 설명은 여기에서는 생략한다.

데이터 수집

Untitled

Untitled

모델 개발

  • 다음 코드는 모델을 개발하는 코드이다.
  • 주어진 데이터셋에서 종속변수 NObeyesdad을 예측하는 모델을 구성했다.
    • 파일명 : model.py
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
from joblib import dump, load
import os

DATA_PATH = './data/train.csv'
data = pd.read_csv(DATA_PATH)

# Separate features and target variable
X = data.drop(['id', 'NObeyesdad'], axis=1)
y = data['NObeyesdad']

# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Identify numerical and categorical columns
num_columns = X.select_dtypes(include=['float64']).columns
cat_columns = X.select_dtypes(include=['object']).columns

# Create the preprocessing pipeline
preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), num_columns),
        ('cat', OneHotEncoder(handle_unknown='ignore'), cat_columns)
    ]
)

# Create the full pipeline
pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier())
])

# Train the model
pipeline.fit(X_train, y_train)

# 모델 저장
model_directory = 'model'

if not os.path.exists(model_directory):
    os.makedirs(model_directory)

model_path = os.path.join(model_directory, 'NObeyesdad_prediction_pipeline.joblib')
dump(pipeline, model_path)
  • 위 코드에서 핵심은 모델을 저장하는 것이며, 또한 OneHotEncoder(handle_unknown='ignore') 을 지정하는 것이다.
  • 해당하는 폴더에 model 폴더가 없으면 model 폴더를 생성하고 NObeyesdad_prediction_pipeline.joblib 이름으로 모델을 저장한다.

파일 실행

  • model.py 를 실행하여 모델을 생성한다.
python model.py

Streamlit App 개발

  • 다음 코드는 Streamlit App 개발을 하는 코드이다.
    • 파일명 : app.py
import streamlit as st
import pandas as pd
from joblib import load
import os

# Assuming your model is saved in the 'model' directory with the name 'obesity_prediction_pipeline.joblib'
model_directory = 'model'
model_path = os.path.join(model_directory, 'NObeyesdad_prediction_pipeline.joblib')

def predict_NObeyesdad_level(model_path, Gender, Age, Height, Weight, family_history_with_overweight, FAVC, FCVC, NCP, CAEC, SMOKE, CH2O, SCC, FAF, TUE, CALC, MTRANS):
    
    # 모델 불러오기
    pipeline = load(model_path)
    
    # 데이터프레임 생성
    df = pd.DataFrame([{
        'Gender': Gender, 'Age': Age, 'Height': Height, 'Weight': Weight,
        'family_history_with_overweight': family_history_with_overweight, 'FAVC': FAVC,
        'FCVC': FCVC, 'NCP': NCP, 'CAEC': CAEC, 'SMOKE': SMOKE, 'CH2O': CH2O,
        'SCC': SCC, 'FAF': FAF, 'TUE': TUE, 'CALC': CALC, 'MTRANS': MTRANS
    }])
    
    # 예측 값 생성
    prediction = pipeline.predict(df)
    return prediction[0]

def main():
    st.title('Obesity Level Prediction Model')
    st.write('Predict obesity levels based on personal and health-related attributes.')

    # Create input fields for each feature
    Gender = st.selectbox('Gender', ['Male', 'Female'])
    Age = st.number_input('Age', min_value=0.0, format='%f')
    Height = st.number_input('Height (in meters)', min_value=0.0, format='%f')
    Weight = st.number_input('Weight (in kg)', min_value=0.0, format='%f')
    family_history_with_overweight = st.selectbox('Family history with overweight', ['yes', 'no'])
    FAVC = st.selectbox('Frequent consumption of high caloric food', ['yes', 'no'])
    FCVC = st.number_input('Frequency of consumption of vegetables', min_value=0.0, max_value=3.0, step=0.1)
    NCP = st.number_input('Number of main meals', min_value=1.0, max_value=4.0, step=0.1)
    CAEC = st.selectbox('Consumption of food between meals', ['No', 'Sometimes', 'Frequently', 'Always'])
    SMOKE = st.selectbox('Do you smoke?', ['yes', 'no'])
    CH2O = st.number_input('Consumption of water daily (liters)', min_value=0.0, format='%f')
    SCC = st.selectbox('Calories consumption monitoring', ['yes', 'no'])
    FAF = st.number_input('Physical activity frequency (per week)', min_value=0.0, format='%f')
    TUE = st.number_input('Time using technology devices (hours)', min_value=0.0, format='%f')
    CALC = st.selectbox('Consumption of alcohol', ['Never', 'Sometimes', 'Frequently', 'Always'])
    MTRANS = st.selectbox('Mode of transportation', ['Automobile', 'Bike', 'Motorbike', 'Public_Transportation', 'Walking'])

    if st.button('Predict Obesity Level'):
        result = predict_NObeyesdad_level(model_path, Gender, Age, Height, Weight, family_history_with_overweight, FAVC, FCVC, NCP, CAEC, SMOKE, CH2O, SCC, FAF, TUE, CALC, MTRANS)
        st.success(f'Predicted Obesity Level: {result}')

if __name__ == "__main__":
    main()
  • 위 코드에서 핵심은 predict_tip 함수이다. pipeline으로 모델을 설계하면 곧바로 predict() 저장된 모델을 불러온 후, 함수 사용이 가능하다.

테스트

  • 테스트 결과는 아래와 같이 나온다.
streamlit run app.py 

Untitled

Streamlit ML Model Prediction Sample (feat. Pipeline)

강의소개

  • 인프런에서 Streamlit 관련 강의를 진행하고 있습니다.
  • 인프런 : https://inf.run/YPniH

개요

  • tips 데이터셋을 활용하여 Streamlit ML Model을 배포한다.
  • 각 코드에 대한 자세한 설명은 여기에서는 생략한다.

모델 개발

  • 다음 코드는 모델을 개발하는 코드이다.
  • 주어진 데이터셋에서 tip을 예측하는 모델을 구성했다.
    • 파일명 : model.py
import streamlit as st
import pandas as pd
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from joblib import dump, load
import os

# 데이터셋 불러오기
tips = sns.load_dataset('tips')

# 데이터셋 컬럼 추출
categorical_features = ['sex', 'smoker', 'day', 'time']
numerical_features = ['total_bill', 'size']

# pipeline 모델 만들기
preprocessor = ColumnTransformer(
    transformers=[
        ('num', 'passthrough', numerical_features),
        ('cat', OneHotEncoder(), categorical_features)
    ])

pipeline = Pipeline(steps=[('preprocessor', preprocessor),
                           ('model', LinearRegression())])

# 데이터셋 분류 / 종속 변수 tip을 예측하는 모델
X = tips.drop('tip', axis=1)
y = tips['tip']

# 데이터셋 분류
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 모델 학습
pipeline.fit(X_train, y_train)

# 모델 저장
model_directory = 'model'

if not os.path.exists(model_directory):
    os.makedirs(model_directory)

model_path = os.path.join(model_directory, 'tip_prediction_pipeline.joblib')
dump(pipeline, model_path)
  • 위 코드에서 핵심은 모델을 저장하는 것이다. 마지막 코드이다.
  • 해당하는 폴더에 model 폴더가 없으면 model 폴더를 생성하고 tip_prediction_pipeline.joblib 이름으로 모델을 저장한다.
  • model.py를 실행한다.
python model.py
  • 정상적으로 모델이 만들어졌으면, model 폴더가 생겼을 것이고, 그 다음에 해당 모델이 저장되어 있을 것이다.

Streamlit App 개발

  • 다음 코드는 Streamlit App 개발을 하는 코드이다.
    • 파일명 : app.py
import streamlit as st
import pandas as pd
from joblib import load
import os

model_directory = 'model'
model_path = os.path.join(model_directory, 'tip_prediction_pipeline.joblib')

def predict_tip(model_path, total_bill, size, sex, smoker, day, time):
    
    # 모델 불러오기
    pipeline = load(model_path)
    
    # 예측 데이터 생성
    df = pd.DataFrame([{'total_bill': total_bill, 'size': size, 'sex': sex, 'smoker': smoker, 'day': day, 'time': time}])
    
    # 예측값 생성
    prediction = pipeline.predict(df)
    return prediction[0]

def main():

    st.title('팁 예측 모델')
    st.write('total_bill과 다른 요인을 고려하여 tip 예측 모델 생성')

    total_bill = st.number_input('Total Bill ($)', min_value=0.0, format='%f')
    size = st.number_input('Size of the Party', min_value=1, step=1)
    sex = st.selectbox('Sex', ['Male', 'Female'])
    smoker = st.selectbox('Smoker', ['Yes', 'No'])
    day = st.selectbox('Day', ['Thur', 'Fri', 'Sat', 'Sun'])
    time = st.selectbox('Time', ['Lunch', 'Dinner'])

    if st.button('예상 Tip 예측'):
        result = predict_tip(model_path, total_bill, size, sex, smoker, day, time)
        st.success(f'예측 Tip: ${result:.2f}')

if __name__ == "__main__":
    main()
  • 위 코드에서 핵심은 predict_tip 함수이다. pipeline으로 모델을 설계하면 곧바로 predict() 저장된 모델을 불러온 후, 함수 사용이 가능하다.

테스트

  • 테스트 결과는 아래와 같이 나온다.
streamlit run app.py 

Untitled

openAI API, Text & Image 생성 예제

강의소개

  • 인프런에서 Streamlit 관련 강의를 진행하고 있습니다.
  • 인프런 : https://inf.run/YPniH

소스 참조

  • 후루카와 히데카즈 저/트랜스메이트 역. (2023). GPT-4, ChatGPT, 라마인덱스, 랭체인을 활용한 인공지능 프로그래밍 한권으로 끝내는 OpenAI API 기반 LLM 애플리케이션 구축. 위키북스, 판매처 : https://www.yes24.com/Product/Goods/122533123

라이브러리 설치

  • openai 패키지를 설치한다.
!pip install openai
Collecting openai
  Obtaining dependency information for openai from https://files.pythonhosted.org/packages/26/a1/75474477af2a1dae3a25f80b72bbaf20e8296191ece7fff2f67984206f33/openai-1.12.0-py3-none-any.whl.metadata
  Downloading openai-1.12.0-py3-none-any.whl.metadata (18 kB)
.
.
.
[notice] A new release of pip is available: 23.2.1 -> 24.0
[notice] To update, run: python.exe -m pip install --upgrade pip

환경변수 준비

  • python-dotenv를 설치 한다.
!pip install python-dotenv
Collecting python-dotenv
  Obtaining dependency information for python-dotenv from https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl.metadata
  Downloading python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)
Downloading python_dotenv-1.0.1-py3-none-any.whl (19 kB)
Installing collected packages: python-dotenv
Successfully installed python-dotenv-1.0.1

[notice] A new release of pip is available: 23.2.1 -> 24.0
[notice] To update, run: python.exe -m pip install --upgrade pip

.env 파일 저장

  • .env 파일 생성 후, OpenAI Key값을 아래와 같이 저장한다.
OPENAI_API_KEY = 'your_id'

저장한 값 불러오기

  • OPENAI_API_KEY를 불러오고 싶다면 다음 코드를 사용한다.
import dotenv
import os
env_file = dotenv.find_dotenv()
dotenv.load_dotenv(env_file)
print(os.environ['OPENAI_API_KEY'])
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

프롬프트 준비

  • 여러줄에 걸친 문자열 정의
prompt = '''다음 이야기를 써주세요기타를 좋아하지만 컴맹인 여고생이 어떤 계기로 록밴드에 가입하고, 낯선 인간관계를 통해 활동하게 되는 이야기'''
import os
from openai import OpenAI
client = OpenAI(
    # This is the default and can be omitted    api_key=os.environ.get("OPENAI_API_KEY"),
)
response = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": "이 채팅은 좋습니다. 남동생과 대하를 하겠습니다. "},
    {"role": "user", "content": "안녕"},
    {"role": "assistant", "content": "남동생은 현재 컴퓨터를 하고 있습니다."},
    {"role": "user", "content": "너 지금 뭐하고 있어?"}
  ],
   temperature=0)
response.choices[0].message.content
'남동생은 지금 사용자와 대화하고 있습니다. 대하를 하고 있습니다.'

이미지 생성

from openai import OpenAI
client = OpenAI(
    # This is the default and can be omitted    api_key=os.environ.get("OPENAI_API_KEY"),
)
response = client.images.generate(
  model="dall-e-3",
  prompt="cat dancing on car",
  size="1024x1024",
  quality="standard",
  n=1,
)
image_url = response.data[0].url
image_url
'https://oaidalleapiprodscus.blob.core.windows.net/private/org-znknr4OkH2n7N79B7CFhmUj9/user-fyfrdMeg75Lew6QIXrb6JCzr/img-QI9YCiwm7w4N99SeSaFuverT.png?st=2024-02-27T05%3A20%3A01Z&se=2024-02-27T07%3A20%3A01Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-02-26T20%3A01%3A40Z&ske=2024-02-27T20%3A01%3A40Z&sks=b&skv=2021-08-06&sig=O9MYsIZUoSy2jjDjTVLeHbhPGyWtnHP%2BfFEJUgVNSvU%3D'