BonQuery
  • Home
  • Left in the Cold
  • Audits
    • INDEPENDENT OVERSIGHT
    • Discrepancy Tracker
    • Emergency Occupancy Audit
    • Central Intake Audit
    • About the Data
  • Shelter Dashboards
    • Inside Toronto’s Shelter System
    • Monthly System Flow Replication
    • Year-to-Date Capacity Match
    • Historical Trend Lines
    • Referral Requests Analysis
    • Daily Occupancy & Capacity
  • About
  • FR

YTD Comparison

Toronto Shelter System — year-to-date cumulative trends

Year-to-date (YTD) comparison of Toronto’s shelter system — see whether the YTD selected in the dropdown is tracking above or below prior years on the inflow/outflow categories: Newly Identified and Moved to Permanent Housing.
Author

Miriam Marling

data = FileAttachment("../data/shelter_flow.json").json()
MON = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
fmtN = d => d.toLocaleString()

// Month dropdown: display "March 2026", return "2026-03-01", default = most recent.
reportingMonthOptions = data
  .filter(d => d.population_group === "All Population")
  .sort((a, b) => b.flow_date.localeCompare(a.flow_date))
  .map(d => {
    const [y, m] = d.flow_date.split("-").map(Number)
    return {
      label: new Date(y, m - 1, 15).toLocaleString("default", {month: "long", year: "numeric"}),
      value: d.flow_date
    }
  })
viewof reportingDate = Inputs.select(reportingMonthOptions, {
  label: "Reporting Month",
  format: d => d.label,
  value: reportingMonthOptions[0]
})
reportingYear  = +reportingDate.value.slice(0, 4)
reportingMonth = +reportingDate.value.slice(5, 7)

// YTD cumulative sums for current year and prior year through selected month.
ytdComputed = {
  const rows = data
    .filter(d => d.population_group === "All Population")
    .filter(d => {
      const y = +d.flow_date.slice(0, 4)
      const m = +d.flow_date.slice(5, 7)
      return (y === reportingYear || y === reportingYear - 1) && m <= reportingMonth
    })
    .sort((a, b) => a.flow_date.localeCompare(b.flow_date))

  const accNI = {}, accMH = {}
  return rows.map(d => {
    const y = +d.flow_date.slice(0, 4)
    const m = +d.flow_date.slice(5, 7)
    accNI[y] = (accNI[y] || 0) + d.newly_identified
    accMH[y] = (accMH[y] || 0) + d.moved_to_housing
    return {
      year: y, month: m,
      monthLabel: MON[m - 1],
      yearLabel: String(y),
      isCurrentYear: y === reportingYear,
      ytd_ni: accNI[y],
      ytd_mh: accMH[y]
    }
  })
}

// Sort: by month, prior year first within each month — creates visual bar grouping
ytdSorted = ytdComputed
  .sort((a, b) => a.month - b.month || a.year - b.year)
  .map(d => ({...d, barLabel: `${d.monthLabel} ${d.yearLabel}`}))

ytdDomain = ytdSorted.map(d => d.barLabel)

Cumulative YTD comparison: Newly Identified

Plot.plot({
  width,
  height: 60 + ytdSorted.length * 22,
  marginLeft: 100,
  marginRight: 80,
  x: {label: "Cumulative newly identified (YTD)", grid: true, tickFormat: fmtN},
  y: {label: null, domain: ytdDomain},
  color: {
    domain: [String(reportingYear - 1), String(reportingYear)],
    range: ["#FFB3C1", "#FF2D55"],
    legend: true
  },
  marks: [
    Plot.ruleX([0]),
    Plot.barX(ytdSorted, {x: "ytd_ni", y: "barLabel", fill: "yearLabel", tip: true,
                          insetTop: 1, insetBottom: 1}),
    Plot.text(ytdSorted, {
      x: "ytd_ni", y: "barLabel",
      text: d => d.ytd_ni.toLocaleString(),
      textAnchor: "start", dx: 5, fontSize: 11
    })
  ]
})

Cumulative YTD comparison: Moved to Permanent Housing

Plot.plot({
  width,
  height: 60 + ytdSorted.length * 22,
  marginLeft: 100,
  marginRight: 80,
  x: {label: "Cumulative moved to permanent housing (YTD)", grid: true, tickFormat: fmtN},
  y: {label: null, domain: ytdDomain},
  color: {
    domain: [String(reportingYear - 1), String(reportingYear)],
    range: ["#AED5AE", "#5BA75B"],
    legend: true
  },
  marks: [
    Plot.ruleX([0]),
    Plot.barX(ytdSorted, {x: "ytd_mh", y: "barLabel", fill: "yearLabel", tip: true,
                          insetTop: 1, insetBottom: 1}),
    Plot.text(ytdSorted, {
      x: "ytd_mh", y: "barLabel",
      text: d => d.ytd_mh.toLocaleString(),
      textAnchor: "start", dx: 5, fontSize: 11
    })
  ]
})

Explore other dashboards:

  • Historical Trends
  • Monthly Snapshot

This dashboard is also available as an Oracle APEX dashboard built on the same database — useful if you want to see how the same data renders in Oracle’s native low-code platform, or if you’re curious about the SQL and APEX configuration behind it.

Key terms

Inflow categories (people entering the shelter system this month):

  • Newly Identified. People entering the shelter system for the first time. One exception for the “Chronic” group: in that row, this column counts people who became chronically homeless during the reporting month, regardless of how long they’d already been using the shelter system.

Outflow categories (people leaving the shelter system this month):

  • Moved to Permanent Housing. People who left the shelter system for permanent housing.

Definitions are based on the City of Toronto’s Shelter System Flow Data page.

Read all data findings →

Data source

All data comes from the City of Toronto’s Shelter System Flow dataset, published monthly on the City’s open data portal.

Contains information licensed under the Open Government Licence – Toronto.

 

An independent data audit for Toronto residents. / Un audit de données indépendant pour les résidents de Toronto.
© 2026 Miriam Marling · BonQuery