Schedule Your Evaluation
For Providers
Provider Login
Access your monthly calendar and view your daily schedule hour by hour.
Login
Blue: scheduled
Orange: open spots
Green: completed
Red: rescheduled/cancelled
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
For Providers
Provider Login
Access your Nova Neuro provider calendar.
Login
Choose Action
Early intervention enables faster recovery; secure an appointment to initiate a tailored rehabilitation program.
Schedule Your Neurology Appointment
Enter your name and date of birth so we can locate your referral.
import express from "express";
import cors from "cors";
import crypto from "crypto";
const app = express();
app.use(express.json());
app.use(
cors({
origin: [
"[your-squarespace-domain.com](https://YOUR-SQUARESPACE-DOMAIN.com)",
"[your-squarespace-domain.com](https://www.YOUR-SQUARESPACE-DOMAIN.com)"
],
methods: ["GET", "POST"],
allowedHeaders: ["Content-Type"]
})
);
const {
MONDAY_API_TOKEN,
REFERRALS_BOARD_ID,
AVAILABILITY_BOARD_ID,
PATIENT_NAME_COLUMN_ID,
DOB_COLUMN_ID,
REFERRAL_STATUS_COLUMN_ID,
STATE_COLUMN_ID,
SCHEDULED_PROVIDER_COLUMN_ID,
APPOINTMENT_DATETIME_COLUMN_ID,
VISIT_TYPE_COLUMN_ID,
DOXY_LINK_COLUMN_ID,
SCHEDULING_SOURCE_COLUMN_ID,
SCHEDULED_AT_COLUMN_ID,
PROVIDER_NAME_COLUMN_ID,
PROVIDER_STATE_COLUMN_ID,
SLOT_DATETIME_COLUMN_ID,
SLOT_DURATION_COLUMN_ID,
SLOT_STATUS_COLUMN_ID,
SLOT_DOXY_LINK_COLUMN_ID
} = process.env;
const MONDAY_API_URL = "[api.monday.com](https://api.monday.com/v2)";
/**
* In production, store referral tokens in Redis/database with expiration.
* This in-memory store is only for first-pass testing.
*/
const referralTokenStore = new Map();
function createReferralToken(referralItemId) {
const token = crypto.randomBytes(32).toString("hex");
referralTokenStore.set(token, {
referralItemId,
createdAt: Date.now()
});
return token;
}
function getReferralFromToken(token) {
const record = referralTokenStore.get(token);
if (!record) return null;
const tokenAgeMinutes = (Date.now() - record.createdAt) / 1000 / 60;
if (tokenAgeMinutes > 20) {
referralTokenStore.delete(token);
return null;
}
return record;
}
async function mondayRequest(query, variables = {}) {
const response = await fetch(MONDAY_API_URL, {
method: "POST",
headers: {
Authorization: MONDAY_API_TOKEN,
"Content-Type": "application/json"
},
body: JSON.stringify({
query,
variables
})
});
const data = await response.json();
if (!response.ok || data.errors) {
console.error("Monday API error:", JSON.stringify(data, null, 2));
throw new Error("Monday API request failed");
}
return data.data;
}
function normalizeText(value) {
return String(value || "")
.trim()
.toLowerCase()
.replace(/\s+/g, " ");
}
function normalizeDob(value) {
if (!value) return "";
// Handles YYYY-MM-DD directly.
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
return value;
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return "";
}
return date.toISOString().slice(0, 10);
}
function getColumnText(item, columnId) {
const column = item.column_values.find(col => col.id === columnId);
return column?.text || "";
}
function isNameMatch(inputFirst, inputLast, mondayFullName) {
const cleanFullName = normalizeText(mondayFullName);
const parts = cleanFullName.split(" ").filter(Boolean);
if (parts.length < 2) return false;
const mondayFirst = parts[0];
const mondayLast = parts[parts.length - 1];
const inputFirstClean = normalizeText(inputFirst);
const inputLastClean = normalizeText(inputLast);
const firstMatches =
inputFirstClean === mondayFirst ||
inputFirstClean === mondayFirst.charAt(0);
const lastMatches =
inputLastClean === mondayLast ||
inputLastClean === mondayLast.charAt(0);
return firstMatches && lastMatches;
}
function isActiveReferralStatus(statusText) {
const status = normalizeText(statusText);
return [
"new",
"active",
"pending",
"ready to schedule",
"unscheduled"
].includes(status);
}
/**
* POST /api/referrals/match-by-name-dob
*
* Matches:
* - Patient Name column
* - DOB column
*
* Allows:
* - John Smith
* - J Smith
* - John S
* - J S
*/
app.post("/api/referrals/match-by-name-dob", async (req, res) => {
try {
const { firstNameOrInitial, lastNameOrInitial, dob } = req.body;
if (!firstNameOrInitial || !lastNameOrInitial || !dob) {
return res.status(400).json({
message: "Missing required fields"
});
}
const normalizedInputDob = normalizeDob(dob);
if (!normalizedInputDob) {
return res.status(400).json({
message: "Invalid DOB"
});
}
const query = `
query GetReferralItems($boardId: [ID!]) {
boards(ids: $boardId) {
items_page(limit: 500) {
items {
id
name
column_values {
id
text
value
}
}
}
}
}
`;
const data = await mondayRequest(query, {
boardId: [String(REFERRALS_BOARD_ID)]
});
const items = data.boards?.[0]?.items_page?.items || [];
const matches = items.filter(item => {
const patientName = getColumnText(item, PATIENT_NAME_COLUMN_ID);
const mondayDob = normalizeDob(getColumnText(item, DOB_COLUMN_ID));
const referralStatus = getColumnText(item, REFERRAL_STATUS_COLUMN_ID);
const nameMatches = isNameMatch(
firstNameOrInitial,
lastNameOrInitial,
patientName
);
const dobMatches = mondayDob === normalizedInputDob;
const statusActive = isActiveReferralStatus(referralStatus);
return nameMatches && dobMatches && statusActive;
});
/**
* Security: only proceed if exactly one referral matched.
* Do not reveal whether there were zero or multiple.
*/
if (matches.length !== 1) {
return res.status(404).json({
message: "Referral not verified"
});
}
const referralToken = createReferralToken(matches[0].id);
return res.json({
referralToken
});
} catch (error) {
console.error(error);
return res.status(500).json({
message: "Unable to verify referral"
});
}
});
/**
* GET /api/availability/slots-by-state
*
* Returns available provider slots filtered by:
* - state
* - duration
* - optional date
*/
app.get("/api/availability/slots-by-state", async (req, res) => {
try {
const { referralToken, state, duration, date } = req.query;
const referralRecord = getReferralFromToken(referralToken);
if (!referralRecord) {
return res.status(401).json({
message: "Invalid or expired session"
});
}
if (!state || !duration) {
return res.status(400).json({
message: "Missing state or duration"
});
}
const query = `
query GetAvailabilityItems($boardId: [ID!]) {
boards(ids: $boardId) {
items_page(limit: 500) {
items {
id
name
column_values {
id
text
value
}
}
}
}
}
`;
const data = await mondayRequest(query, {
boardId: [String(AVAILABILITY_BOARD_ID)]
});
const items = data.boards?.[0]?.items_page?.items || [];
const requestedState = normalizeText(state);
const requestedDuration = String(duration);
const slots = items
.filter(item => {
const providerState = normalizeText(
getColumnText(item, PROVIDER_STATE_COLUMN_ID)
);
const slotDuration = getColumnText(item, SLOT_DURATION_COLUMN_ID);
const slotStatus = normalizeText(
getColumnText(item, SLOT_STATUS_COLUMN_ID)
);
const slotDateTimeText = getColumnText(
item,
SLOT_DATETIME_COLUMN_ID
);
const slotDate = normalizeDob(slotDateTimeText);
const stateMatches = providerState === requestedState;
const durationMatches = String(slotDuration) === requestedDuration;
const statusAvailable = slotStatus === "available";
const dateMatches = date ? slotDate === normalizeDob(date) : true;
return (
stateMatches &&
durationMatches &&
statusAvailable &&
dateMatches
);
})
.map(item => {
const providerName = getColumnText(item, PROVIDER_NAME_COLUMN_ID);
const slotDateTimeText = getColumnText(item, SLOT_DATETIME_COLUMN_ID);
return {
id: item.id,
providerId: providerName,
providerName,
displayDate: slotDateTimeText,
displayTime: slotDateTimeText,
displayDateTime: slotDateTimeText,
duration: requestedDuration
};
});
return res.json({
slots
});
} catch (error) {
console.error(error);
return res.status(500).json({
message: "Unable to load availability"
});
}
});
/**
* POST /api/appointments/book-from-referral
*
* Books selected availability slot by:
* - marking slot as Booked
* - updating referral as Scheduled
*/
app.post("/api/appointments/book-from-referral", async (req, res) => {
try {
const {
referralToken,
slotId,
providerId,
state,
visitType,
duration
} = req.body;
const referralRecord = getReferralFromToken(referralToken);
if (!referralRecord) {
return res.status(401).json({
message: "Invalid or expired session"
});
}
if (!slotId || !state || !visitType || !duration) {
return res.status(400).json({
message: "Missing booking fields"
});
}
/**
* Re-fetch the selected slot to verify it is still available.
*/
const slotQuery = `
query GetSlot($ids: [ID!]) {
items(ids: $ids) {
id
name
column_values {
id
text
value
}
}
}
`;
const slotData = await mondayRequest(slotQuery, {
ids: [String(slotId)]
});
const slot = slotData.items?.[0];
if (!slot) {
return res.status(404).json({
message: "Slot not found"
});
}
const slotStatus = normalizeText(
getColumnText(slot, SLOT_STATUS_COLUMN_ID)
);
if (slotStatus !== "available") {
return res.status(409).json({
message: "Slot no longer available"
});
}
const providerName =
getColumnText(slot, PROVIDER_NAME_COLUMN_ID) ||
providerId ||
"Neurology Provider";
const appointmentDateTime =
getColumnText(slot, SLOT_DATETIME_COLUMN_ID) || "";
const doxyLink =
getColumnText(slot, SLOT_DOXY_LINK_COLUMN_ID) || "";
/**
* Update availability slot to Booked.
*/
await changeSimpleColumnValues(AVAILABILITY_BOARD_ID, slotId, {
[SLOT_STATUS_COLUMN_ID]: "Booked"
});
/**
* Update referral item to Scheduled.
*/
await changeSimpleColumnValues(REFERRALS_BOARD_ID, referralRecord.referralItemId, {
[REFERRAL_STATUS_COLUMN_ID]: "Scheduled",
[STATE_COLUMN_ID]: state,
[SCHEDULED_PROVIDER_COLUMN_ID]: providerName,
[APPOINTMENT_DATETIME_COLUMN_ID]: appointmentDateTime,
[VISIT_TYPE_COLUMN_ID]: `${visitType} - ${duration} minutes`,
[DOXY_LINK_COLUMN_ID]: doxyLink,
[SCHEDULING_SOURCE_COLUMN_ID]: "Website",
[SCHEDULED_AT_COLUMN_ID]: new Date().toISOString()
});
referralTokenStore.delete(referralToken);
return res.json({
status: "scheduled"
});
} catch (error) {
console.error(error);
return res.status(500).json({
message: "Unable to book appointment"
});
}
});
async function changeSimpleColumnValues(boardId, itemId, values) {
const mutation = `
mutation ChangeValues(
$boardId: ID!,
$itemId: ID!,
$columnValues: JSON!
) {
change_multiple_column_values(
board_id: $boardId,
item_id: $itemId,
column_values: $columnValues
) {
id
}
}
`;
return mondayRequest(mutation, {
boardId: String(boardId),
itemId: String(itemId),
columnValues: JSON.stringify(values)
});
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Scheduler backend running on port ${PORT}`);
});