Skip to content

Commit

Permalink
Add function for launching web UI
Browse files Browse the repository at this point in the history
  • Loading branch information
junhyukjeon committed Oct 1, 2024
1 parent 9cee503 commit 0be7e4e
Showing 1 changed file with 332 additions and 0 deletions.
332 changes: 332 additions & 0 deletions src/util/serve.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
using Cropbox
using CSV
using DataFrames: DataFrames, DataFrame
using DataStructures: OrderedDict
using GenieFramework
using Genie, Genie.Renderer, Genie.Renderer.Json, Genie.Requests
using Dates
using TimeZones
import Genie.Renderer.Html

datetime_from_julian_day_WEA(year, jday, time::Time, tz::TimeZone, occurrence) =

Check warning on line 11 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L11

Added line #L11 was not covered by tests
zoned_datetime(Date(year) + (Day(jday) - Day(1)) + time, tz, occurrence)
datetime_from_julian_day_WEA(year, jday, tz::TimeZone) = datetime_from_julian_day_WEA(year, jday, "00:00", tz)

Check warning on line 13 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L13

Added line #L13 was not covered by tests

#HACK: handle different API for Fixed/VariableTimeZone
zoned_datetime(dt::DateTime, tz::TimeZone, occurrence=1) = ZonedDateTime(dt, tz)
zoned_datetime(dt::DateTime, tz::VariableTimeZone, occurrence=1) = ZonedDateTime(dt, tz, occurrence)

Check warning on line 17 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L16-L17

Added lines #L16 - L17 were not covered by tests

# Handle .wea files
loadwea(filename, timezone; indexkey=:index) = begin
df = CSV.File(filename) |> DataFrame
df[!, indexkey] = map(r -> begin
occurrence = 1
i = DataFrames.row(r)
if i > 1
r0 = parent(r)[i-1, :]
r0.time == r.time && (occurrence = 2)

Check warning on line 27 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L20-L27

Added lines #L20 - L27 were not covered by tests
end
datetime_from_julian_day_WEA(r.year, r.jday, r.time, timezone, occurrence)

Check warning on line 29 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L29

Added line #L29 was not covered by tests
end, eachrow(df))
df

Check warning on line 31 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L31

Added line #L31 was not covered by tests
end

"""
Starts a web-based user interface for given Cropbox model.
"""
function launch_ui(S::Type{<:System})

Check warning on line 37 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L37

Added line #L37 was not covered by tests
# At launch
route("/") do
model_name = String(nameof(S)) # Model name for page header.
config = parameters(S) # Retrieve default model configuration.
variables = Cropbox.fieldunits(S) |> keys |> collect # Retrive all the variables within the model.
D = Cropbox.dependency(S)

Check warning on line 43 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L39-L43

Added lines #L39 - L43 were not covered by tests

# Get list of parameter names to search in dependency
params = []
for (_, dict) in config
for (key, _) in dict
push!(params, key)
end
end

Check warning on line 51 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L46-L51

Added lines #L46 - L51 were not covered by tests

# Create OrderedDict of systems and parameters for form generation
system_params = OrderedDict{String, OrderedDict{String, OrderedDict{Symbol, Any}}}()
for param in params
for node in D.N
if param == node.info.name && !occursin("Vector", string(node.info.type)) && node.info.state != :Tabulate
system_name = String(node.info.system)
param_name = String(param)

Check warning on line 59 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L54-L59

Added lines #L54 - L59 were not covered by tests

if !haskey(system_params, system_name)
system_params[system_name] = OrderedDict{String, OrderedDict{Symbol, Any}}()

Check warning on line 62 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L61-L62

Added lines #L61 - L62 were not covered by tests
end

system_params[system_name][param_name] = OrderedDict(

Check warning on line 65 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L65

Added line #L65 was not covered by tests
:alias => node.info.alias,
:value => config[Symbol(model_name)][param] |> deunitfy,
:state => node.info.state,
:type => node.info.type,
:unit => Cropbox.fieldunit(S, param)
)
end
end
end

Check warning on line 74 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L73-L74

Added lines #L73 - L74 were not covered by tests

# Generate the HTML form dynamically based on parameters
forms_html = ""
for (system_name, params_dict) in system_params

Check warning on line 78 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L77-L78

