six-degrees 0.4.0-beta.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +5 -5
- package/.next/standalone/.next/required-server-files.json +6 -1
- package/.next/standalone/.next/routes-manifest.json +12 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +13 -13
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +13 -13
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0rc27za._.js → [root-of-the-server]__0bat8nx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -6
- package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +12 -8
- package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0cu6-an._.js → _1vpfyd-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/.next/standalone/CHANGELOG.md +152 -0
- package/.next/standalone/CONTRIBUTING.md +15 -4
- package/.next/standalone/README.md +21 -15
- package/.next/standalone/SECURITY.md +10 -0
- package/.next/standalone/app/api/ingest/route.js +6 -1
- package/.next/standalone/app/api/scraper/route.js +79 -11
- package/.next/standalone/app/api/update-images/route.js +41 -7
- package/.next/standalone/app/avatars/[file]/route.js +6 -1
- package/.next/standalone/app/components/Avatar.js +5 -2
- package/.next/standalone/app/components/ChainView.js +659 -469
- package/.next/standalone/app/components/EmptyState.js +1 -1
- package/.next/standalone/app/components/FieldStep.js +94 -0
- package/.next/standalone/app/components/ForceGraph.js +183 -768
- package/.next/standalone/app/components/GridView.js +9 -7
- package/.next/standalone/app/components/LinkedInLimits.js +22 -7
- package/.next/standalone/app/components/ListView.js +6 -4
- package/.next/standalone/app/components/MappingProgress.js +5 -6
- package/.next/standalone/app/components/OnboardingGate.js +1 -1
- package/.next/standalone/app/components/OrbitGraph.js +3 -1
- package/.next/standalone/app/components/OutlinkQuest.js +5 -4
- package/.next/standalone/app/components/RingsView.js +9 -8
- package/.next/standalone/app/components/Sidebar.js +172 -112
- package/.next/standalone/app/components/TheirCircle.js +18 -11
- package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
- package/.next/standalone/app/components/settings/SectorSection.js +17 -191
- package/.next/standalone/app/components/settings/TierSection.js +7 -12
- package/.next/standalone/app/components/views.js +2 -2
- package/.next/standalone/app/globals.css +7 -0
- package/.next/standalone/app/launch/page.js +2 -2
- package/.next/standalone/app/page.js +130 -25
- package/.next/standalone/app/paths/page.js +3 -2
- package/.next/standalone/app/queue/page.js +18 -10
- package/.next/standalone/app/setup/page.js +423 -260
- package/.next/standalone/db/schema.js +1 -1
- package/.next/standalone/eslint.config.mjs +3 -3
- package/.next/standalone/install.sh +3 -3
- package/.next/standalone/lib/chain-layout.js +139 -0
- package/.next/standalone/lib/csv.js +4 -6
- package/.next/standalone/lib/data-export.js +2 -1
- package/.next/standalone/lib/data-folder.js +4 -2
- package/.next/standalone/lib/galaxy.js +12 -0
- package/.next/standalone/lib/ingest.js +15 -8
- package/.next/standalone/lib/linkedin-limits.js +30 -8
- package/.next/standalone/lib/photos.js +47 -0
- package/.next/standalone/lib/quest.js +8 -3
- package/.next/standalone/lib/reach.js +113 -0
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scanner-setup.js +20 -1
- package/.next/standalone/lib/scoring.js +769 -18
- package/.next/standalone/lib/scraper-client.js +27 -0
- package/.next/standalone/lib/sector-focus.js +13 -0
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/settings-client.js +21 -0
- package/.next/standalone/lib/settings.js +3 -1
- package/.next/standalone/next.config.mjs +25 -3
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/build-app.mjs +11 -2
- package/.next/standalone/scripts/check-node.mjs +1 -1
- package/.next/standalone/scripts/image_store.py +77 -21
- package/.next/standalone/scripts/scrape.py +243 -29
- package/.next/standalone/server.js +1 -1
- package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/CHANGELOG.md +152 -0
- package/README.md +21 -15
- package/SECURITY.md +10 -0
- package/db/schema.js +1 -1
- package/lib/chain-layout.js +139 -0
- package/lib/csv.js +4 -6
- package/lib/data-export.js +2 -1
- package/lib/data-folder.js +4 -2
- package/lib/galaxy.js +12 -0
- package/lib/ingest.js +15 -8
- package/lib/linkedin-limits.js +30 -8
- package/lib/photos.js +47 -0
- package/lib/quest.js +8 -3
- package/lib/reach.js +113 -0
- package/lib/scan-state.js +1 -0
- package/lib/scanner-setup.js +20 -1
- package/lib/scoring.js +769 -18
- package/lib/scraper-client.js +27 -0
- package/lib/sector-focus.js +13 -0
- package/lib/separation.js +4 -2
- package/lib/settings-client.js +21 -0
- package/lib/settings.js +3 -1
- package/package.json +1 -1
- package/scripts/check-node.mjs +1 -1
- package/scripts/image_store.py +77 -21
- package/scripts/scrape.py +243 -29
- package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0223v9s._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0bcf13g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0wxbq78._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0z88zcl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
- package/.next/standalone/.next/static/chunks/0_yurn4q91vq8.js +0 -16
- package/.next/standalone/.next/static/chunks/0d_mh-06srh2k.js +0 -1
- package/.next/standalone/.next/static/chunks/0x3oadfikvb1c.js +0 -1
- package/.next/standalone/.next/static/chunks/1d5hl1io798n2.js +0 -1
- package/.next/standalone/.next/static/chunks/1qt67cvj9sjwi.js +0 -1
- package/.next/standalone/.next/static/chunks/1roznk7hkjes1.js +0 -44
- package/.next/standalone/.next/static/chunks/2_bf2n4gstad9.js +0 -33
- package/.next/standalone/.next/static/chunks/36gbwek52x_w-.js +0 -2
- package/.next/standalone/.next/static/chunks/3a55suosthntc.js +0 -1
- package/.next/standalone/.next/static/chunks/3fq1zq-6cfwbc.js +0 -1
- package/.next/standalone/.next/static/chunks/3nbnio971zlji.js +0 -2
- package/.next/standalone/.next/static/chunks/3s6awp3qq9s7w.js +0 -1
- package/.next/static/chunks/0_yurn4q91vq8.js +0 -16
- package/.next/static/chunks/0d_mh-06srh2k.js +0 -1
- package/.next/static/chunks/0x3oadfikvb1c.js +0 -1
- package/.next/static/chunks/1d5hl1io798n2.js +0 -1
- package/.next/static/chunks/1qt67cvj9sjwi.js +0 -1
- package/.next/static/chunks/1roznk7hkjes1.js +0 -44
- package/.next/static/chunks/2_bf2n4gstad9.js +0 -33
- package/.next/static/chunks/36gbwek52x_w-.js +0 -2
- package/.next/static/chunks/3a55suosthntc.js +0 -1
- package/.next/static/chunks/3fq1zq-6cfwbc.js +0 -1
- package/.next/static/chunks/3nbnio971zlji.js +0 -2
- package/.next/static/chunks/3s6awp3qq9s7w.js +0 -1
- /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
- /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
package/lib/scoring.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
// on every rescore and never written into your company scores.
|
|
37
37
|
//
|
|
38
38
|
// Tiers are graded on your network's curve unless you choose the fixed scale
|
|
39
|
-
// (
|
|
39
|
+
// (Scores → Tiers): your top 3% of 1st-degree people are S, and so on down
|
|
40
40
|
// (CURVE). The curve only lifts, so a strong network keeps its fixed-scale
|
|
41
41
|
// tiers, and it never lifts anyone under 4 into S or A. Everyone, 2nd degree
|
|
42
42
|
// included, is graded on the same cut-offs, taken from your 1st degree.
|
|
@@ -88,6 +88,25 @@ export const LEVELS = {
|
|
|
88
88
|
audience10m: { key: 'audience10m', label: 'Audience of 10M+', level: 5, points: 9 },
|
|
89
89
|
audience1m: { key: 'audience1m', label: 'Audience of 1M+', level: 4, points: 7.5 },
|
|
90
90
|
audience100k: { key: 'audience100k', label: 'Audience of 100K+', level: 3, points: 6.5 },
|
|
91
|
+
// Government and the services, on the same points. An elected or appointed
|
|
92
|
+
// leader (a senator, a governor, a mayor, a cabinet secretary) is C-suite;
|
|
93
|
+
// their deputies, state legislators, commissioners, ambassadors and judges
|
|
94
|
+
// are VPs; a deputy assistant secretary, a city councilmember or a sheriff a
|
|
95
|
+
// director. Officers by rank: a 3–4 star general or admiral is C-suite, a
|
|
96
|
+
// 1–2 star a VP, a colonel (a Navy captain) a director, a lieutenant colonel
|
|
97
|
+
// (a commander) a manager, a major (a captain) a senior IC; a command
|
|
98
|
+
// sergeant major or a master chief leads like a manager.
|
|
99
|
+
govLeader: { key: 'govLeader', label: 'Government Leader', level: 6, points: 10 },
|
|
100
|
+
govSenior: { key: 'govSenior', label: 'Senior Official', level: 5, points: 9 },
|
|
101
|
+
judge: { key: 'judge', label: 'Judge', level: 5, points: 9 },
|
|
102
|
+
govOfficial: { key: 'govOfficial', label: 'Government Official', level: 4, points: 7.5 },
|
|
103
|
+
seniorGeneral: { key: 'seniorGeneral', label: 'General / Admiral (3–4 star)', level: 6, points: 10 },
|
|
104
|
+
general: { key: 'general', label: 'General / Admiral (1–2 star)', level: 5, points: 9 },
|
|
105
|
+
colonel: { key: 'colonel', label: 'Colonel / Navy Captain', level: 4, points: 7.5 },
|
|
106
|
+
ltColonel: { key: 'ltColonel', label: 'Lt. Colonel / Commander', level: 3, points: 6.5 },
|
|
107
|
+
seniorEnlisted: { key: 'seniorEnlisted', label: 'Senior Enlisted Leader', level: 3, points: 6.5 },
|
|
108
|
+
nco: { key: 'nco', label: 'Senior NCO / Warrant Officer', level: 2, points: 5 },
|
|
109
|
+
major: { key: 'major', label: 'Major / Captain', level: 2, points: 5 },
|
|
91
110
|
};
|
|
92
111
|
|
|
93
112
|
// Checked in this order within one part of a headline. Earlier rules REMOVE
|
|
@@ -95,8 +114,15 @@ export const LEVELS = {
|
|
|
95
114
|
// Staff" as a chief, or "Product Owner" as an owner. The highest level found wins.
|
|
96
115
|
// A title's possessive is an award or an office named for it, not the title:
|
|
97
116
|
// "President's Club" (sales), "Chairman's Award", "CEO's Office", "Dean's List".
|
|
117
|
+
// Staff are not their boss: "Chief of Staff to the CEO", "Executive Assistant
|
|
118
|
+
// to the General Manager" (but "Promoted to VP" is the VP). Government and
|
|
119
|
+
// military titles aren't here: they count only where a title is written
|
|
120
|
+
// (officeTitle, below).
|
|
98
121
|
const TITLE_RULES = [
|
|
99
122
|
[/\b(reporting )?(to|for) (the |our |a )?(ceo|cto|coo|cfo|cmo|founders?|co-?founders?|president|chair(man)?|professors?|dean|provost|chancellor)\b/g, null],
|
|
123
|
+
// An assistant's boss, with or without "the"; another staff word's only with
|
|
124
|
+
// it ("Associate to VP", "from Manager to GM" are promotions).
|
|
125
|
+
[/(?<!\bfrom\s+(?:[a-z]+\s+){0,2})(?:(?<=\b(?:assistant|aide|advisor|adviser|chief of staff|support|liaison|scheduler|ea)\s+)(to|for) (the |our |a )?|(?<=\b(?:paralegal|clerk|counsel|associate|coordinator|manager)\s+)(to|for) (the|our|a) )(general manager|managing director|gm|vice president|[se]?vp|general counsel|chief counsel)\b/g, null],
|
|
100
126
|
[/\bchief of staff\b/g, 'director'],
|
|
101
127
|
[/\b(senior |executive |assistant |associate )?vice[-\s]?president\b|\b(s|e|a)?vp\b/g, 'vp'],
|
|
102
128
|
// Universities, before the C-suite rule reads "Vice Chancellor" as a
|
|
@@ -113,6 +139,23 @@ const TITLE_RULES = [
|
|
|
113
139
|
// A dean only as a title: before "of", "for", a comma or the part's end, and
|
|
114
140
|
// never a company's name ("Dean Foods", "at Dean Health Plan").
|
|
115
141
|
[/(?<!\b(?:at|@)\s*)\bdean\b(?!['’]s\b)(?=\s*(?:(?:of|for|at|and|emerit(?:us|a))\b|[&@,(]|$))/g, 'dean'],
|
|
142
|
+
// A company's general counsel is an executive; the lawyers under a general
|
|
143
|
+
// or a chief counsel are directors and seniors. A military chief (a chief
|
|
144
|
+
// petty officer, a chief warrant officer) isn't a chief officer.
|
|
145
|
+
[/(?<!\b(?:promoted|from\s+[a-z]+(?:\s+[a-z]+)?)\s)\b(?:to|supporting|supports|support to) (?:the|our|a) (?:(?:deputy|associate|assistant) )?(?:(?:general|chief) counsel|division chief|section chief)\b/g, null],
|
|
146
|
+
[/\b(associate|assistant|deputy) general counsel\b/g, 'director'],
|
|
147
|
+
[/\b(associate|assistant|deputy) chief counsel\b/g, 'senior'],
|
|
148
|
+
[/(?<=\b(?:assistant|paralegal|clerk|coordinator|specialist|analyst|aide|supporting|support)\b[^|]*?|\b(?:attorney|lawyer|counsel|associate|manager|administrator)\b[^|,]*,[^|]*?)\b(general|chief) counsel\b/g, null],
|
|
149
|
+
[/\b(?:general|chief) counsel\b(?=[\s,]*(?:['’]s|office|division|practice|group|llp|l\.l\.p|p\.?c\.?|pllc|law|operations|team|department)\b)/g, null],
|
|
150
|
+
[/^\s*(?:(?:(?:senior |executive )?vice president|[se]?vp|chief legal officer|clo|head of legal|corporate secretary|secretary)\s*(?:,|&|and)\s*)*(?:general|chief) counsel\b|(?<=\b(?:[se]?vp|vice president|chief legal officer|clo|head of legal|president|coo|ceo)\s*(?:,|&|and)\s*|\b(?:attorney|lawyer)\s*(?:&|and)\s*)(?:general|chief) counsel\b/g, 'vp'],
|
|
151
|
+
[/\b(?:general|chief) counsel\b/g, null],
|
|
152
|
+
[/\b(senior |master )?chief petty officer\b|\bchief warrant officer\b|\bchief master sergeant\b/g, null],
|
|
153
|
+
// A government's or a lab's division and branch chiefs lead them, and a
|
|
154
|
+
// medical school's section chief is its division chief. A division's or a
|
|
155
|
+
// unit's chief officer is a chief officer ("Division Chief Nursing Officer").
|
|
156
|
+
[/\b(division chief|section chief(?= of\b))\b(?!\s+(\w+\s+){0,3}officer\b|\s+executive\b)|\bchief of (the )?[a-z]+ division\b/g, 'director'],
|
|
157
|
+
[/\b(branch|section|unit) chief\b(?!\s+(\w+\s+){0,3}officer\b|\s+executive\b)|^\s*chief,\s+[a-z &]+ (branch|section|office)\b/g, 'manager'],
|
|
158
|
+
[/^\s*chief,\s+[a-z &]+ division\b/g, 'director'],
|
|
116
159
|
[/\b(product|process|content|data|feature|service|project|business process) owner\b/g, 'ic'],
|
|
117
160
|
[/\b(client|channel|strategic|business|brand|creative|agency|solutions?|account|community|developer|technology|alliances?|content|media|marketing|talent|partner) partner\b/g, 'senior'],
|
|
118
161
|
[/\bpartner (manager|marketing|development|success)\b/g, 'manager'],
|
|
@@ -121,9 +164,10 @@ const TITLE_RULES = [
|
|
|
121
164
|
[/(?:\b(ceo|cto|cfo|coo|cmo|cro|cpo|cio|ciso|cdo|cso)\b|\bchief\s+(\w+\s+){0,3}officer\b|\bchief executive\b|\b(co-?)?founder\b|\bcofounder\b|\bpresident\b|\bchair(man|woman|person)\b|\bexecutive chair\b|\b(general|managing|founding) partner\b|\bchancellor\b|\bprovost\b)(?!['’]s\b)/g, 'csuite'],
|
|
122
165
|
[/\b(co-?)?owner\b|\bentrepreneur\b|\bself[-\s]employed\b|\bproprietor\b/g, 'owner'],
|
|
123
166
|
// "MD" is a managing director only where a doctor's degree can't be meant:
|
|
124
|
-
// at a company that isn't a hospital's or a clinic's ("MD @ J.P. Morgan"
|
|
125
|
-
// with a division after it ("MD, Investment Banking"), not a
|
|
126
|
-
|
|
167
|
+
// at a company that isn't a hospital's or a clinic's ("MD @ J.P. Morgan", read
|
|
168
|
+
// in readPart), or with a division after it ("MD, Investment Banking"), not a
|
|
169
|
+
// degree ("MD, MBA").
|
|
170
|
+
[/\bmanaging director\b|\bmd\b(?=\s*(?:at\s|@))|^\s*md,\s+(?!(?:mba|phd|mph|ms|msc|mhs|mha|mhsa|jd|facs|facp|facc|faap|dds|dmd|do|rn|np|pa|ma|mbe|mpp)\b)|\bgeneral manager\b|\bgm\b|\bcountry (manager|director|lead|head)\b|\bhead of country\b|\bglobal head\b|\bchief (technologist|scientist|architect|economist|evangelist|strategist|engineer|of \w+)\b|^\s*partner\b|\bpartner (at|@)|\b(venture|senior|equity|operating) partner\b|\bboard (member|director|chair)\b|\bchair of the board\b|\bmember of the board\b|\bboard of directors\b/g, 'vp'],
|
|
127
171
|
[/\b(senior |executive |creative |art |associate )?director\b|\bhead of\b|\bhead\b(?= (coach|chef))|\b(angel |venture )?investor\b|\bprincipal at\b|\b(technical|distinguished|senior) fellow\b|(?<!account |sales |customer )\bexecutive\b(?! (assistant|producer|chef))/g, 'director'],
|
|
128
172
|
[/\bmanager\b|\bmgr\b|\b(team |tech |design |engineering )?lead\b|\bleader\b|\bprincipal\b|\bstaff (engineer|designer|scientist|product|data)\b|\bsupervisor\b|\bsuperintendent\b/g, 'manager'],
|
|
129
173
|
[/\bsenior\b|\bsr\b\.?|\bspecialist\b|\barchitect\b|\bexpert\b|\bexecutive producer\b/g, 'senior'],
|
|
@@ -166,7 +210,33 @@ const SHORT_NAME_CLUB = /\b(club|society)\b/;
|
|
|
166
210
|
const FIELD_ASSOCIATION = /\b(marketing|finance|financial|business|economics|accounting|consulting|investment|investing|entrepreneurship|engineering|computer science|data science|nursing|pre-?law|pre-?med|students?)\b.*\bassociation\b/;
|
|
167
211
|
// An alumni club or a parents' association is for grown-ups.
|
|
168
212
|
const GROWN_UP = /\b(alumni|alumnae|alumnus|alum|parents?)\b/;
|
|
169
|
-
const FORMER = /^\s*(ex[-\s]|former(ly)?\b|previously\b|prev\b|past\b|retired\b)/i;
|
|
213
|
+
const FORMER = /^\s*(ex[-\s]|former(ly)?\b|previously\b|prev\b|past\b|retired\b|ret(\.|d\.?)?\s)/i;
|
|
214
|
+
// Retired, written after the title: "Colonel (Ret.), U.S. Army", "COL (R)",
|
|
215
|
+
// "Admiral, USN, Ret.", "Army Colonel, Retired". A former role, like "Ex-" or
|
|
216
|
+
// "Retired" before it. A part that says only that ("Colonel, USAF - Retired")
|
|
217
|
+
// makes the role before it former (readRoles).
|
|
218
|
+
const RETIRED = /\(\s*(?:[a-z.]+,\s*)?ret(\.|ired|d\.?)?\s*\)|,\s*ret(\.|ired|d\.?)?\s*(?=,|$)|,\s*retired\s*(?=,|\)|$)|\bret\.\s*$|\b(army|navy|air force|marine corps|marines|usmc|usaf|usn|uscg|coast guard|space force)\s+(ret(\.|d\.?)?|retired)\s*$/i;
|
|
219
|
+
const RETIRED_ONLY = /^\s*\(?\s*(ret\.?|retd\.?|retired)\s*\)?\s*$/i;
|
|
220
|
+
// An office named for its head is where someone works, not their title: "IT
|
|
221
|
+
// Specialist, Office of the Chief Information Officer", "Attorney, Judge
|
|
222
|
+
// Advocate General's Corps". Only the head's title is taken out, so a title
|
|
223
|
+
// written after an office's own name stays ("Office of Technology Licensing
|
|
224
|
+
// Director").
|
|
225
|
+
const OFFICE_NAME = /\boffice of (the )?(chief [a-z]+ officer|ceo|cto|cfo|coo|cio|ciso|cmo|president|vice president|general counsel|chief counsel|inspector general|solicitor general|surgeon general|attorney general|(under |deputy )?secretary( of [a-z ]+?)?|governor|lieutenant governor|mayor|director( of [a-z ]+?)?|chair(man)?|commissioner|(u\.?s\.? )?(senator|congress(wo)?man|representative)( [a-z.]+){0,3})( of (the )?[a-z.& ]+?)?(?=\s*(,|$|\||\(|\sat\s|@))|\b(judge advocate|adjutant) general['’]?s? corps\b|\b(general|chief) counsel['’]?s? (office|division|section|staff|team|department)\b|\boffice of (the )?(general|chief) counsel\b/g;
|
|
226
|
+
// The company after "at" says where someone works, not what they do: its
|
|
227
|
+
// words aren't read as a title ("Server at President Hotel", "Driver at
|
|
228
|
+
// Executive Limo"). It runs to the next comma, a bracket, an emoji, or to
|
|
229
|
+
// what starts another title or says what they do there: "at Stripe as Head
|
|
230
|
+
// of Growth", "at Goldman Sachs (VP)", "at Google and Founder of Northwind".
|
|
231
|
+
// "at" is the word alone: "AT&T Director" and "At-Large Director" are titles.
|
|
232
|
+
// A part that starts with "at" has its title in the company's span ("At Home
|
|
233
|
+
// Store Manager", "At Microsoft: Principal PM"), so nothing is taken out.
|
|
234
|
+
const AT_COMPANY = /(?<=\S.*?)(\s(?:at\s|@)\s*)(?:(?!\s(?:as|where|leading|building|helping|working|and|while|also|plus|now|since|then|formerly|previously|x|in (?:19|20)\d\d)\s|\s[&+/]\s|\.\s|\s(?:at\s|@)|\s\S+(?:\s\S+)?\s(?:of|at|for)\s|\s\S+(?:\s\S+)?\s?@|\s?(?:→|►|->|=>)|[:(\[/\p{Extended_Pictographic}])[^,|])*/gu;
|
|
235
|
+
const HEALTH_ORG = /\b(?:hospitals?|clinics?|health|healthcare|medical|medicine|physicians?|pediatrics?|surgery|surgical|kaiser|mayo|hopkins|dermatology|cardiology|oncology|orthopa?edics?|urgent care)\b/;
|
|
236
|
+
// A doctor's specialty written after the hospital: "MD at Emory, Pediatrics".
|
|
237
|
+
// Not a banker's coverage group ("MD at Goldman Sachs, Healthcare").
|
|
238
|
+
const CARE_PLACE = /\b(?:hospitals?|clinic\w*|medical (?:center|school|group|director)|health system|school of medicine|residency|fellow(?:ship)?|resident|attending|physician|surgeon|hospitalist|professor|pediatric\w*|surg\w*|cardiolog\w*|oncolog\w*|hematolog\w*|orthopa?edic\w*|medicine|radiolog\w*|anesthesi\w*|psychiatr\w*|neurolog\w*|dermatolog\w*|urolog\w*|ophthalmolog\w*|patholog\w*|obstetric\w*|gynecolog\w*|gastroenterolog\w*|nephrolog\w*|endocrinolog\w*|rheumatolog\w*|pulmon\w*|geriatric\w*|palliative|critical care|primary care|infectious disease\w*|neonatolog\w*|otolaryngolog\w*|immunolog\w*|emergency|trauma)\b/;
|
|
239
|
+
const SPECIALTY = /^(?:[a-z-]+ ){0,2}(?:pediatrics?|surgery|cardiology|oncology|orthopa?edics?|medicine|radiology|anesthesiology|anesthesia|psychiatry|neurology|dermatology|urology|ophthalmology|pathology|obstetrics(?: and gynecology)?|gynecology|ob\/?gyn|neurosurgery|gastroenterology|nephrology|endocrinology|rheumatology|pulmonology|hematology|geriatrics|palliative care|critical care|primary care|infectious diseases?|hospitalist|neonatology|otolaryngology|allergy(?: and immunology)?|immunology|emergency|trauma)$/;
|
|
170
240
|
|
|
171
241
|
/** Can this short name ("USC", "UBS") be a school's? Not one on NOT_A_SCHOOL, nor a company the curated list knows. */
|
|
172
242
|
function schoolShortName(name) {
|
|
@@ -176,7 +246,11 @@ function schoolShortName(name) {
|
|
|
176
246
|
}
|
|
177
247
|
|
|
178
248
|
/** Does this part of a headline say they're studying now? "CS @ NYU", "Economics student". */
|
|
249
|
+
// A war or staff college's students are serving officers, not students.
|
|
250
|
+
const SERVICE_SCHOOL = /\b(war college|staff college|command and general staff|naval postgraduate school|air university|national defense university|eisenhower school|joint forces staff college|school of advanced (military|air|warfighting))\b/i;
|
|
251
|
+
|
|
179
252
|
function studies(part) {
|
|
253
|
+
if (SERVICE_SCHOOL.test(part)) return false;
|
|
180
254
|
if (STUDENT.test(` ${part.toLowerCase()} `)) return true;
|
|
181
255
|
const at = MAJOR.test(part) && AT_SHORT_NAME.exec(part);
|
|
182
256
|
return !!at && schoolShortName(at[1]);
|
|
@@ -203,8 +277,13 @@ export function headlineParts(headline = '') {
|
|
|
203
277
|
}
|
|
204
278
|
|
|
205
279
|
function readPart(part) {
|
|
206
|
-
let s = ` ${part.toLowerCase()}
|
|
280
|
+
let s = ` ${part.toLowerCase()} `.replace(OFFICE_NAME, ' ');
|
|
281
|
+
// "MD at a hospital" is a doctor, which only the company (or a specialty
|
|
282
|
+
// after it) says.
|
|
283
|
+
const at = s.match(/\s(?:at\s|@)\s*([^,|]*)((?:,[^,|]*)*)/);
|
|
284
|
+
if (at && /\bmd\b(?=\s*(?:at\s|@))/.test(s) && (HEALTH_ORG.test(at[1]) || at[2].split(',').some((p) => SPECIALTY.test(p.trim()) || CARE_PLACE.test(p)))) s = s.replace(/\bmd\b/, ' ');
|
|
207
285
|
if (INTERN.test(s)) return LEVELS.intern;
|
|
286
|
+
s = s.replace(AT_COMPANY, '$1 ');
|
|
208
287
|
if (studies(part)) return LEVELS.student;
|
|
209
288
|
let best = null;
|
|
210
289
|
for (const [re, key] of TITLE_RULES) {
|
|
@@ -217,6 +296,650 @@ function readPart(part) {
|
|
|
217
296
|
return best;
|
|
218
297
|
}
|
|
219
298
|
|
|
299
|
+
// ── government and the services ─────────────────────────────────────────────
|
|
300
|
+
// A government or military title counts only where a title is written: the
|
|
301
|
+
// start of a headline part, before its organization ("Senator, U.S. Senate",
|
|
302
|
+
// "Colonel | U.S. Air Force", "Deputy Secretary of Defense"). Anywhere else
|
|
303
|
+
// the same words name an office, a company or a club: "Staff Assistant,
|
|
304
|
+
// Office of the Secretary of Defense", "Store Manager at Dollar General",
|
|
305
|
+
// "Law Clerk to Chief Judge…". And a title means government only where its
|
|
306
|
+
// own organization is a government's (where(), below: "Speaker at Soho
|
|
307
|
+
// House", "Administrator at U.S. Bank" and "City Manager, Uber" aren't), and a
|
|
308
|
+
// club, a church, a school, a profession, a mock government, an honorary or a
|
|
309
|
+
// joke title never is (CIVIC: "Rotary District Governor", "Delegate, AMA House
|
|
310
|
+
// of Delegates", "Governor, Florida Boys State", "Mayor of Fun at Northwind";
|
|
311
|
+
// MOCK anywhere in the headline). Every rank but
|
|
312
|
+
// the flag ranks spelled out needs its service beside it, in the same part or
|
|
313
|
+
// as a part of its own ("Captain | U.S. Navy"), and nothing a veterans' group,
|
|
314
|
+
// a cadet, a team, a licence or an honorary title holds is a commission
|
|
315
|
+
// (NOT_MILITARY: "Kentucky Colonel", "Commander, VFW Post 42", "Captain, Navy
|
|
316
|
+
// Rugby", "Master Chief | Halo Cosplayer"). A captain is a colonel's equal only
|
|
317
|
+
// in a navy; a commander's level is their unit's (a division's is a general).
|
|
318
|
+
// Four rounds of adversarial review found what these rules keep out; the
|
|
319
|
+
// cases are in tests/scoring-misses.test.mjs.
|
|
320
|
+
const STATES = String.raw`alabama|alaska|arizona|arkansas|california|colorado|connecticut|delaware|florida|georgia|hawaii|idaho|illinois|indiana|iowa|kansas|kentucky|louisiana|maine|maryland|massachusetts|michigan|minnesota|mississippi|missouri|montana|nebraska|nevada|new hampshire|new jersey|new mexico|new york|north carolina|north dakota|ohio|oklahoma|oregon|pennsylvania|rhode island|south carolina|south dakota|tennessee|texas|utah|vermont|virginia|washington|west virginia|wisconsin|wyoming|puerto rico|guam|american samoa|(?:the )?(?:u\.?\s?s\.?\s)?virgin islands|(?:the )?northern mariana islands|district of columbia`;
|
|
321
|
+
const COUNTRIES = String.raw`afghanistan|albania|algeria|andorra|angola|argentina|armenia|australia|austria|azerbaijan|the bahamas|bahamas|bahrain|bangladesh|barbados|belarus|belgium|belize|benin|bhutan|bolivia|bosnia and herzegovina|botswana|brazil|brunei|bulgaria|burkina faso|burundi|cabo verde|cambodia|cameroon|canada|central african republic|chad|chile|china|colombia|comoros|congo|costa rica|cote d'ivoire|ivory coast|croatia|cuba|cyprus|czech republic|czechia|denmark|djibouti|dominica|dominican republic|ecuador|egypt|el salvador|equatorial guinea|eritrea|estonia|eswatini|ethiopia|fiji|finland|france|gabon|the gambia|gambia|georgia|germany|ghana|greece|grenada|guatemala|guinea|guinea-bissau|guyana|haiti|the holy see|holy see|honduras|hungary|iceland|india|indonesia|iran|iraq|ireland|israel|italy|jamaica|japan|jordan|kazakhstan|kenya|kiribati|kosovo|kuwait|kyrgyzstan|laos|latvia|lebanon|lesotho|liberia|libya|liechtenstein|lithuania|luxembourg|madagascar|malawi|malaysia|maldives|mali|malta|marshall islands|mauritania|mauritius|mexico|micronesia|moldova|monaco|mongolia|montenegro|morocco|mozambique|myanmar|burma|namibia|nauru|nepal|the netherlands|netherlands|new zealand|nicaragua|niger|nigeria|north korea|north macedonia|norway|oman|pakistan|palau|panama|papua new guinea|paraguay|peru|the philippines|philippines|poland|portugal|qatar|romania|russia|rwanda|saint kitts and nevis|saint lucia|saint vincent and the grenadines|samoa|san marino|sao tome and principe|saudi arabia|senegal|serbia|seychelles|sierra leone|singapore|slovakia|slovenia|solomon islands|somalia|south africa|south korea|the republic of korea|korea|south sudan|spain|sri lanka|sudan|suriname|sweden|switzerland|syria|taiwan|tajikistan|tanzania|thailand|timor-leste|togo|tonga|trinidad and tobago|tunisia|turkey|turkiye|turkmenistan|tuvalu|uganda|ukraine|the united arab emirates|united arab emirates|the united kingdom|united kingdom|great britain|uruguay|uzbekistan|vanuatu|venezuela|vietnam|yemen|zambia|zimbabwe|the kyrgyz republic|kyrgyz republic|the slovak republic|slovak republic|lao pdr|viet nam|the vatican|vatican|osce|the osce`;
|
|
322
|
+
// A country's own forces: "French Navy", "German Army" (not the Swiss Army, a knife).
|
|
323
|
+
const NATIONS = String.raw`u\.?\s?s\.?|united states|royal|british|canadian|australian|indian|israeli|french|german|italian|spanish|dutch|polish|norwegian|swedish|danish|finnish|japanese|korean|south korean|republic of korea|philippine|mexican|brazilian|new zealand|irish|belgian|greek|hellenic|turkish|ukrainian|singapore|south african|pakistani|egyptian|saudi|nigerian|kenyan|argentine|chilean|colombian|peruvian|portuguese|czech|romanian|hungarian|estonian|latvian|lithuanian|jordanian|emirati|qatari|taiwanese|thai|malaysian|indonesian|vietnamese|bangladeshi|sri lankan|nepalese|austrian`;
|
|
324
|
+
const INTERNATIONAL = String.raw`the united nations|united nations|u\.?n\.?|nato|the european union|european union|the organization of american states|organization of american states|the african union|african union|asean|the world trade organization|world trade organization|the oecd|oecd|opec|interpol`;
|
|
325
|
+
const STATE_NAME = new RegExp(String.raw`\b(${STATES})\b`);
|
|
326
|
+
// A state's government named as the place it is: "State of Florida", "Commonwealth of Virginia".
|
|
327
|
+
const STATE_GOV = new RegExp(String.raw`\b(?:state|commonwealth) of (?:${STATES})\b|\b(?:${STATES}) (?:state )?(?:government|legislature|senate|house of (?:representatives|delegates)|general assembly|state assembly|supreme court)\b|(?:^|[,|(@:]\s*|\b(?:the|at|of|for|with|in)\s)(?:${STATES}) (?:state )?(?:department|agency|office|cabinet)\b`);
|
|
328
|
+
// A place that is only a state: the whole part says nothing else.
|
|
329
|
+
const JUST_A_STATE = new RegExp(String.raw`^(?:the )?(?:(?:state|commonwealth) of )?(?:${STATES})(?: state)?$`);
|
|
330
|
+
// A government's departments: "Department of Defense", not "Department of Surgery".
|
|
331
|
+
const DEPT = String.raw`(?:the )?(?:state|defense|war|justice|treasury|interior|agriculture(?: and consumer services)?|commerce|labor(?: and (?:industry|workforce development))?|health(?: and human services| and mental hygiene)?|human services|social services|children and famil(?:y|ies)(?: services)?|(?:higher )?education|energy|transportation|veterans (?:affairs|services)|homeland security|housing(?: and urban development)?|army|navy|air force|environmental (?:protection|quality|conservation)|natural resources|conservation|fish and (?:wildlife|game)|parks(?: and recreation)?|revenue|taxation|finance(?: and administration)?|corrections|public safety|motor vehicles|highway safety(?: and motor vehicles)?|juvenile justice|law|insurance|financial (?:services|institutions)|banking(?: and finance)?|economic (?:development|opportunity|security)|community (?:affairs|development)|workforce development|employment(?: security)?|public health|mental health|developmental services|aging|elder affairs|military affairs|emergency management|information technology|administrative services|general services|management services|business(?: and professional)? regulation|consumer affairs|water resources|environment|public works|sanitation|buildings|city planning|homeless services|police|fire)`;
|
|
332
|
+
// Where a government title may name its government: its own body, not "U.S. Bank".
|
|
333
|
+
const GOV_ORG = new RegExp(String.raw`^(?:the )?(?:u\.?\s?s\.?|united states|federal|state of|commonwealth of|city of|county of|town of|village of|borough of|township of)\b[^,|]*?\b(?:department|dept\.?|agency|administration|bureau|commission|board|office|service|authority|court|senate|house of representatives|assembly|legislature|congress|marshals|attorney['’]?s office|embassy|mission)(?: of [a-z ,&'.-]+| for [a-z ,&'.-]+)?$|\b(?:department|dept\.?) of ${DEPT}\b|\b(?:senate(?!\s+(?:bank|federal|credit|capital|group|partners|consulting|solutions|inc|llc))|house of representatives|house of delegates|state assembly|legislature|library of congress|congressional (?:budget office|research service)|(?:u\.?\s?s\.?|united states|${STATES}|state) supreme court|supreme (?:judicial )?court of (?:the )?[a-z ]+|court of appeals|(?:district|circuit|superior|county|municipal|probate|family|juvenile|tax|bankruptcy) court|judicial circuit|supreme judicial court|court of (?:criminal )?appeals|appeals court|court of chancery|court of federal claims|court of international trade|appellate (?:court|division)|(?:congressional|legislative|house|senate|assembly) district|city council|town council|county commission|board of (?:county )?commissioners|board of supervisors|police department|sheriff['’]?s office|state attorney['’]?s office|district attorney['’]?s office|parliament)\b|^(?:the )?white house(?: office| staff| press office| national security council)?$|\bexecutive office of the president\b|\b(?:u\.?\s?s\.?|american) (?:embassy|consulate)\b|\b(?:embassy|consulate(?: general)?) (?:of|in)\b|\b(?:city|county|state|federal|tribal|municipal|local) government$|^(?:the )?government of\b|${STATE_GOV.source}`);
|
|
334
|
+
// Not a government's: clubs, churches, schools and students, professions,
|
|
335
|
+
// mock governments, honorary and joke titles.
|
|
336
|
+
const CIVIC = /\b(rotary|lions clubs?|kiwanis|circle k|jaycees|jci|toastmasters|optimist|civitan|elks|moose|masonic|freemasons?|lodge|knights of columbus|pta|pto|students?|sga|asg|associated students|student government|model (un|united nations|congress|senate|legislature|g20|nato|oas)|\w*mun|boys state|girls state|youth (in|and) government|mock (trial|congress|legislature|government)|intercollegiate|faculty|(staff|faculty|student|graduate|university|employee|classified|academic|campus) (senate|assembly)|honor (court|council|board)|scouts?|fraternity|sorority|district \d{4}|alumni|model parliament|youth parliament|high school|middle school|olympics|ama|aba|american medical association|american bar association|chamber of commerce|youth|church|catholic|diocese|synod|presbyterian|methodist|baptist|episcopal|lutheran|congregation|ministry|ministries|vestry|temple|mosque|synagogue|daughters of the american revolution|sons of the american revolution|lineage|association|society|convention|cosplay|honorary|fantasy|kickball|softball|soccer|baseball|basketball|football|hockey|bowling|golf|pickleball|volleyball|rugby|lacrosse|chess|cornhole|tennis|swim|rodeo|athletics?|league|clubs?|tournament|esports|gaming|panhellenic|interfraternity|greek life|honors college|debate|key club|builders club|american legion|vfw|veterans of foreign wars|freemasonry|scottish rite|shriners|collectors|hoa|homeowners|condo|undergraduate|scouting|bsa|deca|ffa|4-h|fbla|hosa|skillsusa|y ?& ?g|girlguiding|brownies|guides|st john ambulance|chamber|state bar|bar of|oxford union|cambridge union|debating (?:union|society)|youth council|bodybuilding|physique|npc|ifbb|powerlifting|junior chamber|republicans?|democrats?|democratic party|republican party|libertarian|green party|young republicans|young democrats|college republicans|college democrats|party of|gop|teen|campus|neighborhood watch|homeschool(?:ing|er)?|of (fun|vibes|content|culture|coffee|memes|good vibes|snacks|hot takes|takes|chaos|hustle|growth|mischief|awesome|everything|all things|sales|marketing|the (internet|office|slack|break room|group chat|home|family|kitchen|grill)|my (house|home|family|kitchen)))\b|\b(bar|medical|nursing|hospital|dental|bankers|realtors?) (association|board of governors)\b|\bboard of governors\b(?! of the federal reserve)/;
|
|
337
|
+
// Mock governments, anywhere in a headline: no official lists one.
|
|
338
|
+
const MOCK = /\b(boys state|girls state|boys nation|girls nation|model (?:[a-z]+ )?(un|united nations|congress|senate|legislature|g20|nato|oas|oecd|asean|parliament|supreme court|court|government|cabinet|eu|european union|arab league|african union|city council)|\w+mun|mun|youth (in|and|&) government|youth (parliament|senate|legislature|council)|mock (trial|congress|legislature|government|election)|junior state of america|junior chamber|jci|cowboy action|sass|reenact\w*|student (?:parliament|supreme court|court))\b/;
|
|
339
|
+
// A part about someone's past or a role beside the mock body isn't one: "Model UN Alumni".
|
|
340
|
+
const MOCK_PAST = /\b(alumni|alum|former|ex-|coach|advis[eo]r|mentor|judge|volunteer|director)\b/;
|
|
341
|
+
// A city's, a county's or a school district's government, as its own name
|
|
342
|
+
// says: "City of Orlando", "Orange County", "Broward County Public Schools",
|
|
343
|
+
// "Orlando Fire Department", "District 5".
|
|
344
|
+
const LOCAL_GOV = /^(?:the )?(?:city|town|village|borough|township|county|parish|municipality) of (?!hope\b)[a-z]|\b(?:city|town|village|county) (?:hall|government|commission)\b|^(?:the )?(?:[a-z.'-]+ ){1,3}(?:county|metropolitan|metro|city|town) council$|^(?:the )?city and county of [a-z]|\bboard of (?:county )?(?:commissioners|supervisors|aldermen|selectmen|education)\b|\b(?:public schools|school district|independent school district|isd|unified school district|county schools)\b|\bfire (?:rescue|department|district)\b|\bpolice department\b|\bsheriff['’]?s? (?:office|department)\b/;
|
|
345
|
+
// A county, a district or a ward named alone is a place: a local office's
|
|
346
|
+
// ("Commissioner, Orange County", "Council Member, District 5"), or where
|
|
347
|
+
// anyone else works ("Ambassador | Orange County | lululemon").
|
|
348
|
+
const LOCAL_PLACE = new RegExp(String.raw`^(?:(?:[a-z.'-]+ ){1,3}(?:county|parish)(?: government)?|(?:district|ward|precinct|seat|place) \d+|\d+(?:st|nd|rd|th) district|at-large)(?:,\s*(?:[a-z]{2}|${STATES}))?$`);
|
|
349
|
+
// Tribal nations, by the names most use.
|
|
350
|
+
const TRIBES = String.raw`cherokee|navajo|choctaw|chickasaw|muscogee|creek|seminole|osage|oneida|seneca|mohawk|onondaga|potawatomi|shawnee|comanche|kiowa|apache|hopi|zuni|lakota|dakota|oglala|sioux|ojibwe|chippewa|ho-chunk|menominee|blackfeet|crow|shoshone|arapaho|ute|yakama|nez perce|tlingit|haida|lumbee|pequot|mashantucket|mohegan|miccosukee|cheyenne|pawnee|quapaw|wyandotte|delaware|lenape|kickapoo|winnebago|omaha|ponca|tohono o'odham|gila river|salt river|pascua yaqui|yaqui|colville|tulalip|puyallup|lummi|makah|nisqually|quinault|coeur d'alene|kootenai|salish|klamath|karuk|yurok|hoopa|pechanga|morongo|agua caliente|san manuel|chumash|pomo|miwok|washoe|paiute|jicarilla|mescalero|laguna|acoma|isleta|jemez|taos|narragansett|wampanoag|passamaquoddy|penobscot|catawba|coushatta|chitimacha`;
|
|
351
|
+
// A tribe's government, not a business it owns ("Cherokee Nation Businesses")
|
|
352
|
+
// or a brand with a tribe's word ("Tribe Capital", "Growth Nation").
|
|
353
|
+
const TRIBAL_GOV = new RegExp(String.raw`\b(?:${TRIBES})(?: (?:indian|tribal))? (?:nation|tribe|tribes|indians|indian community|indian tribe|reservation)(?: of (?:the )?[a-z ]+)?$|\b(?:${TRIBES})[a-z -]* indian community\b|\b(?:nation|tribe|pueblo|band) of (?:the )?(?:${TRIBES})\b|\bpueblo of [a-z]+|\b(?:tribal (?:council|government|court|nation)|indian (?:community|reservation|tribe)|rancheria|first nations?)$|\bband of [a-z ]+ indians\b`);
|
|
354
|
+
const TRIBAL_BUSINESS = /\b(businesses|enterprises|holdings|entertainment|gaming|casino|resort|industries|development corporation|corp|corporation|inc|llc)\b/;
|
|
355
|
+
// A legislature named as the whole organization: "Florida Senate", "U.S. House of Representatives".
|
|
356
|
+
const LEGIS = new RegExp(String.raw`^(?:the )?(?:(?:u\.?\s?s\.?|united states|${STATES})\s+)?(?:state\s+)?(?:house of (?:representatives|delegates)|senate|legislature|congress)$|^(?:the )?senate of (?:the )?[a-z ]+$|^(?:the )?(?:${NATIONS}) senate$|^(?:(?:${STATES}) )?(?:state )?(?:house|senate|assembly|legislative) district \d+$|^(?:the )?(?:${STATES})\s+(?:state\s+)?(?:house|assembly|general assembly)$|^(?:the )?state (?:house|assembly)$`);
|
|
357
|
+
// A state's government itself, as the whole organization: "State of Florida".
|
|
358
|
+
const STATE_ITSELF = new RegExp(String.raw`^(?:the )?(?:(?:state|commonwealth|territory) of (?:${STATES})|(?:${STATES})(?: state)? government|government of (?:${STATES}))$`);
|
|
359
|
+
// A federal agency, by its name or its initials, at the start of an organization: "FBI Miami Field Office".
|
|
360
|
+
const AGENCY_NAMES = String.raw`(?:u\.?\s?s\.?\s|united states )?(?:environmental protection agency|federal aviation administration|federal emergency management agency|national aeronautics and space administration|transportation security administration|agency for international development|drug enforcement administration|national highway traffic safety administration|general services administration|small business administration|centers for medicare (?:and|&) medicaid services|federal highway administration|federal transit administration|federal railroad administration|maritime administration|national oceanic and atmospheric administration|federal bureau of investigation|internal revenue service|social security administration|customs and border protection|immigration and customs enforcement|citizenship and immigration services|secret service|marshals service|bureau of alcohol, tobacco, firearms and explosives|occupational safety and health administration|food and drug administration|veterans health administration|executive office for immigration review|border patrol)`;
|
|
361
|
+
const AGENCY_INITIALS = String.raw`fcc|sec|ftc|fec|nrc|ferc|cftc|eeoc|nlrb|uspto|fdic|cfpb|epa|faa|fema|nasa|tsa|usaid|dea|nhtsa|gsa|sba|cms|fhwa|fta|fra|noaa|fbi|irs|ssa|cbp|uscis|atf|osha|fda|vha|hsi|usss|usms|dhs|doj|hud|usda|hhs`;
|
|
362
|
+
const AGENCY = new RegExp(String.raw`^(?:the )?(?:${AGENCY_NAMES}|(?:u\.?\s?s\.?\s)?(?:${AGENCY_INITIALS}))(?:,?\s+(?:(?:[a-z.]+\s+){0,2}(?:field|district|regional|area|resident) office|region(?:al office)?(?: \d+| [ivx]+)?|headquarters|hq|(?:[a-z]+\s+){0,2}(?:division|sector)))?$`);
|
|
363
|
+
const AGENCY_EXACT = new RegExp(String.raw`^(?:the )?(?:${AGENCY_NAMES}|(?:u\.?\s?s\.?\s)(?:${AGENCY_INITIALS}))$`);
|
|
364
|
+
// An organization that isn't a government's, whatever else it says: "State of Florida Credit Union".
|
|
365
|
+
const NOT_GOV = /\b(credit union|fcu|bank|inc|llc|corp|corporation|company|foods|brands|pharma\w*|holdings|solutions|consulting|partners|realty|dental|university|college|academy|montessori|preparatory|prep school|day school|christian school|catholic school|hospital|health system|medical center|church|association|society|club|league|foundation|chapter|tech council|chamber|hotel|inn|suites|resort|restaurant|podcast|show|radio|meeting|forum|market|coffee|cafe|grill|garage|grocery|salon|studio|brewing|bistro|tavern|pub|deli|bakery|gallery|pac|press|accounts?|team|programs|contracts|relations|partnerships)\b/;
|
|
366
|
+
// A part that names no organization: a place, a credential, who they are.
|
|
367
|
+
const PLACE = new RegExp(String.raw`^(?:the )?(?:(?:state|commonwealth) of )?(?:${STATES}|${COUNTRIES})(?: state)?$|^[a-z .'-]+,\s*[a-z]{2}$|^[a-z]{2}$|\b(?:area|metro|region|based)\b`);
|
|
368
|
+
const NOT_AN_ORG = /^(?:cpa|mba|phd|jd|esq\.?|md|rn|pmp|cfa|cfp|pe|ms|ma|mpa|mph|llm|shrm-[a-z]+)$|\b(?:veteran|vet|mom|dad|mother|father|husband|wife|parent|grand(?:ma|pa|mother|father|parent)|christian|believer|author|speaker|podcaster|host|advocate|volunteer|mentor|runner|golfer|traveler|lover|enthusiast|fan|licensed|admitted|bar|retired|former)\b/;
|
|
369
|
+
const LEAD = String.raw`(?:(?:acting|interim|the|honorable|hon\.?|\d+(?:st|nd|rd|th)|principal|u\.?\s?s\.?|united states|(?:two|three|four|five|\d)[-\s]term|longtime|elected|sitting|incumbent|current|active duty)\s+)*`;
|
|
370
|
+
// A company's or a board's officers: "Assistant Secretary of the Corporation", "of Acme Holdings Inc.".
|
|
371
|
+
const CORP_OFFICER = /\b(corporation|company|board|inc\.?|llc|holdings|ltd|plc|trust|fund|bank|partners|group)\b/;
|
|
372
|
+
// Judging that isn't a court's: competitions, fairs, moot courts, a food court.
|
|
373
|
+
const NOT_COURT = /\b(universit(y|ies)|college|students?|sga|moot|mock|sommeliers?|food court|fairs?|competitions?|hackathons?|pageants?|shows?|debate|awards?|contests?|olympiads?|festivals?|teen court|cook-?off|bake-?off|rodeo|barrel racing|cheer(?:leading)?|bbq|barbecue|chili|ffa|4-h|livestock|cattle|horse show|equestrian|kennel club|dog show|gymnastics|figure skating|culinary|robotics|spelling bee|martial arts|pitch|startup|demo day|shark tank|bodybuilding|physique|npc|ifbb|powerlifting|car shows?|dance|cheer)\b/;
|
|
374
|
+
const FED_BODY = String.raw`(?:u\.?\s?s\.?\s|united states )?(?:securities and exchange commission|consumer product safety commission|nuclear regulatory commission|commodity futures trading commission|equal employment opportunity commission|international trade commission|federal (?:communications|trade|election|energy regulatory|maritime) commission|federal deposit insurance corporation|federal reserve(?! bank)(?: board| system)?|board of governors of the federal reserve(?: system)?|national (?:labor relations|transportation safety) board|internal revenue service|food and drug administration|social security administration|customs and border protection|council of economic advisers)`;
|
|
375
|
+
|
|
376
|
+
// The services, and the units and bases that say which one.
|
|
377
|
+
const SVC = String.raw`(?:(?:${NATIONS})\s+)?(?:army national guard|air national guard|national guard|army reserves?|navy reserves?|naval reserves?|air force reserves?|marine corps reserves?|coast guard reserves?|army (?:nurse|medical|dental|medical service|medical specialist|veterinary|chaplain) corps|navy (?:nurse|medical service|chaplain) corps|army|navy|air force|marine corps|marines|space force|coast guard|armed forces|canadian armed forces|department of defense|public health service(?: commissioned corps)?|noaa corps|noaa commissioned officer corps)|(?:u\.?\s?s\.?|united states) marine|\b(?:usmc|usmcr|usaf|usafr|afrc|usn|usnr|uscg|uscgr|ussf|usar|arng|ang|usphs|dod|raf|idf)\b`;
|
|
378
|
+
// "Army" and "Navy" alone are also a crowd's or a brand's words ("Church Army",
|
|
379
|
+
// "Brand Army", "Grassroots Army"): alone, a service is the whole organization
|
|
380
|
+
// ("Captain, U.S. Navy", "Colonel, Army National Guard"), or qualified.
|
|
381
|
+
const QUALIFIED_SVC = String.raw`(?:(?:${NATIONS}|the)\s+(?:army|navy|air force|marine corps|marines|space force|coast guard)|army national guard|air national guard|national guard|(?:army|navy|naval|air force|marine corps|coast guard) reserves?|air force|marine corps|space force|coast guard|armed forces|department of defense|public health service|noaa corps|noaa commissioned officer corps|army (?:nurse|medical|dental|medical service|medical specialist|veterinary|chaplain) corps|navy (?:nurse|medical service|chaplain) corps)|(?:u\.?\s?s\.?|united states) marine|\b(?:usmc|usmcr|usaf|usafr|afrc|usn|usnr|uscg|uscgr|ussf|usar|arng|ang|usphs|dod|raf|idf)\b`;
|
|
382
|
+
const NOT_SVC_AFTER = String.raw`(?!\s(?:spouses?|wife|wives|husbands?|moms?|mothers?|dads?|fathers?|grandmas?|grandpas?|brats?|famil(?:y|ies)|kids?|rotc|jrotc|federal|exchange|cross|pier|surplus|one|league|association|times|history|museum|band|academy|veterans?|vet|of|navy|army|surplus|store|beach|lifeguards?|blue|ferry|taxi|island|chess|debate|esports|rifle|pistol|sports|athletics|club|team|choir|chorus|yard|coffee|brewing|supply|outfitters|paintball|airsoft|wrestling|swimming|rugby|lacrosse|football|soccer|basketball|baseball|hockey|golf|tennis|volleyball|softball|sailing|rowing|crew)\b)`;
|
|
383
|
+
const SERVICE_IN = new RegExp(String.raw`(?<!(?:salvation|old|texas|nebraska|great|commemorative|church|brand|grassroots)\s)(?:${QUALIFIED_SVC})${NOT_SVC_AFTER}|\b(?:uss|usns|uscgc|afb)\s|\bnaval\b|\bv[a-z]{1,3}-\d+\b|\b(?:air force base|joint base|naval air station|naval station|marine corps (?:base|air station)|marine (?:division|expeditionary|regiment|aircraft wing|logistics group))\b|\b\d+(?:st|nd|rd|th) (?:[a-z]+ ){0,2}(?:division|brigade|battalion|regiment|wing|squadron|fleet|corps|group|airborne|infantry|cavalry)\b|\b(?:u\.?\s?s\.? )?(?:central|european|pacific|indo-pacific|africa|southern|northern|special operations|cyber|space|strategic|transportation) command\b|\b(?:pacific|u\.?\s?s\.? air forces in europe|air forces) air forces?\b|\bpacific air forces\b|\b(?:army|navy|air force|marine corps|fleet) forces command\b|\bnavy region\b|\b(?:u\.?\s?s\.? )?(?:pacific|atlantic|fleet forces|\d+(?:st|nd|rd|th)|second|third|fifth|sixth|seventh) fleet\b|\b(?:air combat|air mobility|air education and training|global strike|materiel|training and doctrine|forces|futures|sealift|military sealift|space and missile systems) command\b`);
|
|
384
|
+
// A part whose organization is just a service ("Colonel, Army", "Captain, Navy").
|
|
385
|
+
const REST_IS_SERVICE = new RegExp(String.raw`^(?:the\s+)?(?:${SVC})\b${NOT_SVC_AFTER}`);
|
|
386
|
+
const NAVY_IN = new RegExp(String.raw`(?<!(?:old|texas|nebraska|great)\s)\b(?:navy|naval|usn|usnr|coast guard|uscg|uscgr|noaa corps|noaa commissioned officer corps|public health service|usphs|uss|usns|uscgc)\b${NOT_SVC_AFTER}`);
|
|
387
|
+
// A part that is only a service ("U.S. Navy", "Retired U.S. Army", "USAF (Ret.)").
|
|
388
|
+
const SERVICE_ONLY = new RegExp(String.raw`^(?:(?:retired|former|ret\.?|retd\.?)\s+)?(?:the\s+)?(?:(?:${STATES})\s+)?(?:${SVC})(?:\s+reserves?)?(?:,?\s*(?:\((?:ret\.?|retd\.?|retired|active duty)\)|ret\.?|retd\.?|retired|active duty))?$`);
|
|
389
|
+
const RETIRED_SERVICE = /^(retired|former|ret\b)|\bret\.?\)?$|\bretired\)?$/;
|
|
390
|
+
// A service's retirement tag, a rank or two between: "U.S. Army (Ret.)", "(USAF, Ret.)", "Army Colonel (Retired)".
|
|
391
|
+
const SERVICE_RETIRED = new RegExp(String.raw`(?:${SVC})(?:\s+[a-z.]+){0,2}[\s,]*(?:\(\s*)?(?:ret\.?|retd\.?|retired)\b`);
|
|
392
|
+
const RANKED = new Set(['seniorGeneral', 'general', 'colonel', 'ltColonel', 'major', 'seniorEnlisted', 'nco']);
|
|
393
|
+
const MIL_UNIT = /\b(division|brigade|battalion|squadron|wing|regiment|fleet|command|corps|group|task force|carrier|uss|usns|uscgc|infantry|airborne|cavalry|artillery|armor|special forces|seal team|marines?|garrison|installation|base|detachment|company|battery|troop|flight)\b/;
|
|
394
|
+
const NOT_MILITARY = /\b(army and navy union|honorary|kentucky colonels?|tennessee colonel|commemorative air force|great navy|texas navy|nebraska navy|civil air patrol|rotc|jrotc|cadets?|midshipm[ae]n|american legion|vfw|veterans of foreign wars|amvets|disabled american veterans|marine corps league|navy league|air force association|association of the united states army|ausa|moaa|auxiliary|order of the|boy scouts|girl scouts|scouts bsa|sea cadets|young marines|salvation army|licensed|license|mariner|merchant marine|charter|vessel|yacht|\d+\s*-?\s*ton|oupv|six-pack|halo|cosplay\w*|reenact\w*|star trek|police|sheriff['’]?s?|lapd|nypd|highway patrol|state patrol|troopers?|corrections|fire (?:department|rescue|district|dept|station|company|service|marshal)|firefighters?|(?:engine|ladder|truck|rescue|squad|station) \d+|power squadron|boating|sailing|society|relief|ministr(?:y|ies)|fans?|store|shop|surplus|park rangers?|community services|engine company|restaurants?|street|avenue|cleaning|architects?|architecture|lifeguards?|boys brigade|girls brigade|pickleball|squad|hospital|university|college|drum corps|marching|charters?|fishing|ems|ambulance|museum|docent|association|veterans|alumni|chapter|memorial|credit union|fcu|casino|varsity|intramural|club|league|rugby|lacrosse|football|soccer|basketball|baseball|hockey|crew|rowing|sailing team|athletics|captain of|wrestling|swimming|diving|track|cross country|fencing|boxing|golf|tennis|volleyball|softball|water polo|rifle team|pistol team|squash|sprint football|gymnastics|cheer(?:leading)?|starfleet|sea scouts?|model ships?|paintball|airsoft|corps of cadets|citadel|composite squadron|model shipbuilders|chess|debate|esports|(?:usna|usma|usafa|uscga)\s*['’]?\d{2})\b|(?<!seal |combat |protection |support |engagement |training |assistance )\bteam\b/;
|
|
395
|
+
// Civilian words in a part beside a rank whose own part names its service only
|
|
396
|
+
// weakly (a numbered unit, a USS name, "naval"): "Commander, 3rd Division |
|
|
397
|
+
// Northwind Police Department", "Commanding Officer, USS Northwind | Starfleet International".
|
|
398
|
+
const NOT_MILITARY_NEAR = /\b(harbor pilot|ship pilot|river pilot|parasail\w*|watersports|sportfishing|marina|dive|eve online|nerf|warhammer|esports|gaming|game|guild|clan|minecraft|roblox|fortnite|call of duty|discord|virtual|dcs world|flight sim\w*|milsim|texas navy|nebraska navy|great navy|salvation army|cruises|fishing|guide|charters?|boat|yacht|tours?|ferry|cruise|towing|tug|airlines?|air lines|federal air marshal|fams|tsa|starfleet|star trek|sea cadets?|sea scouts?|cosplay\w*|halo|reenact\w*|police|sheriff|fire (?:department|rescue)|lapd|nypd|model ships?|shipbuilders|civil air patrol|cap|corps of cadets|cadets?|citadel|vmi|rotc|jrotc|paintball|airsoft)\b/;
|
|
399
|
+
// A part that belongs with a rank: its service, its unit, its job, a place, "Retired".
|
|
400
|
+
const milish = (p) => SERVICE_ONLY.test(p) || RETIRED_ONLY.test(p) || RETIRED_SERVICE.test(p) || (PLACE.test(p) && !/^[a-z]{2}$/.test(p)) || /\b(?:army|navy|naval|air force|marine|coast guard|military|infantry|staff) officer\b/.test(p) || (MIL_ROLE.test(p) && !isOrg(p) && !NOT_MILITARY_NEAR.test(p))
|
|
401
|
+
|| (MIL_UNIT.test(p) && /\d/.test(p) && !isOrg(p) && !NOT_MILITARY.test(p) && !NOT_MILITARY_NEAR.test(p));
|
|
402
|
+
const STRONG_SERVICE = new RegExp(String.raw`(?:${QUALIFIED_SVC})`);
|
|
403
|
+
// Short forms that are also credentials or companies: a CSM is a Certified ScrumMaster,
|
|
404
|
+
// a CPT a personal trainer, LTC long-term care, ADM and MSG companies. With their
|
|
405
|
+
// service in the same part only ("LTC, U.S. Army"), never one borrowed from another part.
|
|
406
|
+
const LOOKALIKE_SHORT = /^(?:ltc|cpt|csm|cmc|msg|msgt|mg|bg|adm|gen)\b/;
|
|
407
|
+
|
|
408
|
+
// Military short forms, read only at the start of a title and only beside a service.
|
|
409
|
+
const RANK_SHORT = [
|
|
410
|
+
[/^(?:lt\.?\s?gen(?:eral|\.)?|ltgen|ltg|lieut\.?\s?general)(?=\s|$)/, 'lieutenant general'],
|
|
411
|
+
[/^(?:maj\.?\s?gen(?:eral|\.)?|majgen|mg)(?=\s|$)/, 'major general'],
|
|
412
|
+
[/^(?:brig\.?\s?gen(?:eral|\.)?|bgen|bg)(?=\s|$)/, 'brigadier general'],
|
|
413
|
+
[/^gen\.?(?=\s|$)/, 'general'],
|
|
414
|
+
[/^(?:vice\s?adm\.?|vadm)(?=\s|$)/, 'vice admiral'],
|
|
415
|
+
[/^(?:rear\s?adm\.?|radm|rdml)(?=\s|$)/, 'rear admiral'],
|
|
416
|
+
[/^adm\.?(?=\s|$)/, 'admiral'],
|
|
417
|
+
[/^(?:lt\.?\s?col(?:onel|\.)?|lieut\.?\s?colonel|ltcol|ltc|lcol)(?=\s|$)/, 'lieutenant colonel'],
|
|
418
|
+
[/^col\.?(?=\s|$)/, 'colonel'],
|
|
419
|
+
[/^(?:lt\.?\s?cdr\.?|lcdr|ltcdr|lt\.?\s?commander)(?=\s|$)/, 'lieutenant commander'],
|
|
420
|
+
[/^cdr\.?(?=\s|$)/, 'commander'],
|
|
421
|
+
[/^(?:capt\.?|cpt)(?=\s|$)/, 'captain'],
|
|
422
|
+
[/^maj\.?(?=\s|$)/, 'major'],
|
|
423
|
+
[/^lt\.?(?=\s|$)/, 'lieutenant'],
|
|
424
|
+
[/^csm(?=\s|$)/, 'command sergeant major'],
|
|
425
|
+
[/^(?:sgtmaj|sgt\.?\s?maj\.?)(?=\s|$)/, 'sergeant major'],
|
|
426
|
+
[/^sgm(?=\s|$)/, 'sergeant major'],
|
|
427
|
+
[/^cmsgt(?=\s|$)/, 'chief master sergeant'],
|
|
428
|
+
[/^(?:mcpo|mcpon|cmc)(?=\s|$)/, 'master chief'],
|
|
429
|
+
[/^scpo(?=\s|$)/, 'senior chief petty officer'],
|
|
430
|
+
[/^(?:cw[2-5]|cwo[2-5]?)(?=\s|$)/, 'chief warrant officer'],
|
|
431
|
+
[/^wo1(?=\s|$)/, 'warrant officer'],
|
|
432
|
+
[/^sfc(?=\s|$)/, 'sergeant first class'],
|
|
433
|
+
[/^1sg(?=\s|$)/, 'first sergeant'],
|
|
434
|
+
[/^(?:msg|msgt)(?=\s|$)/, 'master sergeant'],
|
|
435
|
+
[/^smsgt(?=\s|$)/, 'senior master sergeant'],
|
|
436
|
+
[/^mgysgt(?=\s|$)/, 'master gunnery sergeant'],
|
|
437
|
+
[/^gysgt(?=\s|$)/, 'gunnery sergeant'],
|
|
438
|
+
];
|
|
439
|
+
// [the whole title, its level, whether it needs a service beside it]. The
|
|
440
|
+
// flag ranks spelled out, and the services' chiefs, are nobody else's. A
|
|
441
|
+
// captain is 'captain' (a colonel's equal in a navy, a major's elsewhere), a
|
|
442
|
+
// lieutenant counts only in a navy (a major's equal), and a commander,
|
|
443
|
+
// commanding or executive officer is 'commander': their unit decides.
|
|
444
|
+
const RANKS = [
|
|
445
|
+
[String.raw`chief of staff of the (?:u\.?\s?s\.?\s)?(?:army|air force)|vice chief of staff of the (?:u\.?\s?s\.?\s)?(?:army|air force)|chief of naval operations|vice chief of naval operations|commandant of the (?:u\.?\s?s\.?\s)?(?:marine corps|coast guard)|(?:vice )?chairman of the joint chiefs(?: of staff)?|chief of space operations|chief of the national guard bureau`, 'seniorGeneral', false],
|
|
446
|
+
[String.raw`sergeant major of the (?:army|marine corps)|master chief petty officer of the (?:navy|coast guard)|chief master sergeant of the (?:air force|space force)|senior enlisted advisor to the chairman`, 'seniorEnlisted', false],
|
|
447
|
+
[String.raw`lieutenant general|vice admiral|(?:three|four|3|4)[-\s]star (?:general|admiral)|general of the army|fleet admiral`, 'seniorGeneral', false],
|
|
448
|
+
[String.raw`air marshal|air chief marshal`, 'seniorGeneral', true],
|
|
449
|
+
[String.raw`major general|brigadier general|rear admiral(?: \((?:lower|upper) half\))?|(?:one|two|1|2)[-\s]star (?:general|admiral)`, 'general', false],
|
|
450
|
+
[String.raw`air vice marshal|air commodore`, 'general', true],
|
|
451
|
+
[String.raw`(?:deputy )?commanding general|brigadier|commodore`, 'general', true],
|
|
452
|
+
[String.raw`general|admiral`, 'seniorGeneral', true],
|
|
453
|
+
[String.raw`lieutenant colonel|wing commander`, 'ltColonel', true],
|
|
454
|
+
[String.raw`colonel|group captain`, 'colonel', true],
|
|
455
|
+
[String.raw`(?:(?:deputy|vice|garrison|installation|base|battalion|squadron|wing|group|ship|regimental|brigade|division|corps|fleet|company|battery|troop|flight|detachment|task force) )*commander|commanding officer|executive officer`, 'commander', true],
|
|
456
|
+
[String.raw`lieutenant commander|squadron leader|major`, 'major', true],
|
|
457
|
+
[String.raw`captain`, 'captain', true],
|
|
458
|
+
[String.raw`lieutenant`, 'lieutenant', true],
|
|
459
|
+
[String.raw`command sergeant major|sergeant major|(?:command )?master chief(?: petty officer)?|chief master sergeant|command chief(?: master sergeant)?|master gunnery sergeant`, 'seniorEnlisted', true],
|
|
460
|
+
[String.raw`(?:senior )?chief petty officer|senior chief|(?:senior )?master sergeant|(?:first|1st) sergeant|sergeant first class|gunnery sergeant|(?:chief )?warrant officer(?: [1-5])?`, 'nco', true],
|
|
461
|
+
[String.raw`o-?(?:9|10)`, 'seniorGeneral', true],
|
|
462
|
+
[String.raw`o-?[78]`, 'general', true],
|
|
463
|
+
[String.raw`o-?6`, 'colonel', true],
|
|
464
|
+
[String.raw`o-?5`, 'ltColonel', true],
|
|
465
|
+
[String.raw`o-?4`, 'major', true],
|
|
466
|
+
[String.raw`e-?9`, 'seniorEnlisted', true],
|
|
467
|
+
[String.raw`e-?[78]`, 'nco', true],
|
|
468
|
+
].map(([src, key, needsService]) => [new RegExp(`^(?:${src})$`), key, needsService]);
|
|
469
|
+
const LEVEL_DOWN = { seniorGeneral: 'general', general: 'colonel', colonel: 'ltColonel', ltColonel: 'major', major: 'major' };
|
|
470
|
+
|
|
471
|
+
// A unit's usual commander, smallest first: the first unit a title names is the
|
|
472
|
+
// one commanded ("Executive Officer, 1st Battalion, 75th Ranger Regiment" is a
|
|
473
|
+
// battalion's).
|
|
474
|
+
const UNIT_LEVELS = [
|
|
475
|
+
[/\b(company|battery|troop|flight|detachment)\b/, 'major'],
|
|
476
|
+
[/\b(battalion|squadron|ship|uss|uscgc|submarine|destroyer|frigate|cutter|v[a-z]{1,3}-\d+)\b/, 'ltColonel'],
|
|
477
|
+
[/\b(brigade|wing|group|regiment|garrison|installation|base|naval (air )?station|naval hospital|joint base)\b/, 'colonel'],
|
|
478
|
+
[/\b(division|strike group|region|command|(surface|submarine|naval|expeditionary) forces?|air forces)\b/, 'general'],
|
|
479
|
+
[/\b(corps|fleet|combatant command|(materiel|combat|mobility|training and doctrine|futures|forces|special operations|space|cyber|strategic|transportation|northern|southern|central|european|pacific|indo-pacific|africa|global strike|education and training) command)\b/, 'seniorGeneral'],
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
/** A commander's (or a commanding or executive officer's) level, from their unit. */
|
|
483
|
+
function commandLevel(n, where) {
|
|
484
|
+
const text = `${n.replace(/\b(commander|commanding officer|executive officer|deputy|vice)\b/g, ' ')} ${where}`
|
|
485
|
+
.replace(new RegExp(String.raw`(?:${SVC})(?![a-z])`, 'g'), ' ').replace(/\b(?:nurse|medical|dental|medical service|chaplain|supply|civil engineer|judge advocate general['’]?s?) corps\b/g, ' ');
|
|
486
|
+
let key = 'ltColonel';
|
|
487
|
+
let first = Infinity;
|
|
488
|
+
for (const [re, level] of UNIT_LEVELS) {
|
|
489
|
+
const m = text.match(re);
|
|
490
|
+
if (m && (m.index < first || (m.index === first && level === 'seniorGeneral'))) { first = m.index; key = level; }
|
|
491
|
+
}
|
|
492
|
+
if (/\b(deputy|vice)\b/.test(n) || /^executive officer/.test(n)) key = LEVEL_DOWN[key];
|
|
493
|
+
return key;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** A rank written as the title, or null; `retired` when it says so. */
|
|
497
|
+
// A serving officer's job, as its own part: "F-35 Pilot", "Intelligence Officer".
|
|
498
|
+
const MIL_ROLE = /\b(pilot|aviator|navigator|operator|surgeon|physician|chaplain|jag|strategist|intelligence|logistics|signal|infantry|armor|artillery|cyber|special forces|seal|ranger|aviation|acquisition|commander|garrison|battalion|brigade|squadron|staff officer)\b/;
|
|
499
|
+
|
|
500
|
+
function militaryTitle(raw, t, title, rest, prev, next, others, nextRetired = false, after = '') {
|
|
501
|
+
if (NOT_MILITARY.test(t)) return null;
|
|
502
|
+
// A civilian word beside the rank ("| Warhammer", "| Fishing Guide") counts unless the rank's own
|
|
503
|
+
// part names its service; the Coast Guard's name is also on boat captains' licences.
|
|
504
|
+
const ownService = (STRONG_SERVICE.test(t.replace(/\bgeneral of the army\b/, ' ')) || REST_IS_SERVICE.test(rest)) && !/\b(?:coast guard|uscg)\b/.test(t);
|
|
505
|
+
if (!ownService && [prev, next].some((p) => p && NOT_MILITARY_NEAR.test(p))) return null;
|
|
506
|
+
const atOrg = rest.match(/(?:^|\s)(?:at|@)\s+(.+)$/);
|
|
507
|
+
if (atOrg && !SERVICE_IN.test(atOrg[1]) && !REST_IS_SERVICE.test(atOrg[1])) return null;
|
|
508
|
+
let n = title.replace(/(?<=[a-z])-(?=[a-z])/g, ' ').replace(/^(?:(?:acting|interim|the)\s+)*/, '');
|
|
509
|
+
let retired = /\(\s*r\s*\)/.test(raw.toLowerCase());
|
|
510
|
+
n = n.replace(/,?\s+(?:\(?ret\.?\)?|\(?retired\)?)$/, () => { retired = true; return ''; })
|
|
511
|
+
.replace(/,?\s+\(?active duty\)?$/, '');
|
|
512
|
+
let service = SERVICE_IN.test(t) || REST_IS_SERVICE.test(rest) || /^usa(?:,|$)/.test(rest);
|
|
513
|
+
// A numbered unit, a USS name or "naval" alone is weak: not beside a police
|
|
514
|
+
// department, a fire rescue, a fan club or any other organization.
|
|
515
|
+
const weak = service && !STRONG_SERVICE.test(t) && !REST_IS_SERVICE.test(rest);
|
|
516
|
+
if (weak && (isOrg(rest.replace(/\b(group|company|hospital|battery|troop)\b/g, ' ')) || [prev, next].some((p) => p && !milish(p)))) service = false;
|
|
517
|
+
const own = service;
|
|
518
|
+
const lead = n.match(new RegExp(`^(?:${SVC}|marine)(?:\\s+reserves?)?\\s+`));
|
|
519
|
+
if (lead) { n = n.slice(lead[0].length); service = true; }
|
|
520
|
+
const trail = n.match(new RegExp(`(?:\\s+(?:in|with|of) the)?\\s+(?:${SVC})$`));
|
|
521
|
+
const variants = trail ? [n, n.slice(0, -trail[0].length)] : [n];
|
|
522
|
+
if (trail) service = true;
|
|
523
|
+
// A service in a part of its own is the rank's when nothing else is named
|
|
524
|
+
// beside the rank but its units and places ("Commander, 1st Brigade | U.S.
|
|
525
|
+
// Army"), not a civilian employer ("Commander, Security Group, Northwind
|
|
526
|
+
// Casino | U.S. Army"); one further off past another title ("LTC |
|
|
527
|
+
// Acquisition Officer | U.S. Army"), not past an employer ("Captain | Delta
|
|
528
|
+
// Air Lines | Retired USAF" is an airline captain).
|
|
529
|
+
const servicePart = (p) => SERVICE_ONLY.test(p) || /^(?:the )?(?:u\.?\s?s\.? )?(?:central|european|pacific|indo-pacific|africa|southern|northern|special operations|cyber|space|strategic|transportation) command$/.test(p);
|
|
530
|
+
const alone = servicePart(next) ? next : !rest && servicePart(prev) ? prev : null;
|
|
531
|
+
const unitsOnly = pieces(rest).every((p) => SERVICE_IN.test(p) || REST_IS_SERVICE.test(p) || PLACE.test(p)
|
|
532
|
+
|| (MIL_UNIT.test(p) && !isOrg(p) && /\d|\b(?:[ivxl]+|fort|camp|joint|naval|marine|battalion|brigade|squadron|regiment|fleet|garrison|infantry|airborne|cavalry|artillery|special forces|seal team|detachment|task force|expeditionary|corps)\b/.test(p)));
|
|
533
|
+
const formerThen = alone === next && !!after && (isOrg(after) || NOT_MILITARY_NEAR.test(after)) && !milish(after); // "Captain | Former USAF | Delta Air Lines"
|
|
534
|
+
// A service before the rank with an employer after it ("U.S. Navy | Captain | Delta Air Lines"),
|
|
535
|
+
// or a civilian job just before a bare rank ("Claims Handler | Admiral | Royal Navy Reserve").
|
|
536
|
+
const otherSide = alone === next ? prev : next;
|
|
537
|
+
const flanked = !otherSide || milish(otherSide); // "Claims Handler | Admiral | Royal Navy Reserve" isn't
|
|
538
|
+
const jobBefore = !rest && !!prev && !milish(prev);
|
|
539
|
+
if (alone && unitsOnly && !formerThen && flanked) { service = true; if (alone === next && (RETIRED_SERVICE.test(next) || nextRetired)) retired = true; }
|
|
540
|
+
// A service further off, past only the rank's own jobs and units ("LTC | Acquisition Officer |
|
|
541
|
+
// U.S. Army", "Colonel | 82nd Airborne Division | U.S. Army"), never past an employer.
|
|
542
|
+
const milPart = (p) => !isOrg(p) && !(isGovOrg(p) && !SERVICE_ONLY.test(p)) && !NOT_MILITARY.test(p) && !NOT_MILITARY_NEAR.test(p)
|
|
543
|
+
&& (!!readPart(p) || MIL_ROLE.test(p) || RETIRED_ONLY.test(p) || (MIL_UNIT.test(p) && /\d|\b(?:division|brigade|battalion|squadron|wing|regiment|fleet|command)\b/.test(p)));
|
|
544
|
+
const at = others.indexOf(t);
|
|
545
|
+
const far = at < 0 ? -1 : others.findIndex((o, i) => i > at + 1 && servicePart(o));
|
|
546
|
+
if (!service && !rest && !jobBefore && far > 0 && others.slice(at + 1, far).every(milPart)) service = true;
|
|
547
|
+
for (let v of variants) {
|
|
548
|
+
let short = false;
|
|
549
|
+
for (const [re, full] of RANK_SHORT) {
|
|
550
|
+
if (re.test(v)) {
|
|
551
|
+
if (LOOKALIKE_SHORT.test(v) && !own && !others.every((o) => o === t || milish(o))) return null;
|
|
552
|
+
v = v.replace(re, full); short = true; break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
for (const [re, key, needsService] of RANKS) {
|
|
556
|
+
if (!re.test(v)) continue;
|
|
557
|
+
if ((needsService || short) && !service) return null;
|
|
558
|
+
const naval = NAVY_IN.test(t) || (!!alone && NAVY_IN.test(alone));
|
|
559
|
+
const level = key === 'captain' ? (naval ? 'colonel' : 'major')
|
|
560
|
+
: key === 'lieutenant' ? (naval ? 'major' : null)
|
|
561
|
+
: key === 'commander' ? commandLevel(v, rest) : key;
|
|
562
|
+
return level ? { key: level, retired } : null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Government titles, [the whole title after LEAD, a level or (c) => level | null].
|
|
569
|
+
// The first pattern that matches decides; a function that returns SKIP lets the
|
|
570
|
+
// later ones try. `c` is { n: the title without LEAD, rest: the part's text
|
|
571
|
+
// after the title, prev, next and after: the parts before and after it, text:
|
|
572
|
+
// the part with the parts on either side, at: whether the part names its
|
|
573
|
+
// organization with "at", us: whether the title says "U.S." }.
|
|
574
|
+
//
|
|
575
|
+
// A title means government only where its organization is a government's
|
|
576
|
+
// (where(), below): "Controller, State of Florida", not "Controller |
|
|
577
|
+
// Texas | Northwind Homes". Only a title nobody else holds counts with no
|
|
578
|
+
// organization named ("State Senator", "Special Agent in Charge"); one that
|
|
579
|
+
// others hold too needs its government named ("Mayor, City of Orlando",
|
|
580
|
+
// "Administrator, U.S. Small Business Administration"), or to be the whole
|
|
581
|
+
// headline ("Mayor of Orlando", "Secretary of Commerce").
|
|
582
|
+
const SKIP = Symbol('not this pattern');
|
|
583
|
+
const place = String.raw`(?:(?:${STATES}|al|ak|az|ar|ca|co|ct|de|fl|ga|hi|id|il|in|ia|ks|ky|la|me|md|ma|mi|mn|ms|mo|mt|ne|nv|nh|nj|nm|ny|nys|nc|nd|oh|ok|or|pa|ri|sc|sd|tn|tx|ut|vt|va|wa|wv|wi|wy) (?:state )?)?`;
|
|
584
|
+
// Courts named by their circuit or district: "Ninth Circuit", "Middle District of Florida".
|
|
585
|
+
const JUDICIAL_AREA = new RegExp(String.raw`^(?:the )?(?:(?:first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|eleventh|twelfth|thirteenth|fourteenth|fifteenth|sixteenth|seventeenth|eighteenth|nineteenth|twentieth|federal|d\.?c\.?|\d+(?:st|nd|rd|th)) (?:judicial )?circuit(?: court)?(?: of [a-z ]+)?|(?:(?:northern|southern|eastern|western|middle|central) )?district of (?:${STATES}))$`);
|
|
586
|
+
const INTERNATIONAL_BODY = new RegExp(String.raw`^(?:the )?(?:${INTERNATIONAL})$`);
|
|
587
|
+
const INTERNATIONAL_IN = new RegExp(String.raw`\b(?:${INTERNATIONAL})\b`);
|
|
588
|
+
// An organization anyone can see is one: a company the lists know, or a name
|
|
589
|
+
// with a company's, a school's or a club's word ("Northwind Pharmaceuticals").
|
|
590
|
+
const ORG_WORD = /\b(inc|llc|ltd|corp|corporation|co|company|group|brands?|foods?|beverages?|pharma\w*|health|healthcare|bank|capital|partners|solutions|services|systems|technolog(?:y|ies)|labs?|media|studios?|apparel|fitness|coaching|consulting|realty|homes|law|title|insurance|airlines?|air lines|university|college|school|academy|institute|association|society|club|league|foundation|church|collective|agency|ventures|holdings|enterprises|industries|motors|logistics|staffing|restaurants?|cafe|coffee|sake|tequila|whisk(?:e)?y|wines?|spirits|brewing|distillery|hotels?|resorts?|casino|creative|design|digital|software|network|plan|clinic|hospital|medical|dental|pharmacy|store|shop|market|mall|refinery|plant|repair|recruiting|properties|real estate|mortgage|lending|financial|investments?|wealth|trust|credit union|kitchen|grill|bakery|diner|martial arts|dojo|gym|maritime|shipping|care|home care|living|travel|tours|yoga|retreats?|wellness|podcast|seeds|exports?|imports?)\b/;
|
|
591
|
+
const DEPT_OF = new RegExp(String.raw`^${DEPT}\b`);
|
|
592
|
+
// Cities people are mayors of: the largest U.S. cities, the state capitals and
|
|
593
|
+
// some more. "Mayor of Tampa" names a city; "Mayor of Candyland" doesn't.
|
|
594
|
+
const CITY = new RegExp(String.raw`^(?:the )?(?:city of )?(?:new york|new york city|los angeles|chicago|houston|phoenix|philadelphia|san antonio|san diego|dallas|austin|jacksonville|fort worth|san jose|columbus|charlotte|indianapolis|san francisco|seattle|denver|oklahoma city|nashville|el paso|las vegas|boston|detroit|portland|louisville|memphis|baltimore|milwaukee|albuquerque|tucson|fresno|sacramento|mesa|kansas city|atlanta|omaha|colorado springs|raleigh|long beach|virginia beach|miami|oakland|minneapolis|tulsa|bakersfield|wichita|arlington|aurora|tampa|new orleans|cleveland|honolulu|anaheim|lexington|stockton|corpus christi|henderson|riverside|newark|saint paul|st\. paul|santa ana|cincinnati|irvine|orlando|pittsburgh|st\. louis|saint louis|greensboro|jersey city|anchorage|lincoln|plano|durham|buffalo|chandler|chula vista|toledo|madison|gilbert|reno|fort wayne|north las vegas|st\. petersburg|saint petersburg|lubbock|irving|laredo|winston-salem|chesapeake|glendale|garland|scottsdale|norfolk|boise|fremont|spokane|santa clarita|baton rouge|richmond|hialeah|san bernardino|tacoma|modesto|huntsville|des moines|yonkers|rochester|moreno valley|fayetteville|fontana|columbus|worcester|port st\. lucie|little rock|augusta|oxnard|birmingham|montgomery|frisco|amarillo|salt lake city|grand rapids|huntington beach|overland park|glendale|tallahassee|grand prairie|mckinney|cape coral|sioux falls|peoria|providence|vancouver|knoxville|akron|shreveport|mobile|brownsville|newport news|fort lauderdale|chattanooga|tempe|ontario|santa rosa|elk grove|salem|pembroke pines|eugene|garden grove|cary|fort collins|corona|springfield|jackson|alexandria|hayward|clarksville|lakewood|lancaster|salinas|palmdale|springfield|macon|sunnyvale|pomona|killeen|escondido|pasadena|naperville|bellevue|joliet|murfreesboro|midland|rockford|paterson|savannah|bridgeport|torrance|mcallen|syracuse|surprise|denton|roseville|thornton|miramar|mesquite|olathe|dayton|carrollton|waco|orange|fullerton|charleston|west valley city|visalia|hampton|gainesville|warren|coral springs|cedar rapids|round rock|sterling heights|kent|columbia|santa clara|new haven|stamford|concord|elizabeth|athens|thousand oaks|lafayette|simi valley|topeka|norman|fargo|wilmington|abilene|odessa|pearland|victorville|hartford|vallejo|allentown|berkeley|richardson|arvada|ann arbor|cambridge|sugar land|lansing|evansville|college station|fairfield|clearwater|beaumont|independence|provo|west jordan|murrieta|palm bay|el monte|carlsbad|charleston|temecula|clovis|meridian|westminster|costa mesa|high point|manchester|pueblo|lakeland|pompano beach|west palm beach|antioch|everett|downey|lowell|centennial|elgin|broken arrow|miami gardens|billings|jurupa valley|sandy springs|gresham|lewisville|hillsboro|ventura|greeley|inglewood|waterbury|league city|santa maria|tyler|davie|daly city|boulder|allen|west covina|sparks|wichita falls|green bay|san mateo|norwalk|rialto|las cruces|chico|el cajon|burbank|south bend|renton|vista|davenport|edinburg|tuscaloosa|kenosha|albany|annapolis|augusta|bismarck|carson city|cheyenne|dover|frankfort|harrisburg|helena|jefferson city|juneau|montpelier|olympia|pierre|santa fe|trenton|winter park|winter garden|kissimmee|sanford|oviedo|maitland|altamonte springs|daytona beach|sarasota|naples|boca raton|delray beach|key west|ocala|pensacola|plant city|palm beach|miami beach|coral gables|hoboken|asheville|boulder|flagstaff|santa barbara|palo alto|mountain view|redmond|kirkland|scottsdale|sedona)(?:,\s*(?:[a-z]{2}|${STATES}))?$`);
|
|
595
|
+
// A municipality named as one: "Plant City", "League City", "College Station".
|
|
596
|
+
const MUNICIPALITY = /^[a-z .'-]+ (?:city|town|township|village|borough|station|springs|beach|heights|falls)$/;
|
|
597
|
+
const MUNICIPALITY_OF = /\bof (?:the )?[a-z .'-]+ (?:city|town|township|village|borough|station|springs|beach|heights|falls)\b/g;
|
|
598
|
+
const JOKE_PLACE = /\b(downtown|midtown|uptown|flavortown|funkytown|linkedin|twitter|tiktok|instagram|youtube|social media|the internet|the (block|neighborhood|hood|office|house)|my (house|block|street|home)|[a-z]+ town|real estate|sales|marketing|content|mortgages?)\b/;
|
|
599
|
+
const SALES = /\b(account|sales|manager|representative|specialist|consultant|recruiter|advisor|adviser|hr|benefits|payroll|lead|business development|partner)\b/;
|
|
600
|
+
const FED_RESERVE = /\bfederal reserve (board|system)\b|\bboard of governors of the federal reserve\b|^(the )?federal reserve$/;
|
|
601
|
+
|
|
602
|
+
const pieces = (s) => s.replace(/^(?:at|@)\s+/, '').replace(/(\S)\s*\([^)]*\)/g, '$1').split(/,\s*/).map((p) => p.trim()).filter(Boolean);
|
|
603
|
+
// A state's or the federal government's own body by its name: "Florida Public
|
|
604
|
+
// Service Commission", "Texas Medical Board", "Nuclear Regulatory Commission".
|
|
605
|
+
const STATE_BODY = new RegExp(String.raw`^(?:the )?(?:(?:${STATES})(?! city)(?: state)? )?(?:public service|public utilit(?:y|ies)|corporation|railroad) commission$|^(?:the )?(?:${STATES})(?! city)(?: state)? (?:gaming|ethics|elections?|parole|pardons and paroles|lottery|racing|athletic|real estate|medical|nursing|pharmacy|dental|bar examiners|workers['’]? compensation|industrial|civil service|human rights|tax|utility|water|environmental quality|fish and (?:game|wildlife)) (?:commission|board)$|^(?:the )?${FED_BODY}$`);
|
|
606
|
+
// A company the lists know is a government only when its own name says so
|
|
607
|
+
// (the public dataset has the U.S. Senate and the IRS): "Norfolk Southern
|
|
608
|
+
// Police Department" is the railroad's.
|
|
609
|
+
const govName = (name) => GOV_ORG.test(name) || AGENCY.test(name) || LEGIS.test(name) || STATE_BODY.test(name) || SERVICE_ONLY.test(name) || /\bu\.?\s?s\.? courts?\b/.test(name);
|
|
610
|
+
const knownCompany = (p) => isKnownCompany(p) && !govName(String(cleanCompany(p) || '').toLowerCase());
|
|
611
|
+
// A police or fire department named for a company, a campus or a mall is theirs.
|
|
612
|
+
const privateForce = (p) => { const m = p.match(/^(.+?)\s+(?:police|fire|public safety)(?: department)?$/); return !!m && (isOrg(m[1]) || isKnownCompany(m[1]) || /\b(campus|mall|railroad|railway|university|college|school|hospital|airport)\b/.test(m[1])); };
|
|
613
|
+
const isGovOrg = (p) => !NOT_GOV.test(p) && !CIVIC.test(p) && !STUDENT_ORG.test(p) && !knownCompany(p) && !privateForce(p) && (GOV_ORG.test(p) || LOCAL_GOV.test(p) || STATE_ITSELF.test(p) || LEGIS.test(p) || AGENCY.test(p)
|
|
614
|
+
|| (TRIBAL_GOV.test(p) && !TRIBAL_BUSINESS.test(p) && !isOrg(p)) || INTERNATIONAL_BODY.test(p) || JUDICIAL_AREA.test(p) || STATE_BODY.test(p) || SERVICE_ONLY.test(p));
|
|
615
|
+
const isOrg = (p) => ORG_WORD.test(p) || isKnownCompany(p);
|
|
616
|
+
// A tag after a title that names no organization: a party and state ("(R-FL)"), a bracket.
|
|
617
|
+
const TAG = /^\(?[rdi]-?[a-z]{2}(?:-\d+)?\)?$|^\(.*\)$|^[a-z]{2}-(?:\d{1,2}|al)$|^(?:19|20)\d\d\s*[-–]\s*(?:(?:19|20)\d\d|present)$|^(?:[a-z .]+['’]s? )?\d+(?:st|nd|rd|th) (?:congressional |assembly |senate |house )?(?:district|ward)$/;
|
|
618
|
+
// A place a local office can name without its government's word: after its
|
|
619
|
+
// comma any name that isn't an organization or a title ("City Councilmember,
|
|
620
|
+
// Orlando"); as a part of its own only a county, a district or "City, State"
|
|
621
|
+
// ("Mayor | Oviedo, Florida").
|
|
622
|
+
const CITY_STATE = new RegExp(String.raw`^[a-z .'-]{2,30},\s*(?:[a-z]{2}|${STATES})$`);
|
|
623
|
+
const placeLike = (p, own = true) => PLACE.test(p) || LOCAL_PLACE.test(p) || CITY_STATE.test(p)
|
|
624
|
+
|| (own && (CITY.test(p) || (MUNICIPALITY.test(p) && !JOKE_PLACE.test(p) && !BODY_WORD.test(p))));
|
|
625
|
+
// A name with a body's word is an organization, not a place: "The Joint Commission", "City Year".
|
|
626
|
+
const BODY_WORD = /\b(commission|council|committee|board|association|league|chamber|foundation|society|institute|club|center|centre|program|group|agency|office|department|authority|bureau|services?|team|network|alliance|coalition|project|initiative|fund|trust|year|corps|federation|union|guild|partnership)\b/;
|
|
627
|
+
// A piece after a comma that is a second title, not an organization: "Ambassador, Deputy Permanent Representative…".
|
|
628
|
+
const anotherTitle = (p) => !isOrg(p) && !!readPart(p);
|
|
629
|
+
/**
|
|
630
|
+
* Where the title is held: 'gov' when its organization is a government's,
|
|
631
|
+
* 'other' when it's some other organization, 'none' when none is named. The
|
|
632
|
+
* organization is the rest of the part ("Mayor, City of Orlando"); with none
|
|
633
|
+
* there, the next part, past a place ("Controller | Texas | Northwind
|
|
634
|
+
* Homes"), or a headline's first part when it's a government's ("State of
|
|
635
|
+
* Ohio | Governor"). Anything after a comma is an organization but a second
|
|
636
|
+
* title; a part of its own only when it looks like one, since it may be
|
|
637
|
+
* another title or who they are ("Retired Judge | Mediator").
|
|
638
|
+
*/
|
|
639
|
+
function whereOwn(c) {
|
|
640
|
+
const own = pieces(c.rest).filter((p) => !TAG.test(p));
|
|
641
|
+
if (own.some((p) => CIVIC.test(p) || STUDENT_ORG.test(p))) return 'other';
|
|
642
|
+
if (own.some(isGovOrg)) return 'gov';
|
|
643
|
+
return own.some((p) => !placeLike(p) && (isOrg(p) || CIVIC.test(p) || (!NOT_AN_ORG.test(p) && !anotherTitle(p)))) ? 'other' : 'none';
|
|
644
|
+
}
|
|
645
|
+
// A part that says who someone is or what else they do, not an employer: a
|
|
646
|
+
// job title, a family or a hobby word ("Podcast Host", "Swim Dad", "Little
|
|
647
|
+
// League Coach", "Former Police Chief").
|
|
648
|
+
const BIO = /\b(host|personality|commentator|contributor|analyst|lover|dad|mom|father|mother|husband|wife|coach|fan|advocate|referee|deacon|elder|volunteer|mentor|planner|author|speaker|veteran|runner|golfer|reader|traveler|enthusiast|former|retired|ex-|alum\w*)\b/;
|
|
649
|
+
const PARTY = /^(?:a |proud )?(?:democrat|republican|independent|libertarian|conservative|progressive|moderate)s?$|^\(?[rdi]\)?$/;
|
|
650
|
+
const employer = (p) => !!p && (isOrg(p) || CIVIC.test(p) || STUDENT_ORG.test(p)) && !readPart(p) && !BIO.test(p) && !PARTY.test(p) && !isGovOrg(p);
|
|
651
|
+
// For a title that names its government ("County Sheriff", "Mayor of Tampa",
|
|
652
|
+
// "Ohio State Treasurer"): the other parts are its bio unless they name an employer.
|
|
653
|
+
function whereSoft(c) {
|
|
654
|
+
const own = whereOwn(c);
|
|
655
|
+
if (own !== 'none') return own;
|
|
656
|
+
const next = [c.next, PLACE.test(c.next) || LOCAL_PLACE.test(c.next) || CITY_STATE.test(c.next) ? c.after : ''];
|
|
657
|
+
return next.some((p) => employer(p)) ? 'other' : next.some((p) => p && isGovOrg(p)) || (c.prevLeads && isGovOrg(c.prev)) ? 'gov' : 'none';
|
|
658
|
+
}
|
|
659
|
+
const soft = (key) => (c) => (whereSoft(c) === 'other' ? null : key);
|
|
660
|
+
function where(c) {
|
|
661
|
+
const own = whereOwn(c);
|
|
662
|
+
if (own !== 'none') return own;
|
|
663
|
+
for (const p of [c.next, c.after]) {
|
|
664
|
+
if (!p) break;
|
|
665
|
+
if (isGovOrg(p)) return 'gov';
|
|
666
|
+
if (!PLACE.test(p) && !LOCAL_PLACE.test(p)) {
|
|
667
|
+
if (CIVIC.test(p) || STUDENT_ORG.test(p) || (!/\s(?:at|@)\s/.test(` ${p} `) && isOrg(p))) return 'other';
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return c.prevLeads && isGovOrg(c.prev) ? 'gov' : 'none';
|
|
672
|
+
}
|
|
673
|
+
// A local office's place: its own part's, or the next part's when that is only a place.
|
|
674
|
+
const localPlace = (c) => pieces(c.rest).some((p) => placeLike(p)) || (!c.rest && !!c.next && placeLike(c.next, false) && where(c) !== 'other');
|
|
675
|
+
// The title is the whole headline (a place after it aside).
|
|
676
|
+
const alone = (c) => !c.prev && !c.next && pieces(c.rest).every((p) => PLACE.test(p) || TAG.test(p));
|
|
677
|
+
const besides = (c) => [...pieces(c.rest), c.prevLeads ? c.prev : '', c.next].filter(Boolean);
|
|
678
|
+
const houseOf = (c) => besides(c).some((p) => LEGIS.test(p)) || /\bhouse district \d+/.test(c.part);
|
|
679
|
+
const congress = (c) => /\b(u\.?\s?s\.?|united states) (house|senate|congress)\b|\bcongressional district\b/.test(c.n)
|
|
680
|
+
|| besides(c).some((p) => /^(?:the )?(?:u\.?\s?s\.?|united states) (?:house of representatives|house|senate|congress)$|congressional district(?: \d+)?$|^(the )?congress$/.test(p));
|
|
681
|
+
const houseOwn = (c) => pieces(c.rest).some((p) => LEGIS.test(p));
|
|
682
|
+
const inState = (c) => STATE_NAME.test(c.n) || besides(c).some((p) => STATE_GOV.test(p) || JUST_A_STATE.test(p));
|
|
683
|
+
// Judging that isn't a court's, in the part or the next part when it's where the title is held.
|
|
684
|
+
const notCourt = (c) => NOT_COURT.test(c.part) || (!c.rest && NOT_COURT.test(c.next));
|
|
685
|
+
// Nobody else holds these: any organization but some other one will do.
|
|
686
|
+
const notOther = (key) => (c) => (where(c) === 'other' ? null : key);
|
|
687
|
+
// Titles only officials hold: the other parts are their bio, not where the
|
|
688
|
+
// title is held ("U.S. Senator | Podcast Host").
|
|
689
|
+
const ownOnly = (key) => (c) => (whereSoft(c) === 'other' ? null : key);
|
|
690
|
+
const FOREIGN_WORDS = /\b(department of state|state department|foreign service|embassy|consulate|u\.?\s?s\.? mission|united nations|nato|usaid|diplomat)\b/;
|
|
691
|
+
const FOREIGN = { test: (p) => FOREIGN_WORDS.test(p) && !/\b(program|fulbright|alumni|club|volunteer|society|association|scholar|suites|hotel|hilton|inn)\b/.test(p) };
|
|
692
|
+
// A title that needs its government named, unless it's the whole headline.
|
|
693
|
+
const needGov = (key) => (c) => (where(c) === 'gov' || alone(c) ? key : null);
|
|
694
|
+
// Nothing else in the headline but the title (tags and places aside), or only a government beside it.
|
|
695
|
+
const nothingElse = (c) => alone(c) || where(c) === 'gov';
|
|
696
|
+
const GOV = [
|
|
697
|
+
// Congress and the legislatures.
|
|
698
|
+
[String.raw`(?:u\.?\s?s\.?|united states) senator(?: (?:for|from) .*)?`, ownOnly('govLeader')],
|
|
699
|
+
[String.raw`${place}state senator(?: .*)?`, ownOnly('govSenior')],
|
|
700
|
+
[String.raw`senator`, (c) => (c.us ? ownOnly('govLeader')(c) : SKIP)],
|
|
701
|
+
[String.raw`${place}state (?:representative|rep\.?|delegate|assembly ?(?:member|man|woman|person)|legislator)(?: .*)?`, (c) => (whereSoft(c) !== 'other' && (houseOf(c) || inState(c) || /\bdistrict\b/.test(`${c.n} ${c.rest} ${c.next}`)) ? 'govSenior' : null)],
|
|
702
|
+
[String.raw`${place}(?:district \d+ )?senator(?: (?:for|from|of) .*)?`, (c) => (where(c) === 'other' ? null : congress(c) ? 'govLeader'
|
|
703
|
+
: houseOf(c) || STATE_NAME.test(c.n) || (where(c) === 'gov' && inState(c)) ? 'govSenior' : null)],
|
|
704
|
+
[String.raw`(?:u\.?\s?s\.?|united states) representative (?:for|from) (?:${STATES})['’]?s? (?:\d+(?:st|nd|rd|th)|at-large)(?: congressional)? district`, 'govLeader'],
|
|
705
|
+
[String.raw`congress(?:wo)?man(?: (?:for|from) .*)?|congressperson(?: (?:for|from) .*)?|member of (?:the )?(?:u\.?\s?s\.?\s|united states )?congress|(?:member|delegate) (?:of|to) the (?:u\.?\s?s\.?|united states) house of representatives|resident commissioner(?: of puerto rico)?`, ownOnly('govLeader')],
|
|
706
|
+
[String.raw`(?:(?:u\.?\s?s\.?|united states|congressional) )?(?:representative|rep\.?)(?: (?:for|from|of) .*)?|member|legislator`, (c) => (where(c) === 'other' ? SKIP : congress(c) ? 'govLeader'
|
|
707
|
+
: houseOf(c) ? 'govSenior' : besides(c).some((p) => /\bcounty legislature\b/.test(p)) ? 'govOfficial' : SKIP)],
|
|
708
|
+
[String.raw`(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3})?county legislator(?:, .*)?`, (c) => (nothingElse(c) || pieces(c.rest).some((p) => placeLike(p)) ? 'govOfficial' : null)],
|
|
709
|
+
[String.raw`member of the (?:[a-z.']+ ){0,3}(?:house of (?:representatives|delegates)|state assembly|assembly|state senate|senate|legislature|general assembly)(?: (?:from|for|representing) .*)?`, (c) => (where(c) !== 'other' && inState(c) ? 'govSenior' : null)],
|
|
710
|
+
[String.raw`${place}(?:assembly ?(?:member|man|woman|person)(?: .*)?|delegate(?: .*)?)`, (c) => (where(c) !== 'other' && (houseOf(c) || (STATE_NAME.test(c.n) && !/delegate/.test(c.n))) && inState(c) ? 'govSenior' : null)],
|
|
711
|
+
[String.raw`${place}(?:state )?(?:(?:senate|house|assembly) )?(?:majority|minority) (?:leader|whip)(?: .*)?|speaker(?: pro tem(?:pore)?)?(?: of the (?:u\.?\s?s\.?\s)?(?:house|assembly)(?: of (?:representatives|delegates))?)?(?: .*)?|(?:senate )?president(?: pro tem(?:pore)?)?(?: of the (?:[a-z.']+ ){0,2}senate)?`,
|
|
712
|
+
(c) => (!(/\b(senate|house|assembly|legislature)\b/.test(c.n) || houseOwn(c)) || whereOwn(c) === 'other' ? SKIP
|
|
713
|
+
: congress(c) ? 'govLeader' : inState(c) || /\bstate\b/.test(`${c.n} ${c.rest}`) ? 'govSenior'
|
|
714
|
+
: houseOwn(c) || alone(c) ? 'govLeader' : SKIP)],
|
|
715
|
+
// Governors and statewide offices.
|
|
716
|
+
[String.raw`(?:(?:${STATES}) )?(?:lieutenant|lt\.?) governor(?: of (?:the (?:state|commonwealth) of )?(?:${STATES}))?`, (c) => (STATE_NAME.test(c.n) ? ownOnly('govSenior')(c) : where(c) !== 'other' && inState(c) && !c.after ? 'govSenior' : null)],
|
|
717
|
+
[String.raw`(?:(?:${STATES}) )?governor(?: of (?:the (?:state|commonwealth|territory) of )?(?:${STATES}))?|governor of the (?:federal reserve(?: system| board)?|board of governors of the federal reserve(?: system)?)|governor of the pueblo of [a-z ]+|governor of (?:the )?[a-z' -]+ nation`,
|
|
718
|
+
(c) => {
|
|
719
|
+
const ownRest = pieces(c.rest).filter((p) => !TAG.test(p));
|
|
720
|
+
const own = ownRest.length ? ownRest : [c.next, c.prevLeads ? c.prev : ''];
|
|
721
|
+
if (FED_RESERVE.test(c.n) || /federal reserve/.test(c.n) || own.some((p) => FED_RESERVE.test(p))) return 'govSenior';
|
|
722
|
+
if (/pueblo of| nation$/.test(c.n)) return 'govLeader';
|
|
723
|
+
if (STATE_NAME.test(c.n)) return whereOwn(c) === 'other' ? null : 'govLeader';
|
|
724
|
+
if (where(c) === 'other') return null;
|
|
725
|
+
if (own.some((p) => TRIBAL_GOV.test(p) && !TRIBAL_BUSINESS.test(p))) return 'govLeader';
|
|
726
|
+
return STATE_NAME.test(c.n) || own.some((p) => JUST_A_STATE.test(p) || STATE_ITSELF.test(p)) ? 'govLeader' : null;
|
|
727
|
+
}],
|
|
728
|
+
[String.raw`federal reserve governor|member of the board of governors of the federal reserve(?: system)?`, 'govSenior'],
|
|
729
|
+
// A statewide officer names the state in the title or as the organization:
|
|
730
|
+
// "State Treasurer", "Insurance Commissioner, State of Georgia". A
|
|
731
|
+
// department's own auditor or a company's controller isn't one.
|
|
732
|
+
[String.raw`${place}(?:state )?(?:treasurer|comptroller|controller|auditor|auditor general|superintendent of public instruction|commissioner of (?:agriculture|insurance|education|labor|revenue)|land commissioner|insurance commissioner|(?:agriculture|education|labor|revenue|railroad) commissioner|surgeon general)(?: (?:of|for) .*)?`,
|
|
733
|
+
(c) => (STATE_NAME.test(c.n) && whereSoft(c) !== 'other') || (/^state /.test(c.n) && nothingElse(c))
|
|
734
|
+
|| (!/^(?:controller|auditor)$/.test(c.n) && (pieces(c.rest).some((p) => STATE_ITSELF.test(p)) || (!c.rest && STATE_ITSELF.test(c.next)))) ? 'govSenior' : SKIP],
|
|
735
|
+
// Cities, counties and tribes.
|
|
736
|
+
[String.raw`mayor pro tem(?:pore)?(?:, .*)?`, needGov('govOfficial')],
|
|
737
|
+
[String.raw`(?:deputy|vice|assistant) mayor(?: for .*)?`, (c) => (where(c) === 'gov' || alone(c) || (/ for /.test(c.n) && whereSoft(c) !== 'other') || ((pieces(c.rest).some((p) => placeLike(p)) || CITY_STATE.test(c.next)) && whereSoft(c) !== 'other') ? 'govSenior' : null)],
|
|
738
|
+
[String.raw`mayor(?: of .*)?`, (c) => {
|
|
739
|
+
const w = where(c);
|
|
740
|
+
if (w === 'gov' || (w === 'none' && /\b(city|town|village|borough|township) of\b/.test(c.n))) return 'govLeader';
|
|
741
|
+
const of = c.n.match(/^mayor of (?:the )?(.+)$/);
|
|
742
|
+
// A city named beside the title: "Mayor | Oviedo, Florida", "Mayor, Winter Park".
|
|
743
|
+
const beside = pieces(c.rest)[0] || (!c.rest ? c.next : '');
|
|
744
|
+
if (w !== 'other' && !of && beside && !JOKE_PLACE.test(beside) && (pieces(c.rest).some((p) => placeLike(p)) || CITY_STATE.test(c.next)) && whereSoft(c) !== 'other') return 'govLeader';
|
|
745
|
+
return of && !c.prev && (CITY.test(of[1]) || CITY_STATE.test(of[1]) || MUNICIPALITY.test(of[1])) && !JOKE_PLACE.test(of[1]) && !isKnownCompany(of[1]) && whereSoft(c) !== 'other' ? 'govLeader' : null;
|
|
746
|
+
}],
|
|
747
|
+
[String.raw`(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3})?county (?:executive|mayor|judge executive)(?: of .*)?`, (c) => (nothingElse(c) || pieces(c.rest).some((p) => placeLike(p)) || (!c.rest && LOCAL_PLACE.test(c.next) && !c.after) ? 'govLeader' : null)],
|
|
748
|
+
[String.raw`(?:principal )?chief|(?:tribal )?(?:chairman|chairwoman|chairperson|president)`, (c) => (!c.at && (c.rest ? pieces(c.rest) : [c.next]).some((p) => TRIBAL_GOV.test(p) && !TRIBAL_BUSINESS.test(p) && !isOrg(p)) ? 'govLeader' : SKIP)],
|
|
749
|
+
// "City Manager" is also a gig app's or a mobility company's operations
|
|
750
|
+
// title (Uber, Lime): only with a government's name.
|
|
751
|
+
[String.raw`(?:(?:assistant|deputy|acting|interim) )?(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,2}(?:county|town|village|borough)|city) (?:administrator|manager)(?: .*)?`,
|
|
752
|
+
(c) => { const w = where(c); return w === 'gov' || (w === 'none' && ((pieces(c.rest).some((p) => placeLike(p)) && !c.next) || ((alone(c) || localPlace(c)) && !/\bcity (manager|administrator)\b/.test(c.n)))) ? 'govOfficial' : null; }],
|
|
753
|
+
// Cabinets: the federal one's, and a state's when it says so. With no
|
|
754
|
+
// government named, only the whole headline or "U.S." ("Secretary of
|
|
755
|
+
// Education | Homeschool Mom" is a joke).
|
|
756
|
+
[String.raw`${place}secretary of (?:state|defense|war|commerce|labor|education|energy|agriculture|transportation|veterans affairs|homeland security|health(?: and human services)?|housing and urban development|the (?:treasury|interior|army|navy|air force))(?: (?:of|for) .*)?`,
|
|
757
|
+
(c) => {
|
|
758
|
+
if (/secretary of state of /.test(c.n) && !STATE_NAME.test(c.n)) return null;
|
|
759
|
+
const w = c.us || STATE_NAME.test(c.n) ? whereSoft(c) : where(c);
|
|
760
|
+
if (w === 'other') return null;
|
|
761
|
+
if (STATE_NAME.test(c.n) || besides(c).some((p) => STATE_GOV.test(p) || JUST_A_STATE.test(p))) return 'govSenior';
|
|
762
|
+
return c.us || w === 'gov' || alone(c) ? 'govLeader' : null;
|
|
763
|
+
}],
|
|
764
|
+
[String.raw`deputy assistant secretary(?: (?:of|for) .*)?`, (c) => (CORP_OFFICER.test(c.part) ? null : (/ of /.test(c.n) ? (DEPT_OF.test(c.n.replace(/^.*? of /, '')) && whereOwn(c) !== 'other') : nothingElse(c)) ? 'govOfficial' : null)],
|
|
765
|
+
// An under, deputy or assistant secretary "of" a department, or "for" a
|
|
766
|
+
// policy area; not a company's ("Assistant Secretary of Northwind Foods").
|
|
767
|
+
[String.raw`(?:deputy under|deputy|under)[-\s]?secretary(?: (?:of|for) .*)?|undersecretary(?: .*)?|assistant secretary(?: (?:of|for) .*)?`,
|
|
768
|
+
(c) => {
|
|
769
|
+
if (CORP_OFFICER.test(c.part) || whereOwn(c) === 'other') return null;
|
|
770
|
+
const of = c.n.match(/\b(of|for) (.+)$/);
|
|
771
|
+
if (of && of[1] === 'of') return DEPT_OF.test(of[2]) ? 'govSenior' : null;
|
|
772
|
+
if (where(c) === 'other') return null;
|
|
773
|
+
if (of) return where(c) === 'gov' || alone(c) || c.us ? 'govSenior' : null;
|
|
774
|
+
return where(c) === 'gov' ? 'govSenior' : null;
|
|
775
|
+
}],
|
|
776
|
+
[String.raw`(?:deputy )?(?:national security advis[eo]r|director of national intelligence)|white house chief of staff|director of the office of management and budget`, (c) => (where(c) === 'other' ? null : /^deputy/.test(c.n) ? 'govSenior' : 'govLeader')],
|
|
777
|
+
// A university's or a company's president has assistants too.
|
|
778
|
+
[String.raw`(?:deputy )?assistant to the president(?: (?:for|and) .*)?`, (c) => (/of the united states/.test(c.n) || (where(c) === 'gov' && [c.rest, c.next, c.prevLeads ? c.prev : ''].some((p) => /\b(white house|executive office of the president|national security council|nsc)\b/.test(p))) ? (/^deputy/.test(c.n) ? 'govSenior' : 'govLeader') : null)],
|
|
779
|
+
// A beverage brand's trade representative sells to its trade.
|
|
780
|
+
[String.raw`(?:deputy )?(?:u\.?\s?s\.?\s|united states )?trade representative`, (c) => (where(c) !== 'other' && (c.us || /\b(ustr|office of the (u\.?\s?s\.?|united states) trade representative)\b/.test(c.text)) ? (/^deputy/.test(c.n) ? 'govSenior' : 'govLeader') : null)],
|
|
781
|
+
[String.raw`director`, (c) => (/^(?:the )?(?:office of management and budget|federal bureau of investigation|central intelligence agency|national security agency|u\.?\s?s\.? secret service|u\.?\s?s\.? marshals service|u\.?\s?s\.? census bureau|national institutes of health|centers for disease control and prevention|u\.?\s?s\.? mint|bureau of alcohol, tobacco, firearms and explosives)$/.test(c.rest || (!c.prev && !c.after ? c.next : '')) || /^(?:the )?(?:fbi|cia|nsa|atf|dea)$/.test(c.rest) ? 'govSenior' : SKIP)],
|
|
782
|
+
[String.raw`(?:fbi|cia|nsa|atf|dea|cdc|nih) director`, (c) => (where(c) === 'gov' || alone(c) ? 'govSenior' : null)],
|
|
783
|
+
[String.raw`postmaster general|librarian of congress|archivist of the united states|auditor general of the (?:army|navy|air force)`, notOther('govSenior')],
|
|
784
|
+
[String.raw`(?:u\.?\s?s\.?\s|united states )?special (?:presidential )?envoy(?: .*)?`, (c) => (/ to the (ceo|cto|coo|cfo|founders?|president|chair\w*|board)\b/.test(c.n) ? null : where(c) === 'gov' || (where(c) === 'none' && (c.us || /presidential/.test(c.n) || (alone(c) && / for /.test(c.n)))) ? 'govSenior' : null)],
|
|
785
|
+
[String.raw`charge d'affaires(?: .*)?`, (c) => (c.us || (where(c) === 'gov' && [c.rest, c.next].some((p) => FOREIGN.test(p))) ? 'govSenior' : null)],
|
|
786
|
+
[String.raw`(?:under|assistant)[-\s]secretary[-\s]general(?: .*)?`, notOther('govSenior')],
|
|
787
|
+
[String.raw`special agent in charge|assistant special agent in charge|chief patrol agent|undersheriff`, needGov('govOfficial')],
|
|
788
|
+
// A hospital's, a home-health chain's or a senior-living company's
|
|
789
|
+
// regional and associate administrators are theirs.
|
|
790
|
+
[String.raw`(?:regional|associate|deputy associate) administrator|assistant inspector general(?: .*)?|(?:principal )?deputy assistant attorney general(?: .*)?|associate deputy attorney general(?: .*)?`,
|
|
791
|
+
(c) => (where(c) === 'gov' || (/inspector general|attorney general/.test(c.n) && where(c) !== 'other') ? 'govOfficial' : null)],
|
|
792
|
+
[String.raw`director of the (?:fbi|cia|nsa|federal bureau of investigation|central intelligence agency|national security agency|secret service|u\.?\s?s\.? marshals service)|(?:u\.?\s?s\.?|united states) marshal(?: for .*)?|comptroller (?:general of the united states|of the currency)|treasurer of the united states`, ownOnly('govSenior')],
|
|
793
|
+
// Lawyers for the government.
|
|
794
|
+
[String.raw`(?:principal )?deputy associate attorney general(?: .*)?`, notOther('govOfficial')],
|
|
795
|
+
[String.raw`associate attorney general(?: .*)?`, notOther('govSenior')],
|
|
796
|
+
[String.raw`(?:principal )?deputy attorney general(?: .*)?|assistant attorney general(?: .*)?`, (c) => (/\b(of the united states|u\.?\s?s\.? department of justice|department of justice|doj)\b/.test(c.text) && !STATE_GOV.test(c.text) ? 'govSenior' : 'senior')],
|
|
797
|
+
[String.raw`(?:special deputy|special assistant|senior assistant) attorney general(?: .*)?`, 'senior'],
|
|
798
|
+
[String.raw`${place}attorney general(?: (?:of|for) .*)?`, (c) => ((/ (?:of|for) /.test(` ${c.n} `) || STATE_NAME.test(c.n) || c.us ? ownOnly : needGov)('govLeader')(c))],
|
|
799
|
+
[String.raw`(?:assistant|deputy|senior assistant|chief assistant|executive assistant) (?:u\.?\s?s\.?|united states|district|state|county|commonwealth(?:['’]s)?) attorney(?: .*)?`, 'senior'],
|
|
800
|
+
// "US Attorney" is also what a U.S.-qualified lawyer abroad writes: the
|
|
801
|
+
// U.S. Attorney names a district, or is the whole headline.
|
|
802
|
+
[String.raw`(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3}county |[a-z .'-]+ city )?(?:u\.?\s?s\.?|united states|district|state|state's|county|commonwealth(?:['’]s)?) attorney(?: (?:for|of) .*)?`,
|
|
803
|
+
(c) => {
|
|
804
|
+
if (/\battorney at law\b/.test(c.text)) return null;
|
|
805
|
+
const w = where(c);
|
|
806
|
+
if (w === 'other') return null;
|
|
807
|
+
if (w === 'gov' || pieces(c.rest).some((p) => /\b(district|county|circuit|judicial)\b/.test(p)) || /\bdistrict\b/.test(c.n)) return 'govSenior';
|
|
808
|
+
return w === 'other' || (c.us && !alone(c)) ? null : 'govSenior';
|
|
809
|
+
}],
|
|
810
|
+
[String.raw`deputy (?:inspector general|solicitor general|program executive officer|peo)(?: .*)?`, (c) => (SALES.test(c.n) ? null : notOther('govOfficial')(c))],
|
|
811
|
+
[String.raw`(?:solicitor|inspector|surgeon|adjutant) general(?: .*)?`, (c) => (/general (counsel|office|staff|investigator|auditor|attorney|agent)/.test(c.n) ? null
|
|
812
|
+
: / of /.test(c.n) ? (new RegExp(String.raw` of (?:the )?(?:united states|${STATES})$`).test(c.n) ? soft('govSenior')(c) : null)
|
|
813
|
+
: nothingElse(c) || (!c.rest && JUST_A_STATE.test(c.next) && !c.after) ? 'govSenior' : null)],
|
|
814
|
+
// Defense and civil-service executives. "PEO" is also a professional
|
|
815
|
+
// employer organization, the HR industry's word ("PEO Account Manager").
|
|
816
|
+
[String.raw`program executive officer(?: .*)?`, (c) => (c.service || /\bpeo\b|program executive office/.test(c.rest) || where(c) === 'gov' ? 'govSenior' : null)],
|
|
817
|
+
[String.raw`(?:member of the )?senior executive service(?: member)?|(?:tier (?:[1-3]|i{1,3}) )?ses(?: tier [1-3])?(?: member)?`, (c) => (/senior executive service/.test(c.n) || c.service || whereOwn(c) === 'gov' || (/tier|member/.test(c.n) && where(c) === 'gov') ? 'govSenior' : null)],
|
|
818
|
+
// Diplomats and international bodies. A brand's ambassador is theirs: an
|
|
819
|
+
// ambassador is a diplomat "to" a country, at an embassy or a mission, or
|
|
820
|
+
// retired as one ("Ambassador (Ret.)"; "Former Ambassador | Lululemon" isn't).
|
|
821
|
+
[String.raw`secretary[-\s]general(?: of (?:the )?.*)?`, (c) => ([c.n.replace(/^secretary[-\s]general(?: of (?:the )?)?/, ''), ...pieces(c.rest), ...(c.rest ? [] : [c.next])].some((p) => INTERNATIONAL_BODY.test(p)) ? 'govLeader' : null)],
|
|
822
|
+
[String.raw`(?:u\.?\s?s\.?\s|united states )?ambassador(?: of the united states| extraordinary and plenipotentiary)?(?: to (?:the )?(?:republic of |kingdom of |state of )?(?:${COUNTRIES}|${INTERNATIONAL})(?:,? (?:and|&) (?:the )?(?:${COUNTRIES}))*)?|ambassador-at-large(?: .*)?`,
|
|
823
|
+
(c) => (whereSoft(c) === 'other' ? null : [c.rest, c.next, c.prevLeads ? c.prev : ''].some((p) => FOREIGN.test(p)) || INTERNATIONAL_IN.test(c.rest)
|
|
824
|
+
|| ((/at-large| to /.test(` ${c.n} `) || c.retTag) && (c.us || alone(c))) ? 'govSenior' : null)],
|
|
825
|
+
[String.raw`ambassador of (?:the )?(?:${COUNTRIES}) to (?:the )?(?:united states|${COUNTRIES}|${INTERNATIONAL})`, 'govSenior'],
|
|
826
|
+
[String.raw`(?:deputy |alternate )?(?:permanent )?representative (?:of .* )?to the (?:${INTERNATIONAL})(?: .*)?|(?:deputy |alternate )?permanent representative(?: .*)?`, (c) => (INTERNATIONAL_IN.test(c.text) && where(c) !== 'other' ? 'govSenior' : null)],
|
|
827
|
+
[String.raw`consul general(?: .*)?`, (c) => (c.us || new RegExp(String.raw` of (?:the )?(?:${COUNTRIES}|united states)\b`).test(c.n) || [c.rest, c.next].some((p) => FOREIGN.test(p)) || alone(c) ? 'govOfficial' : null)],
|
|
828
|
+
// Courts, not a fair's, a rodeo's or a campus's: the court is named, or the
|
|
829
|
+
// title says which kind it is ("U.S. District Judge").
|
|
830
|
+
[String.raw`chief justice(?: .*)?`, (c) => (notCourt(c) ? null : where(c) === 'gov' || (whereOwn(c) !== 'other' && (STATE_NAME.test(c.n) || /united states|supreme court/.test(c.n))) ? 'govLeader' : null)],
|
|
831
|
+
[String.raw`(?:(?:${STATES}|state|u\.?\s?s\.?) )?supreme court justice`, ownOnly('judge')],
|
|
832
|
+
[String.raw`(?:associate |presiding )?justice(?: of .*)?`, (c) => (!notCourt(c) && where(c) === 'gov' ? 'judge' : !notCourt(c) && where(c) !== 'other' && /\b(supreme|appeals|appellate|superior) (judicial )?court|court of (appeals|criminal appeals)\b/.test(`${c.n} ${c.rest}`) ? 'judge' : null)],
|
|
833
|
+
[String.raw`(?:(?:${STATES}) |(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3}county )?(?:(?:chief|presiding|senior|associate|federal|state|u\.?\s?s\.?|united states|district|circuit|county|superior|family|probate|municipal|juvenile|criminal|civil|city|justice|appellate|appeals|bankruptcy|magistrate|administrative law|immigration|tax|trial|tribal|court) )*judge(?: of .*)?`,
|
|
834
|
+
(c) => {
|
|
835
|
+
if (notCourt(c)) return null;
|
|
836
|
+
const named = /\b(federal|district|circuit|superior|probate|municipal|juvenile|criminal|appellate|appeals|bankruptcy|magistrate|administrative law|immigration|court|tribal)\b/.test(c.n) || c.us;
|
|
837
|
+
const w = named ? whereSoft(c) : where(c);
|
|
838
|
+
if (w !== 'none') return w === 'gov' ? 'judge' : null;
|
|
839
|
+
return /\b(federal|district|circuit|superior|probate|municipal|juvenile|criminal|appellate|appeals|bankruptcy|magistrate|administrative law|immigration|court|tribal)\b/.test(c.n) || c.us || (c.retired && /^(?:hon\.? )?judge$/.test(c.n)) ? 'judge' : null;
|
|
840
|
+
}],
|
|
841
|
+
// Commissions.
|
|
842
|
+
[String.raw`(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3}(?:county|city|town|village|borough|parish) )?(?:(?:planning|zoning|parks?(?: and recreation)?|library|historic preservation) )*(?:county |city |town )?commissioner(?: .*)?`,
|
|
843
|
+
(c) => (/\b(county|city|town|village|borough|parish)\b/.test(c.n) ? soft('govOfficial')(c) : /\b(planning|zoning|parks?|library|historic)\b/.test(c.n) ? notOther('govOfficial')(c) : SKIP)],
|
|
844
|
+
[String.raw`(?:police|fire) (?:commissioner|chief)|chief of (?:police|the fire department)|superintendent of police`, needGov('govSenior')],
|
|
845
|
+
[String.raw`chief`, (c) => (where(c) === 'gov' && /\b(police|fire|sheriff)/.test(c.rest || c.next) ? 'govSenior' : SKIP)],
|
|
846
|
+
[String.raw`(?:fcc|sec|ftc|fec|nrc|ferc|cftc|eeoc|nlrb) (?:commissioner|chair(?:man|woman)?)`, notOther('govSenior')],
|
|
847
|
+
// An agency's initials are also its programs' ("SBA Administrator" at a
|
|
848
|
+
// bank lends SBA loans): its head names the agency, or is the whole headline.
|
|
849
|
+
[String.raw`(?:fda|irs|cbp|ice|fema|epa|ssa|uspto|nasa|faa|tsa|dea|atf|gsa|sba) (?:commissioner|administrator|chair(?:man|woman)?)`, (c) => (where(c) === 'gov' || alone(c) ? 'govSenior' : null)],
|
|
850
|
+
[String.raw`(?:deputy|assistant|associate) commissioner(?: .*)?`, (c) => (where(c) === 'gov' ? 'govOfficial' : null)],
|
|
851
|
+
[String.raw`commissioner(?: of .*)?`, (c) => {
|
|
852
|
+
const w = where(c);
|
|
853
|
+
if (w === 'other') return null;
|
|
854
|
+
const own = [c.n, ...pieces(c.rest), ...(c.rest ? [] : [c.next]), c.prevLeads ? c.prev : ''];
|
|
855
|
+
if (own.some((p) => STATE_BODY.test(p))) return 'govSenior';
|
|
856
|
+
if (own.some((p) => LOCAL_GOV.test(p))) return 'govOfficial';
|
|
857
|
+
if ((/^commissioner of (the )?(health|education|agriculture|insurance|labor|revenue|police|corrections|public safety|human services|social services|transportation|environmental protection|internal revenue|food and drugs|u\.?\s?s\.? customs|immigration|social security)$/.test(c.n) && nothingElse(c)) || w === 'gov') return 'govSenior';
|
|
858
|
+
return localPlace(c) ? 'govOfficial' : null;
|
|
859
|
+
}],
|
|
860
|
+
// An agency's head: the agency named in full, as the title's or the part's
|
|
861
|
+
// organization ("Administrator, U.S. Small Business Administration").
|
|
862
|
+
[String.raw`(?:deputy )?administrator(?: of .*)?`, (c) => {
|
|
863
|
+
const of = c.n.match(/ of (.+)$/);
|
|
864
|
+
const named = of ? AGENCY_EXACT.test(of[1]) || /^(?:the )?(?:nasa|epa|fema|usaid|sba|gsa|faa|tsa|nhtsa|noaa)$/.test(of[1]) : (c.rest ? pieces(c.rest) : [c.next]).some((p) => AGENCY_EXACT.test(p));
|
|
865
|
+
return named ? (/^deputy/.test(c.n) ? 'govOfficial' : 'govSenior') : null;
|
|
866
|
+
}],
|
|
867
|
+
[String.raw`chair(?:man|woman|person)?(?: of .*)?`, (c) => (!c.at && [c.n.replace(/^chair(?:man|woman|person)? of (?:the )?/, ''), ...pieces(c.rest), ...(c.rest ? [] : [c.next])].some((p) => STATE_BODY.test(p)) ? 'govSenior' : SKIP)],
|
|
868
|
+
// Local officials: a council's own name, or a city's, a county's or a district's.
|
|
869
|
+
[String.raw`(?:(?:${STATES}|[a-z.'-]+ (?:city|county)|city|town|village|borough|county|metro|district \d+|ward \d+|at-large|deputy|vice|chair|former) )*(?:council ?(?:member|man|woman|person)|councilmember|councill?or|alder(?:man|woman|person)|selectman|selectwoman|select board member)(?: .*)?`,
|
|
870
|
+
(c) => (/\b(forbes|advisory|parent|students?|youth|business|young entrepreneur|yec|ypo|eo|technology|tech|advisors?|chamber|young professionals|leadership|development|nonprofit)\b/.test(c.part) ? null
|
|
871
|
+
: where(c) === 'gov' || (/\b(city|town|village|borough|county|district|ward|metro|at-large|parish)\b/.test(c.n) && whereSoft(c) !== 'other') || (where(c) === 'none' && pieces(c.rest).some((p) => placeLike(p))) ? 'govOfficial' : null)],
|
|
872
|
+
[String.raw`(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3})?county supervisor(?:, .*)?|member of the (?:[a-z.']+ ){0,2}board of supervisors`, (c) => (nothingElse(c) || pieces(c.rest).some((p) => placeLike(p)) ? 'govOfficial' : null)],
|
|
873
|
+
[String.raw`(?:(?:(?!(?:assistant|aide|driver|secretary|speechwriter|son|daughter|wife|husband|married|future|aspiring|campaign|former|endorsed|staff|intern|clerk|to|for|of|by|the|a|an|with)\b)[a-z.'-]+ ){0,3}(?:county|parish) )?sheriff(?: of .*)?`, (c) => (where(c) === 'gov' || (/\b(county|parish)\b/.test(c.n) && whereSoft(c) !== 'other') || (where(c) === 'none' && !c.after && [...pieces(c.rest), c.rest ? '' : c.next].some((p) => LOCAL_PLACE.test(p))) ? 'govOfficial' : null)],
|
|
874
|
+
[String.raw`supervisor`, (c) => (pieces(c.rest).some((p) => /board of supervisors$/.test(p) && isGovOrg(p)) ? 'govOfficial' : SKIP)],
|
|
875
|
+
// A private school's board is its trustees'.
|
|
876
|
+
[String.raw`school board (?:member|chair(?:man|woman|person)?|trustee|president|vice chair)(?:, .*)?|member of the (?:[a-z.']+ ){0,3}school board`, needGov('govOfficial')],
|
|
877
|
+
[String.raw`prime minister(?: of (?:the )?(?:${COUNTRIES}))?|member of parliament(?: for .*)?`, (c) => (/ of /.test(c.n) ? ownOnly('govLeader')(c) : needGov('govLeader')(c))],
|
|
878
|
+
].map(([src, key]) => [new RegExp(`^${LEAD}(?:${src})$`), key]);
|
|
879
|
+
const OFFICE_TABLES = { corpOfficer: CORP_OFFICER, notCourt: NOT_COURT, fedBody: FED_BODY, dept: DEPT, mock: MOCK, localGov: LOCAL_GOV,
|
|
880
|
+
tribes: TRIBES, tribalGov: TRIBAL_GOV, tribalBusiness: TRIBAL_BUSINESS, legislature: LEGIS, stateItself: STATE_ITSELF, agency: AGENCY,
|
|
881
|
+
agencyExact: AGENCY_EXACT, notGov: NOT_GOV, place: PLACE, notAnOrg: NOT_AN_ORG, judicialArea: JUDICIAL_AREA, orgWord: ORG_WORD,
|
|
882
|
+
jokePlace: JOKE_PLACE, municipality: MUNICIPALITY, party: PARTY, nations: NATIONS, sales: SALES, fedReserve: FED_RESERVE, stateBody: STATE_BODY, milRole: MIL_ROLE, serviceRetired: SERVICE_RETIRED,
|
|
883
|
+
// How the tables are applied, too: a change to where a title is held rescores like a table's.
|
|
884
|
+
code: [pieces, isGovOrg, isOrg, anotherTitle, whereOwn, where, alone, besides, houseOf, congress, inState, notOther, notCourt, officeTitle, militaryTitle, commandLevel, readPart, readRoles].map(String),
|
|
885
|
+
qualifiedService: QUALIFIED_SVC, restIsService: REST_IS_SERVICE, unitLevels: UNIT_LEVELS, states: STATES, countries: COUNTRIES, international: INTERNATIONAL, stateGov: STATE_GOV, govOrg: GOV_ORG,
|
|
886
|
+
civic: CIVIC, lead: LEAD, service: SVC, serviceIn: SERVICE_IN, navy: NAVY_IN, serviceOnly: SERVICE_ONLY, milUnit: MIL_UNIT,
|
|
887
|
+
notMilitary: NOT_MILITARY, rankShort: RANK_SHORT, ranks: RANKS,
|
|
888
|
+
gov: GOV.map(([re, key]) => [re, typeof key === 'function' ? String(key) : key]) };
|
|
889
|
+
|
|
890
|
+
const lowerPart = (text) => String(text || '').normalize('NFKD').replace(/[\u0300-\u036f]/g, '').replace(/[\p{Extended_Pictographic}\u{1F1E6}-\u{1F1FF}\uFE0F\u200D]/gu, ' ').toLowerCase()
|
|
891
|
+
.replace(/\(\s*([a-z.]+),\s*(?:ret(?:\.|ired|d\.?)?|r)\s*\)/g, ' $1 ').replace(/\(\s*(ret(\.|ired|d\.?)?|r)\s*\)/g, ' ')
|
|
892
|
+
.replace(/[’‘]/g, "'").replace(/\bambassador at large\b/g, 'ambassador-at-large').replace(/\s+/g, ' ').trim();
|
|
893
|
+
// What an official is "for": a portfolio, not a company ("Under Secretary for Science and Technology").
|
|
894
|
+
const PORTFOLIO = /\b(technolog\w*|health|financial|science|security|policy|affairs|development|research|management|programs?|services?|education|energy|environment|resources|operations|legislation|planning|preparedness|intelligence|acquisition|readiness|innovation|infrastructure)\b/;
|
|
895
|
+
// Officials' titles written short, at the start of a title: "Sen., Florida Senate", "Gov. of Ohio".
|
|
896
|
+
const GOV_SHORT = [[/^state sen\.?(?=\s|,|$)/, 'state senator'], [/^sen\.?(?=\s|,|$)/, 'senator'], [/^lt\.? gov\.?(?=\s|,|$)/, 'lieutenant governor'], [/^gov\.?(?=\s|,|$)/, 'governor'],
|
|
897
|
+
[/^amb\.?(?=\s|,|$)/, 'ambassador'], [/^del\.?(?=\s|,|$)/, 'delegate'], [/^rep\.(?=\s|,|$)/, 'representative']];
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* A government or military title written where a title goes, or null: the
|
|
901
|
+
* part's text up to its first comma, "at", "@" or bracket is the title; the
|
|
902
|
+
* rest of the part, and the parts on either side, only say where it is held.
|
|
903
|
+
* A rank that says it's retired comes back with `retired`.
|
|
904
|
+
*/
|
|
905
|
+
// Staff words: a part like this before an official's title names the official as their principal.
|
|
906
|
+
// An official's own title in the part before ("Former Governor | U.S. Senator") isn't a staffer's.
|
|
907
|
+
const officeLike = (p) => /\b(senator|congress\w*|representative|governor|mayor|secretary of|judge|justice|commissioner|general|admiral|colonel|captain|commander)\b/.test(p) && !/\b(aide|assistant|driver|clerk|staff|intern|secretary to|press secretary)\b/.test(p);
|
|
908
|
+
const STAFFER = /\b(aide|law clerk|assistant|staff(?:er)?|scheduler|clerk|paralegal|advocate|driver|intern|fellow|chief of staff|press secretary|secretary|speechwriter|director|counsel|analyst|investigator|deputy|dispatcher|detective|generalist|liaison|coordinator|caseworker|officer|manager|specialist|writer|photographer|agent)\b/;
|
|
909
|
+
function officeTitle(part, prev = '', next = '', others = [], former = false, after = '') {
|
|
910
|
+
const t = lowerPart(part);
|
|
911
|
+
const cut = t.search(/,|\s(?:at|@)\s|\s@|\(/);
|
|
912
|
+
const title = (cut < 0 ? t : t.slice(0, cut)).trim().replace(/[.:;]+$/, '');
|
|
913
|
+
const rest = cut < 0 ? '' : t.slice(cut).replace(/^[,\s]+/, '');
|
|
914
|
+
const side = { prev: lowerPart(prev), next: lowerPart(next) };
|
|
915
|
+
// The official or flag officer a staffer works for: "Flag Aide | Rear Admiral, U.S. Navy".
|
|
916
|
+
const principal = !!side.prev && STAFFER.test(side.prev) && !FORMER.test(prev) && !officeLike(side.prev);
|
|
917
|
+
const mil = militaryTitle(part, t, title, rest, side.prev, side.next, others.map(lowerPart), RETIRED.test(next) || RETIRED_ONLY.test(next), lowerPart(after));
|
|
918
|
+
if (mil && principal && (mil.key === 'seniorGeneral' || mil.key === 'general')) return null;
|
|
919
|
+
if (mil) return { ...LEVELS[mil.key], ...(mil.retired && { retired: true }) };
|
|
920
|
+
// Government: never a club's, a church's, a school's, a fraternity's, a
|
|
921
|
+
// mock government's or a joke's, and never at an organization that isn't a
|
|
922
|
+
// government's. A club or a school in another part is where() 's to see.
|
|
923
|
+
if (principal || CIVIC.test(t.replace(MUNICIPALITY_OF, ' ')) || STUDENT_ORG.test(t) || others.map(lowerPart).some((o) => MOCK.test(o) && !MOCK_PAST.test(o)) || /^(candidate|nominee|running)\b/.test(title)) return null;
|
|
924
|
+
const at = /^(?:at|@)\s/.test(rest);
|
|
925
|
+
if (at && !pieces(rest).some(isGovOrg)) return null;
|
|
926
|
+
let n = title.replace(new RegExp(`^${LEAD}`), '');
|
|
927
|
+
for (const [re, full] of GOV_SHORT) if (re.test(n)) { n = n.replace(re, full); break; }
|
|
928
|
+
// An organization written inside the title is its own: "State Representative for Northwind Insurance".
|
|
929
|
+
const inTitle = n.match(/ (?:for|of|to|at) (?:the )?(.+)$/);
|
|
930
|
+
if (inTitle && (isKnownCompany(inTitle[1]) || (isOrg(inTitle[1]) && !PORTFOLIO.test(inTitle[1]))) && !isGovOrg(inTitle[1]) && !DEPT_OF.test(inTitle[1]) && !MUNICIPALITY.test(inTitle[1])) return null;
|
|
931
|
+
const c = { n, part: t, rest, at, prev: side.prev, prevLeads: !!prev && others[0] === prev, next: side.next, after: lowerPart(after),
|
|
932
|
+
text: `${side.prev} ${t} ${side.next}`, service: SERVICE_IN.test(t), retired: former || RETIRED.test(part),
|
|
933
|
+
retTag: RETIRED.test(part), us: /\b(?:u\.?\s?s\.?|united states)\s/.test(title) };
|
|
934
|
+
for (const [re, key] of GOV) {
|
|
935
|
+
if (!re.test(title) && !re.test(n)) continue; // n: a short form spelled out
|
|
936
|
+
const k = typeof key === 'function' ? key(c) : key;
|
|
937
|
+
if (k === SKIP) continue;
|
|
938
|
+
return k ? LEVELS[k] : null;
|
|
939
|
+
}
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
|
|
220
943
|
// An audience of their own, in one part of a headline: "2.5M+ followers",
|
|
221
944
|
// "2,500,000 followers", "1.2M monthly listeners", "500K subscribers", "3M+ on
|
|
222
945
|
// TikTok". Not views, users or impressions, which are a campaign's or a
|
|
@@ -286,10 +1009,33 @@ function heldAfterComma(part) {
|
|
|
286
1009
|
*/
|
|
287
1010
|
export function readRoles(headline = '', role = '') {
|
|
288
1011
|
const out = [];
|
|
289
|
-
|
|
290
|
-
|
|
1012
|
+
const parts = headlineParts(headline || role);
|
|
1013
|
+
let last = null; // the last role a title gave: { role, i, rank }
|
|
1014
|
+
let retiredNext = false; // a lone "Retired" before any role: the next one's
|
|
1015
|
+
for (const [i, raw] of parts.entries()) {
|
|
1016
|
+
// "Colonel, U.S. Army | Retired": the role just before, or a rank's with
|
|
1017
|
+
// its service between ("Colonel | U.S. Army | Retired"); not a civilian
|
|
1018
|
+
// job further back ("Senior Manager at Deloitte | Navy Veteran | Retired").
|
|
1019
|
+
if (RETIRED_ONLY.test(raw)) {
|
|
1020
|
+
const afterwards = /\b(board|volunteer|advis[eo]r|mentor|trustee|chair|docent|member|consultant|adjunct|part-time|substitute|fractional|golfer|investor|coach|tutor)\b/i.test(last?.role.part || '');
|
|
1021
|
+
if (!last) { retiredNext = true; continue; }
|
|
1022
|
+
if (last && !afterwards && (last.i === i - 1 || (last.i === i - 2 && last.rank && SERVICE_ONLY.test(lowerPart(parts[i - 1]))))) last.role.former = true;
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
// "Paralegal – General Counsel": the office they work in, split off by a dash.
|
|
1026
|
+
if (/^(?:the )?(?:general|chief) counsel(?:,.*)?$/i.test(raw.trim()) && i > 0 && !(readPart(parts[i - 1])?.level >= LEVELS.vp.level)) continue;
|
|
1027
|
+
const retiredTag = RETIRED.test(raw);
|
|
291
1028
|
const part = raw.replace(FORMER, '').replace(/^[@\s.:]+/, '');
|
|
292
|
-
|
|
1029
|
+
const office = officeTitle(part, parts[i - 1] ?? '', parts[i + 1] ?? '', parts, FORMER.test(raw) || retiredTag, parts[i + 2] ?? '');
|
|
1030
|
+
// A civilian job is retired when the tag is its own: "Teacher (Retired)",
|
|
1031
|
+
// "Nurse, Retired". Not a service's after it ("Program Manager at Leidos,
|
|
1032
|
+
// U.S. Army (Ret.)"), nor a second role's ("Realtor at Compass,
|
|
1033
|
+
// Firefighter (Ret.)", "Teacher (Retired), Realtor at Compass").
|
|
1034
|
+
const roles = part.replace(/\([^)]*\)/g, ' ').split(',').filter((p) => p.trim() && !RETIRED_ONLY.test(p));
|
|
1035
|
+
const former = FORMER.test(raw) || (retiredTag && (!!office || (!SERVICE_RETIRED.test(part.toLowerCase()) && roles.length <= 1)));
|
|
1036
|
+
let title = office || readPart(part);
|
|
1037
|
+
const retired = !!title?.retired;
|
|
1038
|
+
if (retired) { const { retired: _, ...rest } = title; title = rest; }
|
|
293
1039
|
let company = companyIn(part) || (out.length === 0 && isKnownCompany(part) ? cleanCompany(part) : null);
|
|
294
1040
|
let inferred = false;
|
|
295
1041
|
if (!company && title) {
|
|
@@ -297,7 +1043,11 @@ export function readRoles(headline = '', role = '') {
|
|
|
297
1043
|
inferred = !!company;
|
|
298
1044
|
}
|
|
299
1045
|
if (!title && company) title = { ...LEVELS.ic, label: 'Works there' };
|
|
300
|
-
if (title)
|
|
1046
|
+
if (title) {
|
|
1047
|
+
out.push({ title, company, former: former || retired || retiredNext, part, ...(inferred && { inferred }) });
|
|
1048
|
+
retiredNext = false;
|
|
1049
|
+
last = { role: out[out.length - 1], i, rank: RANKED.has(title.key) };
|
|
1050
|
+
}
|
|
301
1051
|
else if (!former) {
|
|
302
1052
|
const before = [...out].reverse().find((r) => !r.audience);
|
|
303
1053
|
const at = before && !before.former && !before.company ? heldAt(part, before.part) : null;
|
|
@@ -311,7 +1061,7 @@ export function readRoles(headline = '', role = '') {
|
|
|
311
1061
|
|
|
312
1062
|
/** Is this person a student right now? Then no other title makes them powerful yet. */
|
|
313
1063
|
export function isStudent(headline = '') {
|
|
314
|
-
return headlineParts(headline).some((p) => !FORMER.test(p) && (studies(p) || INTERN.test(` ${p.toLowerCase()} `)));
|
|
1064
|
+
return headlineParts(headline).some((p) => !FORMER.test(p) && !SERVICE_SCHOOL.test(p) && (studies(p) || INTERN.test(` ${p.toLowerCase()} `)));
|
|
315
1065
|
}
|
|
316
1066
|
|
|
317
1067
|
/** The person's first current title (a quick read; scoring weighs every role), an audience only when it's all they state. */
|
|
@@ -337,7 +1087,7 @@ export function readTitle(headline = '', role = '') {
|
|
|
337
1087
|
// Nothing on the list is below 7. Regional picks, picks from one person's
|
|
338
1088
|
// career or network, and small startups are not on it: they are estimated
|
|
339
1089
|
// from the network like any other company (4, or 5–6 when many of the
|
|
340
|
-
// network's people work there), and anyone can score them on
|
|
1090
|
+
// network's people work there), and anyone can score them on the Scores tab,
|
|
341
1091
|
// where a score set always wins. When in doubt, a company stays off: the
|
|
342
1092
|
// estimate is the neutral default.
|
|
343
1093
|
//
|
|
@@ -1065,10 +1815,10 @@ export const COMPANY_DATA = [
|
|
|
1065
1815
|
['U.S. Department of Veterans Affairs', 8, exactly('u.s. department of veterans affairs', 'us department of veterans affairs', 'united states department of veterans affairs', 'department of veterans affairs', 'veterans affairs', 'u.s. veterans affairs', 'us veterans affairs', 'u.s. va', 'us va', 'veterans health administration', 'veterans benefits administration', 'national cemetery administration', 'va medical center', 'veterans affairs medical center', 'va health care system', 'va healthcare system', 'va hospital'), 'health', 'U.S. executive department (va.gov, OPM data), about 446,500 staff (July 2026)'],
|
|
1066
1816
|
['U.S. Department of Homeland Security', 8, exactly('u.s. department of homeland security', 'us department of homeland security', 'united states department of homeland security', 'department of homeland security', 'u.s. dhs', 'us dhs', 'federal law enforcement training centers', 'federal law enforcement training center', 'fletc', 'federal protective service'), 'defense', 'U.S. executive department (dhs.gov, OPM data), about 225,500 civilian staff (July 2026)'],
|
|
1067
1817
|
['U.S. Army', 8, exactly('u.s. army', 'us army', 'united states army', 'u.s. army reserve', 'us army reserve', 'united states army reserve', 'department of the army', 'u.s. department of the army', 'us department of the army', 'united states department of the army', 'hqda'), 'defense', 'U.S. military branch (army.mil, DMDC and OPM data), about 458,100 active duty and 188,300 civilians (2026)'],
|
|
1068
|
-
['U.S. Navy', 8, exactly('u.s. navy', 'us navy', 'united states navy', 'u.s. navy reserve', 'us navy reserve', 'united states navy reserve', 'u.s. naval reserve', 'us naval reserve', 'united states naval reserve', 'department of the navy', 'u.s. department of the navy', 'us department of the navy', 'united states department of the navy', 'office of naval research'), 'defense', 'U.S. military branch (navy.mil, DMDC and OPM data), about 347,700 active duty and 184,300 civilians (2026)'],
|
|
1818
|
+
['U.S. Navy', 8, exactly('u.s. navy', 'us navy', 'united states navy', 'u.s. navy reserve', 'us navy reserve', 'united states navy reserve', 'u.s. naval reserve', 'us naval reserve', 'united states naval reserve', 'department of the navy', 'u.s. department of the navy', 'us department of the navy', 'united states department of the navy', 'office of naval research', 'usn'), 'defense', 'U.S. military branch (navy.mil, DMDC and OPM data), about 347,700 active duty and 184,300 civilians (2026)'],
|
|
1069
1819
|
['U.S. Air Force', 8, exactly('u.s. air force', 'us air force', 'united states air force', 'usaf', 'u.s. air force reserve', 'us air force reserve', 'united states air force reserve', 'air force reserve command', 'department of the air force', 'u.s. department of the air force', 'us department of the air force'), 'defense', 'U.S. military branch (af.mil, DMDC and OPM data), about 320,000 active duty and 144,000 civilians (2026)'],
|
|
1070
1820
|
['U.S. Marine Corps', 8, exactly('u.s. marine corps', 'us marine corps', 'united states marine corps', 'usmc', 'marine corps', 'u.s. marine corps reserve', 'us marine corps reserve', 'united states marine corps reserve', 'usmcr', 'marine forces reserve', 'u.s. marines', 'us marines', 'united states marines'), 'defense', 'U.S. military branch (marines.mil, DMDC and OPM data), about 169,900 active duty and 16,200 civilians (2026)'],
|
|
1071
|
-
['U.S. Space Force', 8, exactly('u.s. space force', 'us space force', 'united states space force', 'space systems command', 'space operations command', 'space training and readiness command', 'space development agency'), 'defense', 'U.S. military branch (spaceforce.mil, DMDC and OPM data), about 10,200 Guardians and 7,100 civilians (2026)'],
|
|
1821
|
+
['U.S. Space Force', 8, exactly('u.s. space force', 'us space force', 'united states space force', 'space systems command', 'space operations command', 'space training and readiness command', 'space development agency', 'ussf'), 'defense', 'U.S. military branch (spaceforce.mil, DMDC and OPM data), about 10,200 Guardians and 7,100 civilians (2026)'],
|
|
1072
1822
|
['U.S. Coast Guard', 8, exactly('u.s. coast guard', 'us coast guard', 'united states coast guard', 'uscg', 'u.s. coast guard reserve', 'us coast guard reserve', 'united states coast guard reserve'), 'defense', 'U.S. military branch (uscg.mil, DMDC and OPM data), about 43,600 active duty and 8,700 civilians (2026)'],
|
|
1073
1823
|
['National Guard', 8, exactly('national guard', 'u.s. national guard', 'us national guard', 'united states national guard', 'army national guard', 'air national guard', 'national guard bureau', 'arng', 'alabama national guard', 'alabama army national guard', 'alabama air national guard', 'alaska national guard', 'alaska army national guard', 'alaska air national guard', 'arizona national guard', 'arizona army national guard', 'arizona air national guard', 'arkansas national guard', 'arkansas army national guard', 'arkansas air national guard', 'california national guard', 'california army national guard', 'california air national guard', 'colorado national guard', 'colorado army national guard', 'colorado air national guard', 'connecticut national guard', 'connecticut army national guard', 'connecticut air national guard', 'delaware national guard', 'delaware army national guard', 'delaware air national guard', 'florida national guard', 'florida army national guard', 'florida air national guard', 'georgia national guard', 'georgia army national guard', 'georgia air national guard', 'hawaii national guard', 'hawaii army national guard', 'hawaii air national guard', 'idaho national guard', 'idaho army national guard', 'idaho air national guard', 'illinois national guard', 'illinois army national guard', 'illinois air national guard', 'indiana national guard', 'indiana army national guard', 'indiana air national guard', 'iowa national guard', 'iowa army national guard', 'iowa air national guard', 'kansas national guard', 'kansas army national guard', 'kansas air national guard', 'kentucky national guard', 'kentucky army national guard', 'kentucky air national guard', 'louisiana national guard', 'louisiana army national guard', 'louisiana air national guard', 'maine national guard', 'maine army national guard', 'maine air national guard', 'maryland national guard', 'maryland army national guard', 'maryland air national guard', 'massachusetts national guard', 'massachusetts army national guard', 'massachusetts air national guard', 'michigan national guard', 'michigan army national guard', 'michigan air national guard', 'minnesota national guard', 'minnesota army national guard', 'minnesota air national guard', 'mississippi national guard', 'mississippi army national guard', 'mississippi air national guard', 'missouri national guard', 'missouri army national guard', 'missouri air national guard', 'montana national guard', 'montana army national guard', 'montana air national guard', 'nebraska national guard', 'nebraska army national guard', 'nebraska air national guard', 'nevada national guard', 'nevada army national guard', 'nevada air national guard', 'new hampshire national guard', 'new hampshire army national guard', 'new hampshire air national guard', 'new jersey national guard', 'new jersey army national guard', 'new jersey air national guard', 'new mexico national guard', 'new mexico army national guard', 'new mexico air national guard', 'new york national guard', 'new york army national guard', 'new york air national guard', 'north carolina national guard', 'north carolina army national guard', 'north carolina air national guard', 'north dakota national guard', 'north dakota army national guard', 'north dakota air national guard', 'ohio national guard', 'ohio army national guard', 'ohio air national guard', 'oklahoma national guard', 'oklahoma army national guard', 'oklahoma air national guard', 'oregon national guard', 'oregon army national guard', 'oregon air national guard', 'pennsylvania national guard', 'pennsylvania army national guard', 'pennsylvania air national guard', 'rhode island national guard', 'rhode island army national guard', 'rhode island air national guard', 'south carolina national guard', 'south carolina army national guard', 'south carolina air national guard', 'south dakota national guard', 'south dakota army national guard', 'south dakota air national guard', 'tennessee national guard', 'tennessee army national guard', 'tennessee air national guard', 'texas national guard', 'texas army national guard', 'texas air national guard', 'utah national guard', 'utah army national guard', 'utah air national guard', 'vermont national guard', 'vermont army national guard', 'vermont air national guard', 'virginia national guard', 'virginia army national guard', 'virginia air national guard', 'washington national guard', 'washington army national guard', 'washington air national guard', 'west virginia national guard', 'west virginia army national guard', 'west virginia air national guard', 'wisconsin national guard', 'wisconsin army national guard', 'wisconsin air national guard', 'wyoming national guard', 'wyoming army national guard', 'wyoming air national guard', 'district of columbia national guard', 'district of columbia army national guard', 'district of columbia air national guard', 'd.c. national guard', 'd.c. army national guard', 'd.c. air national guard', 'dc national guard', 'dc army national guard', 'dc air national guard', 'puerto rico national guard', 'puerto rico army national guard', 'puerto rico air national guard', 'guam national guard', 'guam army national guard', 'guam air national guard', 'virgin islands national guard', 'virgin islands army national guard', 'virgin islands air national guard', 'u.s. virgin islands national guard', 'u.s. virgin islands army national guard', 'u.s. virgin islands air national guard'), 'defense', 'U.S. military reserve component (nationalguard.mil, OPM data), about 433,000 members (FY2025)'],
|
|
1074
1824
|
['FBI', 8, exactly('fbi', 'federal bureau of investigation', 'u.s. federal bureau of investigation', 'us federal bureau of investigation'), 'defense', 'U.S. federal law-enforcement agency (fbi.gov, OPM data), about 35,300 staff (July 2026)'],
|
|
@@ -1542,7 +2292,7 @@ export function companyScore(name, { overrides = new Map(), headcount = 0, indus
|
|
|
1542
2292
|
/**
|
|
1543
2293
|
* A company's score from a read of the network (readNetwork): its headcount,
|
|
1544
2294
|
* industry and where that came from, and its sectors, as scoreNetwork scores
|
|
1545
|
-
* it. Rescoring, Settings' preview and
|
|
2295
|
+
* it. Rescoring, Settings' preview and the Scores tab all score a company
|
|
1546
2296
|
* through this, so they give it the same score.
|
|
1547
2297
|
*/
|
|
1548
2298
|
export function companyScoreIn(read, name, { overrides, focus } = {}) {
|
|
@@ -1619,7 +2369,8 @@ export const RULE_TABLES = {
|
|
|
1619
2369
|
junkCompany: JUNK_COMPANY, notACompany: NOT_A_COMPANY, nameEnd: NAME_END, legalForm: LEGAL_FORM, wholeName: WHOLE_NAME,
|
|
1620
2370
|
schoolName: SCHOOL_NAME, notItsStaff: NOT_ITS_STAFF, platformRole: PLATFORM_ROLE, platforms: PLATFORMS, bonuses: BONUSES,
|
|
1621
2371
|
audience: AUDIENCE, notTheirAudience: NOT_THEIR_AUDIENCE, audienceUnits: UNIT, ownVenture: OWN_VENTURE,
|
|
1622
|
-
affiliation: AFFILIATION, academic: ACADEMIC,
|
|
2372
|
+
affiliation: AFFILIATION, academic: ACADEMIC, retired: RETIRED, retiredOnly: RETIRED_ONLY, officeName: OFFICE_NAME, atCompany: AT_COMPANY, healthOrg: HEALTH_ORG, specialty: SPECIALTY, serviceSchool: SERVICE_SCHOOL,
|
|
2373
|
+
offices: OFFICE_TABLES,
|
|
1623
2374
|
};
|
|
1624
2375
|
|
|
1625
2376
|
// ── putting it together ──────────────────────────────────────────────────────
|
|
@@ -1636,7 +2387,7 @@ export function tierFor(power) {
|
|
|
1636
2387
|
return 'D';
|
|
1637
2388
|
}
|
|
1638
2389
|
|
|
1639
|
-
//
|
|
2390
|
+
// Scores → Tiers: graded on your network's curve (the default), or on the
|
|
1640
2391
|
// fixed scale above.
|
|
1641
2392
|
export const TIER_SCALES = ['curve', 'fixed'];
|
|
1642
2393
|
export const TIER_SCALE_SETTING = {
|
|
@@ -1914,7 +2665,7 @@ export function readNetwork(rows = [], { industryOf, sectorsOf, groupOf } = {})
|
|
|
1914
2665
|
* many of your people work at each company and each company's one industry;
|
|
1915
2666
|
* then, with `tierScale` 'curve', everyone is graded on the network's curve,
|
|
1916
2667
|
* with cut-offs from the 1st-degree people's scores so far (the app passes
|
|
1917
|
-
* the saved
|
|
2668
|
+
* the saved Scores → Tiers, which is the curve unless you chose otherwise;
|
|
1918
2669
|
* called without one, a network is graded on the fixed scale); then
|
|
1919
2670
|
* 1st-degree bridges get their circle boost from their scored circle, judged
|
|
1920
2671
|
* by the tiers you see, and are graded again on the same cut-offs.
|