Signal drop!
Relay (operand.online) is unreachable.
Usually, a dropped signal means an upgrade is happening. Hold on!
Sorry, no connección.
Hang in there while we get back on track
gram: pain
> ./lib/pain/schedule.ex
Lenses
(coming soon!)
defmodule Pain.Schedule do
@api "https://acuityscheduling.com/api/v1"
import Acuity, only: [headers: 0]
def ending, do: ":00-0400"
def log(node), do: node |> IO.inspect
def today, do: now() |> DateTime.to_date()
def now do
case DateTime.now("America/New_York") do
{:ok, c } -> c
{:error, _ } -> DateTime.utc_now()
end
end
def day(phrase), do: Date.from_iso8601(phrase) |> elem(1)
def range(day), do: Date.range(day, day)
def this_month do
Date.range(today(), today() |> Date.end_of_month)
end
def month(month) do
[y,m] = month |> String.split("-") |> Enum.map(&String.to_integer/1)
beginning = Date.new(y,m,1) |> elem(1) |> Date.beginning_of_month
Date.range(beginning, beginning |> Date.end_of_month)
end
def service_demand(menu_keys) do
menu_keys
|> Enum.reduce(%{}, fn x, acc -> Map.update(acc, x, 1, &(&1 + 1)) end)
end
def calendars() do
(@api <> "/calendars")
|> Req.get!(headers: headers())
|> Map.get(:body)
end
def employee_genders do
:pain
|> Application.app_dir("priv")
|> Path.join("employees.yml")
|> YamlElixir.read_from_file
|> elem(1)
|> Map.get("employees")
|> Enum.map(fn %{ "name" => n, "gender" => g} -> { n, g} end)
|> Map.new()
end
def employees do
genders = employee_genders()
Pain.Schedule.calendars() |> Enum.map(& %{
name: &1["name"],
biography: &1["description"] |> String.trim(),
schedule_key: &1["id"],
gender: genders[&1["name"]],
image: "https:" <> &1["image"],
})
end
def employee_keys, do: employees() |> Enum.map(&(&1[:schedule_key]))
def menu do
grouped_menu()["classes"] |> Enum.map(fn c ->
c["services"] |> Enum.map(&(Map.put(&1, "class", c["name"])))
end) |> List.flatten
end
def menu_keys(menu), do: menu |> Enum.map(& &1["schedule_key"])
def menu_keys, do: menu() |> menu_keys
def key_in_menu(name), do: (menu() |> Enum.find(& &1["name"] == name))["schedule_key"]
def grouped_menu do
{:ok, s} = (
:pain
|> Application.app_dir("priv")
|> Path.join("services.yml")
|> YamlElixir.read_from_file
); s
end
@doc """
import Pain.Schedule
menu_keys() |> Enum.take(3)
|> service_demand()
|> check_blocks(employee_keys(), this_month())
"""
def check_blocks demand, employee_keys, range do
(range |> Enum.take(14) |> Parallel.map(fn day ->
demand |> Parallel.map(fn { service, demand } ->
check_calendar_day_service(service, employee_keys, day)
|> reduce_calendars
|> Enum.filter(fn { _, num } -> num >= demand end)
|> Enum.map(fn { block, _ } -> block end)
end)
|> reduce_blocks
end))
end
def check_calendar_day_service service, employee_keys, day do
employee_keys
|> Enum.map(fn employee ->
search_hours = @api <> "/availability/times?date=#{day}&appointmentTypeID=#{service}&calendarID=#{employee}"
case Req.get(search_hours, headers: headers()) do
{:error, r} -> log r; []
{:ok, r = %Req.Response{status: 400}} -> log r; []
{:ok, r } -> r.body
end
end)
end
@doc """
Pain.Schedule.check_blocks_on_calendars(
"2023-08-28T14:00:00-0400",
menu_keys() |> Enum.take(3),
employee_keys()
)
"""
def check_blocks_on_calendars block, menu_keys, employee_keys do
address = @api <> "/availability/check-times"
menu = menu()
menu_keys |> Enum.map(fn m ->
body = (
employee_keys
|> Enum.map(fn employee -> %{
datetime: block,
appointmentTypeID: m,
calendarID: employee
} end)
)
case Req.post(address, json: body, headers: headers()) do
{:error, r} -> log r; []
{:ok, r = %Req.Response{status: 400}} -> log r; []
{:ok, r } -> r.body
end
end) |> Squish.squish
end
def menu do
Req.get!(@api <> "/appointment-types", headers: headers()).body
|> Enum.map(& { &1["id"], &1["calendarIDs"] })
end
def addons, do: Req.get!(@api <> "/appointment-addons", headers: headers()).body
def reduce_calendars cals do
cals |> Enum.reduce(%{}, fn calendar, all ->
calendar |> Enum.reduce(all, fn calBlock, cal ->
Map.update(cal, calBlock["time"], 0, &(&1 + calBlock["slotsAvailable"]))
end)
end)
end
def reduce_blocks [original | remaining] do
remaining |> Enum.reduce(MapSet.new(original), fn blocks, solid ->
MapSet.intersection MapSet.new(blocks), solid
end) |> MapSet.to_list
end
def blocks_by_day blocks do
blocks
|> Enum.filter(&(length(&1) > 0))
|> Enum.reduce(%{}, fn day, all ->
Map.put(all, (day |> hd |> String.split("T") |> hd), day)
end)
end
@doc """
import Pain.Schedule
( service_demand([ 39928578, 39928780, 39931669, ])
|> check_blocks(employee_keys(), this_month())
|> blocks_by_day()
)["2023-09-30"] |> hour_map()
"""
def hour_map(clocks) do
clocks
|> Enum.map(&( Regex.scan(~r/\d{2}:\d{2}/, &1) |> hd |> hd))
|> Enum.sort()
|> Enum.reduce(%{}, fn c, by_hour ->
[h | [m | []]] = c |> String.split(":")
Map.update(by_hour, h, [m], &(&1 ++ [m]))
end)
end
end