Added lines #L77 - L78 were not covered by tests
# forms_html *= "<h3>$system_name</h3>"
forms_html *= """

Check warning on line 80 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L80

Added line #L80 was not covered by tests
<div class="collapsible-container">
<h3 class="collapsible-header" onclick="toggleVisibility(this)">$system_name</h3>
<div class="collapsible-content">
"""

for (param_name, param_info) in params_dict
key = "$(system_name)__$(param_name)"
unit = param_info[:unit] !== nothing ? " $(param_info[:unit])" : ""

Check warning on line 88 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L86-L88

Added lines #L86 - L88 were not covered by tests

if param_info[:state] == :Provide

Check warning on line 90 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L90

Added line #L90 was not covered by tests
# File upload form
forms_html *= """

Check warning on line 92 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L92

Added line #L92 was not covered by tests
<div class='parameter-container'>
<label for='$key'>$param_name</label>
<input type='file' id='$key' name='$(key)__file'>
<span class='unit'>$unit</span>
<br>
</div>
"""
elseif param_info[:type] == :(Cropbox.typefor(Main.Cropbox.TimeZones.ZonedDateTime))

Check warning on line 100 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L100

Added line #L100 was not covered by tests
# Datetime form
forms_html *= """

Check warning on line 102 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L102

Added line #L102 was not covered by tests
<div class='parameter-container'>
<label>$param_name</label>
<div class='datetime-row'>
<input type='number' id='$(key)__year' name='$(key)__year' placeholder='Year' value='' class='datetime-input'>
<input type='number' id='$(key)__month' name='$(key)__month' placeholder='Month' value='' class='datetime-input'>
<input type='number' id='$(key)__day' name='$(key)__day' placeholder='Day' value='' class='datetime-input'>
<input type='text' id='$(key)__timezone' name='$(key)__timezone' placeholder='Timezone' value='' class='datetime-input'>
<span class='unit'>$unit</span>
</div>
</div>
"""
# elseif param_info[:type] |> eval |> supertype == Enum{Int32}
# # Enums? There's probably a better way for this...
# value = param_info[:value]
# forms_html *= """
# <div class='parameter-container'>
# <label for='$key'>$param_name</label>
# <input type='text' id='$(key)__enum' name='$(key)__enum' value='$value'>
# <span class='unit'>$unit</span>
# <br>
# </div>
# """
# elseif param_info[:type] |> eval |> supertype != Enum{Int32}
elseif !occursin("SoilClass", param_info[:type] |> string) && !occursin("LeafAngle", param_info[:type] |> string)

Check warning on line 126 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L126

Added line #L126 was not covered by tests
# Regular parameter form
value = param_info[:value]
forms_html *= """

Check warning on line 129 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L128-L129

Added lines #L128 - L129 were not covered by tests
<div class='parameter-container'>
<label for='$key'>$param_name</label>
<input type='text' id='$key' name='$key' value='$value'>
<span class='unit'>$unit</span>
<br>
</div>
"""
end
end

Check warning on line 138 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L138

Added line #L138 was not covered by tests

forms_html *= """

Check warning on line 140 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L140

Added line #L140 was not covered by tests
</div> <!-- End of collapsible-content -->
</div> <!-- End of collapsible-container -->
"""
end

Check warning on line 144 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L144

Added line #L144 was not covered by tests

# Separate form generation for potential variable names for the `target`` keyword
target_html = """

Check warning on line 147 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L147

Added line #L147 was not covered by tests
<div class='parameter-container'>
<select id="Options__target_skip" name="Options__target_skip" class="dropdown">
<option value="" disabled selected>Select parameters</option>
"""
for var in variables
target_html *= "<option value='$(string(var))'>$(string(var))</option>"
end
target_html *= "</select></div>"

Check warning on line 155 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L152-L155

Added lines #L152 - L155 were not covered by tests

# Read the base HTML template and replace necessary forms
html_page = read(joinpath(@__DIR__, "../../assets/ui.html"), String)
html_page = replace(html_page, "{{model_name}}" => model_name)
html_page = replace(html_page, "{{regular_params_form}}" => forms_html)
html_page = replace(html_page, "{{target_dropdown}}" => target_html)

Check warning on line 161 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L158-L161

Added lines #L158 - L161 were not covered by tests

return html(html_page)

Check warning on line 163 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L163

Added line #L163 was not covered by tests
end

# At simulation
route("/simulate", method = POST) do

Check warning on line 167 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L167

Added line #L167 was not covered by tests
# Model
model_params = OrderedDict{Symbol, Any}()
datetime_params = OrderedDict{Symbol, Any}()

