Guide
How to use Mosaic, role by role.
Getting started
EveryoneMosaic matches a high school baseball player to colleges. You enter who the player is and what matters to the family. Mosaic returns every program in the region ranked by fit, with a read on whether the player's position has room and an estimate of the level where the numbers belong.
Signing in
Open the site and choose Sign in. Google works, and so does an email address. The first time in, you choose a role: Parent or Consultant. Player accounts are created by a parent or a consultant, not from the sign-in page.
The three roles
A consultant manages a list of players, edits any of their profiles, runs reports, and marks target schools.
A parent creates the family account, adds the player, and sees and edits the player's profile and reports.
A player completes the questionnaire and sees the report. A player cannot delete the account or change who it is linked to.
The four pages you will use
The dashboard lists players. The player page holds the profile and the preferences, with a card on the right showing the tier range and showcase numbers. The report page is the ranked school list. The help center explains every number.
Theme
The switch in the top right sets light, dark, or system. The choice stays on that browser.
For consultants
ConsultantAdding a player
On the dashboard, use Add a player. First and last name are enough to create the record. Position, grad year, and home zip make the first report useful, so add them if you have them. A parent email is optional; when that parent signs in, the player is linked to them automatically.
The profile
Open the player and fill in what you know. Showcase numbers set the tier range. GPA and intended major feed the academics and major factors. Height is a dropdown; every velocity, time, and weight field has its unit on the right and a sensible range, so a typo like a 690 exit velocity is caught before it saves.
The preferences
Each factor has an answer and a weight from 0 to 10. Set the weight with the family in the room if you can; the conversation about what matters is half the value of the tool. A weight of 0 removes the factor from the score. See the help article on weights for how to pick them.
Running a report
Click Run report on the player page. The report page opens at once with a progress bar and fills in after about ten seconds. If a run fails, the page says why and offers Retry. If you click Run report while one is already running, you are taken to the running one.
Reading and sharing
The report opens with the top school's ring and letter, five school cards, and then the full table. The tile strip next to each school is the fit broken into factors; hover for reasons. Print gives you a PDF for the family. Every report stays on the player page under Reports, so you can compare a run from last month with today's.
Linking a parent
On the player page, enter the parent's email under Link a parent. When that person signs in with the same address, they see the player. Until then the page says it is waiting.
For parents and players
FamilyYour account
A parent signs in and chooses Parent. If a consultant already added your player and entered your email, the player appears on your dashboard on your first sign-in. If not, you can add the player yourself.
What to fill in
The profile asks for the player's position, grad year, home zip, GPA, intended major, and showcase numbers. Enter the numbers from a showcase or a measured workout, not estimates. A guessed exit velocity moves the tier range and produces a list of the wrong schools.
The preferences ask what matters and how much. There are no right answers. A player who wants to be within an hour of home should say so and weight it high. A player who would go anywhere should weight distance low. The report is only as honest as the answers.
Reading the report
The number in the ring is the fit score for the top school, from 0 to 99. The letter is the grade: A is a strong match, B is good with a catch, C is a stretch. The strip of tiles next to each school shows which factors matched and which did not. Hover a tile to see why.
Playability tells you whether the player's position has room at that school next year. Open means room. Competitive means a normal fight for time. Blocked means established players ahead of you. No data means Mosaic has not read that roster yet.
What the report is not
It is not an offer, and it is not a prediction that a coach will call. It is the list of schools worth a visit, ranked by what your family said matters. Take the three schools you had never heard of and look them up. That is usually where the value is.
Getting help
The question mark next to any number opens the article that explains it. The help center at the top of every page has the rest.
For administrators
AdminThe admin area is at /admin and is visible only to accounts with the admin role.
Feature flags
Each flag takes effect the moment you save it. No deploy is needed. AI blurbs turns the short school notes on and off. Family reports controls whether parents and players can open reports. Roster cron controls the daily roster check. History cron (flags.history_cron_enabled) controls the daily batch of the newest two history seasons, and the insights run after it; it is on by default. Daily digest email (flags.daily_digest_enabled) controls the 12:00 UTC email; it is on by default. New signups controls whether the welcome screen lets a new account pick a role.
Configuration
Numbers and lists the scoring engine reads at run time. Blurb count is how many schools get a written note. Region states is the list of states in scope. Expected depth is the typical number of players a program carries per position, which playability compares against. JSON fields are checked before they save; a bad value shows an error and keeps the old one.
The switch scoring.archived_roster_fallback ("Use the archived roster when no live roster is parsed") is on by default. Newly generated reports use the newest archived roster only when a live roster is unavailable, and the playability reason names its season. Fit and rank follow that playability result as usual. Refreshing Player history on the data board now writes both the summary insights and the per-season counts used on school detail pages; its summary includes the number of season rows written. History visibility remains controlled by its existing flags.
Factors
The list of scoring factors with their labels, default weights, order on the questionnaire, and dropdown options. Edit a label here and it changes on every form. Adding a factor needs a code change, because a factor needs a scorer.
Users
Every account with its role. Change a role with the dropdown and Save. You cannot remove your own admin role. Roles are stored both here and on the sign-in provider, and Save updates both.
Data
Run roster pipeline checks a batch of roster pages now instead of waiting for the morning. Re-seed factors restores the factor list to the defaults in the code without touching player data.
Daily jobs and health
Five jobs run on a schedule. All times are UTC.
| Job | When | Switch that pauses it |
|---|---|---|
| Roster check | Daily at 09:00 | Roster cron |
| History import | Daily at 10:00 | History cron |
| Insights | Daily at 10:30 | History cron |
| Site discovery and logos | Sundays at 11:00 | Roster cron |
| Daily email | Daily at 12:00 | Daily digest email |
The Data health panel on the admin overview shows one card per job with a color and a word:
- On track (green): the job succeeded recently. A daily job is green when its last success is less than 24 hours old.
- Late (amber): no success for more than 24 hours on a daily job, or more than 7 days for site discovery. Insights are also amber when they are more than 36 hours old, and the roster check is amber when fewer than half of the readable schools were checked in the last 7 days.
- Needs attention (red): the newest run failed, or there has been no success for more than 48 hours on a daily job (9 days for site discovery).
Each card shows the last success, the last result, and how many days in the past week had no run.
A paused job writes no run, so it turns Late and then Needs attention. Turn the switch back on, or accept the status until you do.
The Daily email sends the same health summary to the addresses in the DIGEST_TO setting, or to the built-in default address when that setting is empty. Choose Send digest now on Admin → Data to send it straight away. To preview the message without sending it, an operator with the deployment environment can run pnpm digest:send --dry-run, which prints the HTML. pnpm digest:send --to name@example.com sends only to one address.
When a job is red:
- Open Admin → Data and read the job's last result. A failed run shows its error text. The run log under the roster check and history import lists the last 14 runs with their status.
- If the error names a missing setting, such as
CRON_SECRET, correct it in the deployment environment before running the job again. - Run the job again from the admin board. The roster check and the history import each have a Refresh button on their source rows; the history refresh also rebuilds insights and playing time. Site discovery has no button, so an operator runs
pnpm schools:discover. - Refresh the page after a few minutes. The card turns green when a run succeeds.
- If it is still red after a second attempt, send the error text to the person who maintains the app.
Style guide
The page at /styleguide shows every design system component with sample data. Use it to show a stakeholder the visual language without opening a real player.
Keeping school records correct
Open Admin → Schools to search by name or city and filter by division or state. Needs attention shows missing athletics sites, roster URLs, College Scorecard matches and logos, rosters that parsed to zero players, and inactive schools. The counts reflect the current search and filters; the list shows 50 schools per page.
Open a school, correct its fields, and choose Save school. Changed fields show Locked: CSV imports, Scorecard refreshes and discovery keep those values. Clearing a field also locks that empty value. Choose Unlock beside a locked field to allow automatic updates again. A school's stable key stays the same when its name changes. A College Scorecard ID pins the federal record; run the College Scorecard refresh afterward to load its facts.
Uncheck Active and give an inactive reason when a school no longer belongs on the recruiting list. New reports and scheduled roster fetches exclude inactive schools. The record and existing reports remain available; there is no delete action. A discovery result with no baseball links is a prompt to review the program, not an automatic removal.
The three buttons under Status run individual jobs:
- Find roster page tries standard and dated roster addresses, then baseball roster links from the site's home and baseball landing pages. Locked URLs remain unchanged.
- Fetch and parse roster saves and reads the roster page. If it has no players, Mosaic keeps any populated roster already stored. It tries the previous two seasons when no populated version exists or the stored season is older than last season, keeping the first with at least eight players. Status shows the season, date and player count.
- Fetch logo stores a suitable athletics image for use beside the school's name. The School logos feature flag is on by default and can hide all logos and monograms.
For a batch repair, an operator with the deployment environment can run pnpm schools:sync. It runs import, key backfill, College Scorecard, site discovery, missing-roster discovery, roster discovery/fetch/parse and missing logos in that order. Use --steps keys,scorecard,rosters, --schools 12,34, or --limit 20 to narrow the work. Each step prints a summary; a failed step does not stop the next. Repeated runs skip completed work. The Scorecard step uses the local institution CSV; the College Scorecard refresh supplies a newer file when needed. pnpm schools:keys only fills missing keys; pnpm schools:logos missing --limit 60 only fills missing logos.
Missing roster discovery
Run pnpm schools:discover with the deployment environment loaded to retry active schools with no roster URL. Use --schools 12,34, --limit 20, or --include-inactive to include inactive schools. It works on four distinct hosts at a time, prints a result for each school and a tally by note. The discover step in schools:sync runs after sites.
Discovery can follow one link from a university page on a .edu host (including its redirected host) to a verified athletics home page, then checks standard roster paths and baseball navigation on the home and sports pages. Admin locks still apply. Finding a roster reactivates an inactive school only when its reason is exactly No baseball roster page found; baseball not confirmed and Active is unlocked. A different inactive reason stays intact.
The roster column shows a note when no URL is stored; the school Status panel also shows the check date:
| Note | Meaning |
|---|---|
found | A roster URL responded successfully. |
presto | A roster was found with PrestoSports markup or a /sports/bsb/ path; history fetching policy is unchanged. |
no-baseball-link | The home page loaded but had no baseball link, and no roster was found. Review the program before changing Active. |
blocked | A page returned an HTTP refusal (401, 403 or 429). Scheduled roster and history reads skip this school. |
robots-disallowed | The roster read was disallowed by robots.txt. Roster refresh reports skipped-robots; a school with no URL reports skipped-no-url. |
site-name-mismatch | A redirect or proposed athletics home page did not match the school name, including a conflicting institution type. No roster is automatically stored. |
site-unreachable | The site URL was missing, or the home page could not be reached before the timeout. |
not-found | The attempted pages did not yield a roster and no more specific reason applied. |
Playing time and starter settings
scoring.starters_from_stats ("Mark returning starters from last season's statistics") defaults to true. Returning starters count 1.5 in newly generated reports; other returners count 1.0. Turn it off to count every returner at 1.0. The underlying season lines and Starter chips remain available.
scoring.starter_rule defaults to { "fieldGsShare": 0.5, "pitcherMinGs": 6, "pitcherMinIp": 30 }: a position player starts at least half the team's games, or a pitcher has six starts or 30 innings. Missing games started stay unknown. A two-way player can qualify through either role. After changing this rule, refresh Player history or run pnpm history:insights to rebuild the live roster's playing-time fields, then generate new reports. The job reports schools, players, matches and starters. First-year players and transfers in have no line at this school yet; ambiguous names are left unmatched.
Repairing incorrect athletics-site links
With the deployment environment loaded, run pnpm schools:repair-sites --dry-run to list schools whose athletics site is a known vendor, conference or CDN, or whose site URL is shared by at least three schools. Run pnpm schools:repair-sites to clear those athletics-site URLs and their discovery notes. Both commands print the affected school names; administrator locks are respected. The command does not discover replacement sites. Review the list and run the normal discovery workflow afterward.
Apply the generated previous-school migration before deploying the new roster readers. Refresh rosters afterward to fill the new field; older stored players show “—” until a roster read supplies it.
Waiting for the new season
Choose Waiting for the new season's roster in Admin → Schools to find schools whose known roster season is older than the current spring season. The neutral chip is a waiting state. Its count follows your current search and filters. October 2026 belongs to the spring 2027 roster season.
The daily roster job checks up to 30 waiting schools first, oldest check first, within its 40-fetch budget. Remaining requests go to the normal rotation. An empty new page does not replace a populated roster, and a page matching any stored hash from the last 400 days does not create another version.
The school Status panel shows three dates:
- Roster last checked (
rosterCheckedAt): the latest roster check, including unchanged, empty, skipped and failed reads. The school list shows this as “checked” beside the season. - moved to … on … (
rosterSeasonChangedAt): when the recorded roster season last changed. Rechecking the same season leaves this date intact. - History last checked (
historyCheckedAt): when the latest history slice checked this school, including an attempt that found no new roster or encountered an error. It does not mean every season has data.
The history job follows the roster job each morning. It reads the current spring season and the one before from school sites, up to 25 schools per day within its time budget. Linking, insights and playing time are rebuilt by the separate insights job at 10:30 UTC. If a stage runs out of time, it runs on the next day's call or through Player history on the admin data board. Turning off flags.history_cron_enabled pauses the scheduled history job; manual commands remain available.
With the deployment environment loaded, operators can run:
pnpm rosters:prune --dry-run
pnpm rosters:prune
pnpm history:latest --limit 25
Prune prints counts per school. It removes empty versions only for schools that also have a populated version, and removes older populated copies with the same content hash, keeping the newest. The dry run lists those counts without deleting. history:latest calls the same history refresh without a time budget; --limit defaults to 25. Use the admin Player history refresh afterward to rebuild derived history immediately.
Apply the generated season-recheck migration before deploying these checks and pages. Migration application and the one-time prune are operator steps.