Skip to content

Commit

Permalink
feat: backend cloud function code (#225)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyunuk authored Jul 21, 2024
1 parent 87627cf commit 7893dbd
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 204 deletions.
152 changes: 152 additions & 0 deletions data-analytics/minigolf-demo/backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import functions_framework
import firebase_admin
from firebase_admin import firestore
from google.cloud import bigquery, storage
from flask import make_response
import base64
import matplotlib.pyplot as plt
from io import BytesIO
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import pandas as pd


PROJECT_ID = ""
BACKGROUND_IMAGE_BUCKET = ""
VIDEO_BUCKET = ""
BQ_DATASET = ""
BQ_PREFIX = f"{PROJECT_ID}.{BQ_DATASET}"


if not firebase_admin._apps:
firebase_admin.initialize_app(options={'projectId':f'{PROJECT_ID}'})


db = firestore.client()
bq_client = bigquery.Client()


def get_user_status(user_id):
"""Retrieves the status of a user from Firestore."""
user_doc_ref = db.collection('users_a').document(user_id)
user_doc = user_doc_ref.get()

if not user_doc.exists:
return None # Return None if user not found

return user_doc.to_dict().get('status')


def get_commentary(user_id):
"""Fetches commentary for a user from BigQuery."""
query = f"""
SELECT commentary
FROM `{BQ_PREFIX}.commentary`
WHERE user_id = '{user_id}'
"""
query_job = bq_client.query(query)
results = list(query_job)
return results[0].commentary if results else None


def get_stat(user_id):
"""Calculates and returns game statistics."""
query = f"SELECT * FROM {BQ_PREFIX}.tracking"
df = bq_client.query(query).to_dataframe()
last_frame_per_user = df.groupby('user_id')['frame_number'].transform(max)
df_filtered = df[df['frame_number'] == last_frame_per_user]
df_filtered = df_filtered[df_filtered['distance'] < 30]
user_shot_counts = df_filtered.groupby('user_id')['shot_number'].first()
user_shot_counts = user_shot_counts[user_shot_counts > 0]
shot_number_freq = user_shot_counts.value_counts()

# Selected user's number of shots
num_users = df['user_id'].nunique()
user_shots = user_shot_counts.get(user_id, 0)
average_shots_per_user = user_shot_counts.mean()

plt.figure(figsize=(8, 6)) # Set figure size
plt.xlim(0, 9)
barlist = plt.bar(shot_number_freq.index, shot_number_freq.values, color='#4285F4')
plt.xlabel('Number of Shots')
plt.ylabel('Number of Users')
plt.title('Distribution of Number of Shots per User')

if user_shots in shot_number_freq.index:
barlist[shot_number_freq.index.get_loc(user_shots)].set_color('#34A853')
plt.legend([barlist[shot_number_freq.index.get_loc(user_shots)]], [f'Shot Number of {user_id}'])

plt.xticks(range(9))

# Save the plot to a BytesIO object
fig = plt.gcf()
canvas = FigureCanvas(fig)
output = BytesIO()
canvas.print_png(output)
plt.close(fig) # Close the figure to prevent memory leaks

# Encode the image in Base64
base64_encoded_image = base64.b64encode(output.getvalue()).decode('utf-8')

# --- Construct the stat dictionary ---
stat = {
'num_users': num_users,
'user_shots': user_shots,
'average_shots': f'{average_shots_per_user:.2f}',
'barchart': base64_encoded_image
}
return stat



@functions_framework.http
def get_user_data(request):
"""HTTP Cloud Function.
Args:
request (flask.Request): The request object.
<https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
Returns:
The response text, or any set of values that can be turned into a
Response object using `make_response`
<https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
"""

request_args = request.args
if request_args and 'userId' in request_args:
user_id = request_args['userId']
else:
return {'error': 'userId parameter is required'}, 400

try:
image_url = None
commentary = None
video_url = None
stat = None

status = get_user_status(user_id)
if not status:
return {'error': 'User is not existed'}, 400
if status != "processing":
image_url = f"https://storage.cloud.google.com/{BACKGROUND_IMAGE_BUCKET}/{user_id}.png"
video_url = f"https://storage.cloud.google.com/{VIDEO_BUCKET}/{user_id}.mp4"
commentary = get_commentary(user_id)
stat = get_stat(user_id)

response_data = {
'status': status,
'imageUrl': image_url,
'commentary': commentary,
'videoUrl': video_url,
'users': int(stat['num_users']),
'shots': int(stat['user_shots']),
'average': stat['average_shots'],
'barchart': stat['barchart']
}

response = make_response(response_data)
response.headers.add('Access-Control-Allow-Origin', 'http://127.0.0.1:5501')
response.headers['Content-Type'] = 'application/json'
return response, 200

except Exception as e:
print(f"Error fetching user status: {e}")
return {'error': 'Failed to fetch user status'}, 500
10 changes: 10 additions & 0 deletions data-analytics/minigolf-demo/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
functions-framework==3.*
firebase-admin
flask
flask-cors
google-cloud-bigquery
google-cloud-storage
google-cloud-bigquery-storage
matplotlib
pandas
db-dtypes
Loading

0 comments on commit 7893dbd

Please sign in to comment.