Schedule Your Evaluation

For Providers

Provider Login

Access your monthly calendar and view your daily schedule hour by hour.

Login

For Providers

Provider Login

Access your Nova Neuro provider calendar.

Login

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}`); });