Check warning on line 170 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L169-L170

Added lines #L169 - L170 were not covered by tests

# Clock
clock_params = OrderedDict{Symbol, Any}()

Check warning on line 173 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L173

Added line #L173 was not covered by tests

# Calendar
calendar_params = OrderedDict{Symbol, Any}()

Check warning on line 176 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L176

Added line #L176 was not covered by tests

payload = jsonpayload() # JSONPAYLOAD = JULIA DICT FORMAT
step_unit = payload["Clock__unit_skip"]

Check warning on line 179 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L178-L179

Added lines #L178 - L179 were not covered by tests

for (key, value) in payload
if key == "selected_targets"
continue

Check warning on line 183 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L181-L183

Added lines #L181 - L183 were not covered by tests
end

parts = split(key, "__")
system_key = Symbol(parts[1])
param_key = Symbol(parts[2])

Check warning on line 188 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L186-L188

Added lines #L186 - L188 were not covered by tests

if occursin("_skip", key)
continue

Check warning on line 191 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L190-L191

Added lines #L190 - L191 were not covered by tests
end

if system_key == :Clock

Check warning on line 194 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L194

Added line #L194 was not covered by tests
# Handle Clock system parameters
clock_params[:Clock] = OrderedDict{Symbol, Any}()

Check warning on line 196 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L196

Added line #L196 was not covered by tests

if step_unit == "day"
clock_params[:Clock][param_key] = parse(Float64, string(value)) * u"d"

Check warning on line 199 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L198-L199

Added lines #L198 - L199 were not covered by tests
else
clock_params[:Clock][param_key] = parse(Float64, string(value))

Check warning on line 201 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L201

Added line #L201 was not covered by tests
end

elseif system_key == :Calendar && length(parts) == 3 && parts[3] in ["year", "month", "day", "timezone"]

Check warning on line 204 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L204

Added line #L204 was not covered by tests
# Handle Calendar system datetime parameters
if !haskey(calendar_params, param_key)
calendar_params[param_key] = OrderedDict{String, Any}()

Check warning on line 207 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L206-L207

Added lines #L206 - L207 were not covered by tests
end
calendar_params[param_key][parts[3]] = value

Check warning on line 209 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L209

Added line #L209 was not covered by tests

elseif length(parts) == 2

Check warning on line 211 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L211

Added line #L211 was not covered by tests
# Handle regular parameters
if !haskey(model_params, system_key)
model_params[system_key] = OrderedDict{Symbol, Any}()

Check warning on line 214 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L213-L214

Added lines #L213 - L214 were not covered by tests
end
try
model_params[system_key][param_key] = parse(Float64, string(value))

Check warning on line 217 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L216-L217

Added lines #L216 - L217 were not covered by tests
catch e
model_params[system_key][param_key] = string(value)

Check warning on line 219 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L219

Added line #L219 was not covered by tests
end

elseif length(parts) == 3 && parts[3] in ["year", "month", "day", "timezone"]
if !haskey(datetime_params, system_key)
datetime_params[system_key] = OrderedDict{Symbol, Dict{Symbol, Any}}()

Check warning on line 224 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L222-L224

Added lines #L222 - L224 were not covered by tests
end
if !haskey(datetime_params[system_key], param_key)
datetime_params[system_key][param_key] = OrderedDict{String, Any}()

Check warning on line 227 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L226-L227

Added lines #L226 - L227 were not covered by tests
end
datetime_params[system_key][param_key][parts[3]] = value

Check warning on line 229 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L229

Added line #L229 was not covered by tests

# elseif parts[end] == "enum"
# if !haskey(model_params, system_key)
# model_params[system_key] = OrderedDict{Symbol, Any}()
# end
# model_params[system_key][param_key] = value |> Symbol |> eval

elseif parts[end] == "file"

Check warning on line 237 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L237

Added line #L237 was not covered by tests
# Handle file upload parameters
if !haskey(model_params, system_key)
model_params[system_key] = OrderedDict{Symbol, Any}()

Check warning on line 240 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L239-L240

Added lines #L239 - L240 were not covered by tests
end

# println(value))

if payload["$(key)_extension_skip"] == "wea"

Check warning on line 245 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L245

