
Azure MLOps for Beginners: Train, Deploy and Serve a GRU forecasting model. (Part II)
In Part I for Azure MLOps for Begineers, we learnt how to train, register and deploy our model in Azure ML. In this part, we will learn how to use that endpoint to make forecast using a Flask Based web application.
For basic Flask setup check : Streamlined Flask Deployment on Azure. We will build our forecasting application on the top of this application.
helper.py
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from matplotlib import pyplot as plt
import requests
import json
#function to load data from api
def custom_business_week_mean(values):
# Filter out Saturdays
working_days = values[values.index.dayofweek != 5]
return working_days.mean()
#function to read stock data from Nepalipaisa.com api
def stock_dataFrame(stock_symbol,start_date='2020-01-01',weekly=False):
"""
input : stock_symbol
start_data set default at '2020-01-01'
weekly set default at False
output : dataframe of daily or weekly transactions
"""
#print(end_date)
today = datetime.today()
# Calculate yesterday's date
yesterday = today - timedelta(days=1)
# Format yesterday's date
formatted_yesterday = yesterday.strftime('%Y-%-m-%-d')
print(formatted_yesterday)
path = f'https://www.nepalipaisa.com/api/GetStockHistory?stockSymbol={stock_symbol}&fromDate={start_date}&toDate={formatted_yesterday}&pageNo=1&itemsPerPage=10000&pagePerDisplay=5&_=1686723457806'
df = pd.read_json(path)
theList = df['result'][0]
df = pd.DataFrame(theList)
#reversing the dataframe
df = df[::-1]
#removing 00:00:00 time
#print(type(df['tradeDate'][0]))
df['Date'] = pd.to_datetime(df['tradeDateString'])
#put date as index and remove redundant date columns
df.set_index('Date', inplace=True)
columns_to_remove = ['tradeDate', 'tradeDateString','sn']
df = df.drop(columns=columns_to_remove)
new_column_names = {'maxPrice': 'High', 'minPrice': 'Low', 'closingPrice': 'Close','volume':'Volume','previousClosing':"Open"}
df = df.rename(columns=new_column_names)
if(weekly == True):
weekly_df = df.resample('W').apply(custom_business_week_mean)
df = weekly_df
return df
def create_sequences(df, window_size=5):
"""
Create input-output sequences for time series forecasting.
Parameters:
- df: pandas DataFrame with 'Close' column
- window_size: number of days to use as input (default 5)
Returns:
- X: numpy array of shape (n_samples, window_size) containing input sequences
- y: numpy array of shape (n_samples,) containing target prices
"""
close_prices = df['Close'].values
X = []
y = []
print("****************************************")
# Create sliding windows
for i in range(len(close_prices) - window_size):
# Get the window of features
window = close_prices[i:i+window_size]
X.append(window)
# Get the target (next day's close)
target = close_prices[i+window_size]
y.append(target)
return np.array(X), np.array(y)
def inference(X):
# Define the input JSON payload
payload = json.dumps({"data": X.tolist()})
# Get the deployment endpoint
scoring_uri = "http://052b8af2-5fc7-4af2-adc6-4e7dff223ca8.eastus2.azurecontainer.io/score"
headers = {"Content-Type": "application/json"}
# Send request to the deployed model
response = requests.post(scoring_uri, data=payload, headers=headers)
# Print response
# print("Response:", response.json())
return response.json()
This file contains functions to get data, format it and an inference to send data to the endpoint.
scoring_uri = “http://052b8af2-5fc7-4af2-adc6-4e7dff223ca8.eastus2.azurecontainer.io/score”
This will be different for you endpoint. You can fit it in your endpoint.

