Back
GuidesArea Development ReportingReporting the 16th Street reopening in Denver
Shell
USA

Reporting the 16th Street reopening in Denver

This example shows how to report a street intervention with the before-and-after lens of Area Development Reporting: Denver's 16th Street, the mile-long downtown transit mall, was rebuilt in phases with the reopening completing through 2025. We measure the corridor against the metro as benchmark, on 2024 and 2025 data.

For this we use our US Neighborhood metrics at Census Block Group grain, queried from the shell with curl and reshaped with jq. The same recipe reports any street, park, or district intervention.


1. Define the reporting area

The reporting area is a parameter: twelve block groups, listed below. The rule is a quarter mile from the Union Station to Broadway line, applied to the block-group centroids in census_block_groups. Any rule works as long as it is stated.

CORRIDOR='["080310017051","080310017041","080310017043","080310017061",
           "080310017072","080310026041","080310017042","080310017062",
           "080310017052","080310017054","080310017071","080310027041"]'

2. Corridor and benchmark, month by month

foot_traffic_month gives one row per block group per month. Two grouped queries, one filtered to the corridor and one to the whole metro, return a monthly series each. jq turns the response into month<TAB>visits lines:

query() {  # $1 = filter JSON
  curl -s -X POST "https://api.pine59.com/v3/datasets/neighborhood_visitation.foot_traffic_month:runQuery" \
    -H "Authorization: Bearer $PINE59_API_KEY" -H "Content-Type: application/json" \
    -d '{
      "fields": [{ "name": "observation_start_date" },
                 { "name": "visits_sum", "aggregation": "SUM" }],
      "filters": ['"$1"',
                  { "fieldName": "observation_start_date", "operator": ">=", "value": "2024-01-01" },
                  { "fieldName": "observation_start_date", "operator": "<",  "value": "2026-01-01" }],
      "groupBy": [{ "fieldName": "observation_start_date" }],
      "pageSize": 100 }' \
  | jq -r '.records[].fields | [.observation_start_date[:7], (.visits_sum_SUM | tonumber)] | @tsv' \
  | sort
}

query '{ "fieldName": "location_id", "operator": "in", "values": '"$CORRIDOR"' }' > corridor.tsv
query '{ "fieldName": "us_cbsa", "operator": "==", "value": "Denver-Aurora-Lakewood, CO Metro Area" }' > metro.tsv

paste corridor.tsv metro.tsv \
  | awk -F'\t' 'BEGIN { print "month\tcorridor\tmetro\tshare_permille" }
                { printf "%s\t%.2fM\t%.0fM\t%.2f\n", $1, $2/1e6, $4/1e6, $2/$4*1000 }' \
  | column -t

The last column is the read: the corridor's share of all metro visits, in per mille. Dividing by the benchmark every month cancels whatever moves the whole metro at once (season, panel, events), and leaves what is specific to the corridor. The 24 monthly rows summarize into the periods of the rebuild with one more awk pass, averaging visits and share per period:

paste corridor.tsv metro.tsv \
  | awk -F'\t' '
      { y = substr($1, 1, 4); m = substr($1, 6, 2) + 0 }
      y == "2024"                       { p = "2024 average" }
      y == "2025" && m <= 5             { p = "Jan-May 2025" }
      y == "2025" && m >= 6 && m <= 9   { p = "Jun-Sep 2025" }
      y == "2025" && m >= 10 && m <= 11 { p = "Oct-Nov 2025" }
      y == "2025" && m == 12            { next }   # partial month, left out
      { c[p] += $2; v[p] += $4; s[p] += $2 / $4 * 1000; n[p]++ }
      END { for (p in c) printf "%s\t%.2fM\t%.0fM\t%.2f\n", p, c[p]/n[p]/1e6, v[p]/n[p]/1e6, s[p]/n[p] }' \
  | sort | column -t
PeriodCorridor, monthly averageMetro, monthly averageCorridor share (per mille)
2024 average4.10M389M10.55
Jan–May 20253.83M371M10.35
Jun–Sep 20253.55M367M9.68
Oct–Nov 20254.41M423M10.44

How to read it: through 2024 the corridor holds a steady 10 to 11 per mille of the metro. The share dips through the final construction phase, bottoming in June to September 2025 at 9.7, 8% below its 2024 level, and is back within 1% of the 2024 level in October and November, after the reopening. The share is the instrument here, because the raw series carry two things the ratio cancels:

Two data notes. Metro visits step from 344M in June 2024 to 435M in July 2024 and stay at the higher level, a data step rather than a Denver event; it hits corridor and metro alike and cancels in the share, but any comparison across it (a 2025 month against a pre-July 2024 month) compares across the step. And December 2025 is a partial month for the metro, with 1,912 of 2,048 block groups reporting; the corridor's twelve are all present, so we leave December out of every period and every year-over-year figure below rather than compare a complete corridor against an incomplete benchmark.

3. Year over year, with the benchmark attached

The same two files give the identical-months comparison a report is usually asked for. One jq pass sums each quarter and pairs 2025 with the same quarter of 2024, December excluded on both sides:

yoy() {  # $1 = tsv of month<TAB>visits; prints quarter<TAB>change
  jq -R -s -r '
    # 1. one object per line: year, quarter, visits (December left out, see above)
    split("\n") | map(select(length > 0) | split("\t")
      | select(.[0][5:7] != "12")
      | { y: .[0][:4], q: (((.[0][5:7] | tonumber) - 1) / 3 | floor + 1), v: (.[1] | tonumber) })
    # 2. sum visits per year and quarter
    | group_by([.y, .q]) | map({ y: .[0].y, q: .[0].q, v: (map(.v) | add) })
    # 3. pair each quarter of 2025 with the same quarter of 2024
    | group_by(.q) | map(select(length == 2) | { q: .[0].q, change: (.[1].v / .[0].v - 1) * 100 })
    # 4. print
    | .[] | "Q\(.q)\t\((.change * 10 | round) / 10)%"' "$1"
}

paste <(yoy corridor.tsv) <(yoy metro.tsv) \
  | awk -F'\t' 'BEGIN { print "quarter\tcorridor\tmetro" } { print $1"\t"$2"\t"$4 }' \
  | column -t

Result, 2025 vs. 2024 on identical months (Q4 is October and November):

QuarterCorridorMetroSpread
Q1+7.1%+12.1%−5.0 pts
Q2+0.8%+4.0%−3.2 pts
Q3−20.9%−14.3%−6.6 pts
Q4−3.5%−4.8%+1.3 pts

How to read it: read the spread, not the raw change. The metro itself swings between +12% and −14% across the year, so the corridor's own year-over-year numbers say little on their own. Against the benchmark the corridor trails by 3 to 7 points while the work is finishing and pulls ahead in the fourth quarter, the first quarter after the full reopening. The share table in section 2 carries the same finding on a cleaner instrument.

Q1 and Q2 compare 2025 with the months before the July 2024 data step; they are shown for completeness, and their large positive metro figures are mostly that step. Q3 and Q4 sit entirely after it.

4. Mix and audience

Two more lines complete the report, from the same corridor filter. Destination share, visitors who neither live nor work in the corridor, comes from non_resident_non_worker_visits_sum in the same dataset:

curl -s -X POST "https://api.pine59.com/v3/datasets/neighborhood_visitation.foot_traffic_month:runQuery" \
  -H "Authorization: Bearer $PINE59_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "fields": [{ "name": "observation_start_date" },
               { "name": "visits_sum", "aggregation": "SUM" },
               { "name": "non_resident_non_worker_visits_sum", "aggregation": "SUM" }],
    "filters": [{ "fieldName": "location_id", "operator": "in", "values": '"$CORRIDOR"' },
                { "fieldName": "observation_start_date", "operator": ">=", "value": "2025-10-01" },
                { "fieldName": "observation_start_date", "operator": "<",  "value": "2026-01-01" }],
    "groupBy": [{ "fieldName": "observation_start_date" }],
    "pageSize": 12 }' \
| jq -r '.records[].fields
         | "\(.observation_start_date[:7])\t\((.non_resident_non_worker_visits_sum_SUM | tonumber) / (.visits_sum_SUM | tonumber) * 100 | round)%"' \
| sort

The audience line comes from census_visitor_demographics, which is quarterly at this grain; the same corridor filter on the fourth quarters of 2024 and 2025 gives the young-adult share:

for quarter in 2024-10-01 2025-10-01; do
  curl -s -X POST "https://api.pine59.com/v3/datasets/neighborhood_visitation.census_visitor_demographics:runQuery" \
    -H "Authorization: Bearer $PINE59_API_KEY" -H "Content-Type: application/json" \
    -d '{
      "fields": [{ "name": "people_fraction_age_18_29", "aggregation": "AVG" }],
      "filters": [{ "fieldName": "location_id", "operator": "in", "values": '"$CORRIDOR"' },
                  { "fieldName": "observation_start_date", "operator": "==", "value": "'"$quarter"'" }] }' \
  | jq -r --arg q "$quarter" '.records[0].fields | "\($q[:7])\t\(.people_fraction_age_18_29_AVG | tonumber * 1000 | round / 10)%"'
done

How to read it: destination share is 85% to 87% in every month of the fourth quarter of 2025, the same band as through 2024: the corridor's traffic is people choosing to come, before and after the rebuild. The young-adult visitor share was 30.6% in the fourth quarter of 2025 against 33.7% a year earlier, so the audience has not yet returned to its 2024 mix even though the volume has.

5. The report

Four lines, one period, benchmark attached. The format repeats each quarter:

Corridor share of metro visits back within 1% of its 2024 level (10.4 against 10.6 per mille) in October and November 2025 after bottoming at 9.7 through the final construction phase (June to September 2025). Fourth-quarter visits to November −3.5% year over year against a metro at −4.8%, the first quarter ahead of the benchmark since the work began. Destination share steady at 85% to 87%. Young-adult share 30.6%, 3 points below a year earlier.

What the report does not claim: that the reopening caused the fourth-quarter turn. Events, openings, and downtown's broader arc all moved in the same window. The report shows the corridor changed, when, and by how much against the city; the timing alignment with the reopening is evidence, stated as exactly that.

Did you find what you were looking for?