A public dashboard for neXco networking chapters that displays weekly metrics from Google Sheets data, including guest attendance tracking, RSVP counts, follow-up assignments, and open seat availability.
This system provides config-driven multi-tenancy where the same code serves all chapters with only configuration differences. Data is refreshed weekly via cron job and served from static JSON files.
- Guest attendance tracking (attended vs. no-show)
- RSVP counts per meeting
- Follow-up assignments per member with intelligent name matching
- Open seat availability display
- Meeting date snapping to 2nd/4th Wednesdays
- Nickname and partial name matching (e.g., "Dan" → "Daniel Lyon")
┌─────────────────────────────────────────────────────────────────┐
│ Weekly Cron Job │
│ (Thursday 11:00 AM ET) │
└───────────────────────────┬─────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Google Sheets │ │ Google Sheets │ │ neXco Roster │
│ (CSV export) │ │ (CSV export) │ │ (HTML scrape)│
│ RSVPs tab │ │ Guest Tracker │ │ Chapter page │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└───────────────────┼───────────────────┘
▼
┌───────────────────────┐
│ Python Refresh │
│ Script (refresh.py) │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ data.json │
│ (atomic write) │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ Static HTML App │
│ (index.html) │
└───────────────────────┘
refresh.py (orchestrator)
│
├── sheets_fetcher.py → Fetch CSV from Google Sheets
├── roster_scraper.py → Scrape member names from chapter pages
│
├── rsvp_processor.py → Parse, dedupe, filter RSVPs
├── guest_tracker_processor.py → Parse Guest Tracker records
│ └── meeting_calendar.py → Snap dates to 2nd/4th Wednesdays
│ └── date_utils.py → Parse dates with Y2K fix
│
├── data_merger.py → Join RSVP + Guest Tracker data
├── aggregator.py → Aggregate meetings for charts
│
├── name_matcher.py → Match follow-up names to roster
│ └── name_normalizer.py → Tokenize and normalize names
├── followup_widget.py → Calculate follow-up counts
│
├── json_builder.py → Build data.json structure
└── file_writer.py → Atomic write to output
- Data Fetching: CSV export from Google Sheets via gviz URL
- Date Handling: Dates snap to actual meeting dates (2nd/4th Wednesdays)
- RSVP Deduplication: Key = Email + Meeting Date (last row wins)
- Data Merge: Join RSVPs with Guest Tracker (Guest Tracker wins for follow-up/notes)
- Name Matching: Two-pass normalization with nickname support
The system uses sophisticated name matching to resolve follow-up assignments:
- Alias: Configured alias override (e.g., "Ron" → "Suzanne Parisi")
- Roster: Exact full name match
- First Name: Unique first name in roster
- Nickname: Common nickname expansion (e.g., "Dan" → "Daniel")
- Partial: Partial last name match (e.g., "Adam P" → "Adam Pressman")
- Light: Strip/collapse whitespace
- Heavy: Remove parentheticals, brackets, trailing annotations (
:,-, etc.)
Follow-up strings can use: comma (,), slash (/), or ampersand (&)
Example: "Dan/Luanne" → ["Dan", "Luanne"]
BB-NexcoChapterDashboard/
├── refresh.py # Main orchestrator
├── date_utils.py # Date parsing with Y2K fix
├── meeting_calendar.py # 2nd/4th Wednesday calculation
├── rsvp_processor.py # RSVP parsing/deduplication
├── guest_tracker_processor.py
├── data_merger.py # Join RSVP + Guest Tracker
├── aggregator.py # Meeting aggregation
├── name_normalizer.py # Tokenize/normalize names
├── name_matcher.py # Match names to roster
├── followup_widget.py # Follow-up count calculation
├── json_builder.py # Build output JSON
├── file_writer.py # Atomic file writes
├── roster_scraper.py # Scrape member names
└── frontend/
├── index.html # Dashboard HTML
├── dashboard.js # Dashboard logic
└── styles.css # neXco-branded styles
/home/adam/nexco-dashboard/
├── bin/ # Python scripts
├── config/chapters/ # Per-chapter JSON configs
└── logs/ # Weekly rotating logs
/home/adam/public_html/leadershipshape/nexco-dashboard/
├── index.html # Chapter index
├── assets/ # Shared CSS/JS/images
└── nexco-novacore/ # Chapter folder
├── index.html
└── data.json # Generated weekly
{
"chapter_slug": "nexco-novacore",
"display_name": "NOVA Core (B2C)",
"page_title": "neXco NOVA Core Dashboard",
"sheet_url": "https://docs.google.com/spreadsheets/d/...",
"roster_url": "https://members.nexconational.com/nova-core/",
"tab_names": {
"rsvps": "RSVPs",
"guest_tracker": "Guest Tracker",
"open_seats": "Top 3 Open Seats"
},
"aliases": {
"Ron": "Suzanne Parisi"
}
}Use aliases to:
- Map nicknames to full names
- Handle non-roster members (e.g., partners)
- Resolve ambiguous first names
- Python 3.12+ (dataclasses required)
- No external Python packages (stdlib only)
- Google Sheets must be publicly accessible
ssh eve@inmotion
sudo -u adam /usr/bin/python3.12 /home/adam/nexco-dashboard/bin/refresh.pyCRON_TZ=America/New_York
0 11 * * 4 /usr/bin/python3.12 /home/adam/nexco-dashboard/bin/refresh.py- Vanilla HTML/CSS/JavaScript
- Chart.js v4.5.1 (vendored)
- neXco brand colors (Navy #102048, Gold #d0a848)
- Month selector with Firefox fallback (dual dropdowns)
- Stacked bar chart (Attended vs No-Show)
- Follow-up counts widget with Prior Members section
- Guest tables with live filtering
- Stale data warning (>8 days old)
Production: https://leadershipshape.com/nexco-dashboard/nexco-novacore/
Proprietary - Business Builders / neXco National