app.py
Update your app.py as follows.
from flask import Flask, render_template, request
import pandas as pd
import plotly.express as px
import numpy as np
from helper import stock_dataFrame, create_sequences, inference
app = Flask(__name__)
def generate_plotly_plot(result_df, symbol):
"""Generate an interactive Plotly plot and return its HTML div."""
fig = px.line(result_df, x=result_df.index, y=['Actual', 'Predicted'],
markers=True, title=f'Actual vs Predicted Stock Prices for {symbol}')
fig.update_layout(
xaxis_title="Date",
yaxis_title="Stock Price",
template="plotly_white",
legend_title="Legend"
)
return fig.to_html(full_html=False) # Return only the div content
def calculate_mape(y_actual, y_pred):
"""Calculate Mean Absolute Percentage Error (MAPE)."""
y_actual, y_pred = np.array(y_actual), np.array(y_pred)
return np.mean(np.abs((y_actual - y_pred) / y_actual)) * 100
@app.route('/', methods=['GET', 'POST'])
def home():
df_html = None
plot_html = None
mape_value = None
forecasted_price = None # Store next-day forecast
if request.method == 'POST':
symbol = request.form.get('symbol')
if symbol:
df = stock_dataFrame(symbol)
df.reset_index(inplace=True)
# Create sequences
X, y = create_sequences(df)
print(X.shape)
print(type(X))
# Convert NumPy arrays to lists
y_list = y.tolist()
prediction = inference(X)
print(X[-5:])
prediction_list = prediction
print(prediction_list[-5:])
# Take only the last 'len(y)' dates from df
df_dates = df.iloc[-len(y):]['Date'].values
# Create DataFrame for predictions
result_df = pd.DataFrame({
'Date': df_dates,
'Actual': y_list,
'Predicted': prediction_list
})
result_df.set_index('Date', inplace=True)
# Calculate MAPE
mape_value = calculate_mape(y_list, prediction_list)
# Select last 10 records for display
result_df_tail = result_df.tail(10)
df_html = result_df_tail.to_html(classes='table table-striped table-bordered')
# Generate interactive plot
plot_html = generate_plotly_plot(result_df, symbol)
# # --- 🔹 Next-Day Forecast ---
print(df.tail())
df =df[['Close']]
last_5_days = df.iloc[-5:]
print(f"Last 5 days: {last_5_days}")
last_5_days_array = last_5_days.values
last_5_days_array = last_5_days_array.reshape(1, 5)
print(last_5_days_array.shape)
print(type(last_5_days_array))
forecasted_price = inference(last_5_days_array) # Pass list to inference()
print(forecasted_price)
forecasted_price = round(float(forecasted_price[0]), 2) # Convert to readable format
return render_template('index.html', df_html=df_html, plot_html=plot_html,
mape_value=mape_value, forecasted_price=forecasted_price)
if __name__ == '__main__':
app.run(debug=True)
This codes loads the data, sends it to endpoint and forecast the future price of the stock. Apart from that, it also plots the actual vs prediction.
index.html
Only app.py is not enough for a flask app to run, update your index.html with the following code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stock Prediction</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <!-- Include Plotly -->
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">Viewer</h1>
<!-- Form for Stock Symbol Input -->
<form method="POST" class="mb-4">
<div class="input-group">
<input type="text" name="symbol" class="form-control" placeholder="Enter Stock Symbol (e.g., NTC)" required>
<button type="submit" class="btn btn-primary">Get Data</button>
</div>
</form>
<!-- Display DataFrame if Available -->
{% if df_html %}
<h2 class="mt-4">Stock Data for {{ request.form.get('symbol') }}</h2>
<div class="table-responsive">
{{ df_html | safe }}
</div>
<!-- Display the Plotly Plot -->
<h2 class="mt-4">Actual vs Predicted Plot</h2>
<div>
{{ plot_html | safe }} <!-- Render Plotly HTML -->
</div>
{% endif %}
{% if mape_value is not none %}
<h3 class="mt-4">MAPE: {{ "%.2f"|format(mape_value) }}%</h3>
{% endif %}
{% if forecasted_price is not none %}
<h3 class="mt-4">Next-Day Forecast: {{ forecasted_price }}</h3>
{% endif %}
</div>
</body>
</html>
Test
First test locally.
python app.py
If you find it satisfying, push it in gihub. Since we have connected our github to Azure, the changes will appear in flask application hosted on Azure. Run your app and test it.

Conclusion
We finally created a web application that is able to forecast the future stock price.
But there are other MLOps consideration, such as model monitoring, data drift, scaling etc. We will learn to deal with those in coming tutorials.