Added line #L245 was not covered by tests
# println("WEA file content:", value)
model_params[system_key][param_key] = loadwea(IOBuffer(value), tz"America/Los_Angeles")
elseif payload["$(key)_extension_skip"] == "csv"
model_params[system_key][param_key] = CSV.read(IOBuffer(value), DataFrame)

Check warning on line 249 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L247-L249

Added lines #L247 - L249 were not covered by tests
end
end
end

Check warning on line 252 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L252

Added line #L252 was not covered by tests

calendar = OrderedDict{Symbol, Any}()
calendar[:Calendar] = OrderedDict{Symbol, Any}()
for (param_key, datetime_dict) in calendar_params
year = parse(Int, datetime_dict["year"])
month = parse(Int, datetime_dict["month"])
day = parse(Int, datetime_dict["day"])
timezone = datetime_dict["timezone"]
datetime_value = ZonedDateTime(year, month, day, TimeZone(timezone))
calendar[:Calendar][param_key] = datetime_value
end

Check warning on line 263 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L254-L263

Added lines #L254 - L263 were not covered by tests

for (system_key, params) in datetime_params
for (param_key, datetime_dict) in params
year = parse(Int, datetime_dict["year"])
month = parse(Int, datetime_dict["month"])
day = parse(Int, datetime_dict["day"])
timezone = datetime_dict["timezone"]
datetime_value = ZonedDateTime(year, month, day, TimeZone(timezone))
model_params[system_key] = get(model_params, system_key, OrderedDict{Symbol, Any}())
model_params[system_key][param_key] = datetime_value
end
end

Check warning on line 275 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L265-L275

Added lines #L265 - L275 were not covered by tests

config = @config (model_params, clock_params, calendar)

Check warning on line 277 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L277

Added line #L277 was not covered by tests

kwargs = Dict{Symbol, Any}()

Check warning on line 279 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L279

Added line #L279 was not covered by tests

if haskey(payload, "Options__stop_number_skip")
stop_value = parse(Float64, payload["Options__stop_number_skip"])
if payload["Options__stop_unit_skip"] == "hour"
kwargs[:stop] = stop_value * u"hr"
elseif payload["Options__stop_unit_skip"] == "day"
kwargs[:stop] = stop_value * u"d"
elseif payload["Options__stop_unit_skip"] == "year"
kwargs[:stop] = stop_value * u"yr"

Check warning on line 288 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L281-L288

Added lines #L281 - L288 were not covered by tests
end
end

if haskey(payload, "Options__snap_number_skip")
snap_value = parse(Float64, payload["Options__snap_number_skip"])
if payload["Options__snap_unit_skip"] == "hour"
kwargs[:snap] = snap_value * u"hr"
elseif payload["Options__snap_unit_skip"] == "day"
kwargs[:snap] = snap_value * u"d"

Check warning on line 297 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L292-L297

Added lines #L292 - L297 were not covered by tests
end
end

if haskey(payload, "Options__index_skip")
if occursin(".", payload["Options__index_skip"])
kwargs[:index] = payload["Options__index_skip"]

Check warning on line 303 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L301-L303

Added lines #L301 - L303 were not covered by tests
else
kwargs[:index] = Symbol(payload["Options__index_skip"])

Check warning on line 305 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L305

Added line #L305 was not covered by tests
end
end

if !isempty(payload["selected_targets"])
kwargs[:target] = collect(payload["selected_targets"])

Check warning on line 310 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L309-L310

Added lines #L309 - L310 were not covered by tests
end

# if haskey(payload, "Snap__number_skip")
# kwargs[:target] = [:GDD, :cGDD]
# end

df = simulate(S; config=config, kwargs...) |> deunitfy

Check warning on line 317 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L317

Added line #L317 was not covered by tests

# Convert the DataFrame to a JSON-friendly format
columns = names(df)
for column in columns
replace!(df[!, column], Inf => 1e308)
replace!(df[!, column], -Inf => -1e308)
end
data = [collect(row) for row in eachrow(df)]
result = Dict("columns" => columns, "data" => data)

Check warning on line 326 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L320-L326

Added lines #L320 - L326 were not covered by tests

return Json.json(Dict("status" => "Simulation complete", "results" => result))

Check warning on line 328 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L328

Added line #L328 was not covered by tests
end

up(async = false)

Check warning on line 331 in src/util/serve.jl

View check run for this annotation

Codecov / codecov/patch

src/util/serve.jl#L331

Added line #L331 was not covered by tests
end

0 comments on commit 0be7e4e

Please sign in to comment.