six-degrees 0.2.1 → 0.3.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.
Files changed (470) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +10 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +39 -11
  5. package/.next/standalone/.next/required-server-files.json +4 -1
  6. package/.next/standalone/.next/routes-manifest.json +60 -0
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +10 -10
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +3 -2
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +3 -2
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +3 -2
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/app-paths-manifest.json +3 -0
  30. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/build-manifest.json +12 -0
  31. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/server-reference-manifest.json +4 -0
  32. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +9 -0
  33. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.map +5 -0
  34. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -0
  35. package/.next/standalone/.next/server/app/api/company-scores/legacy/route_client-reference-manifest.js +3 -0
  36. package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -2
  37. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/connections/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/data/export/route/app-paths-manifest.json +3 -0
  41. package/.next/standalone/.next/server/app/api/data/export/route/build-manifest.json +12 -0
  42. package/.next/standalone/.next/server/app/api/data/export/route/server-reference-manifest.json +4 -0
  43. package/.next/standalone/.next/server/app/api/data/export/route.js +7 -0
  44. package/.next/standalone/.next/server/app/api/data/export/route.js.map +5 -0
  45. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -0
  46. package/.next/standalone/.next/server/app/api/data/export/route_client-reference-manifest.js +3 -0
  47. package/.next/standalone/.next/server/app/api/data/import/route/app-paths-manifest.json +3 -0
  48. package/.next/standalone/.next/server/app/api/data/import/route/build-manifest.json +12 -0
  49. package/.next/standalone/.next/server/app/api/data/import/route/server-reference-manifest.json +4 -0
  50. package/.next/standalone/.next/server/app/api/data/import/route.js +8 -0
  51. package/.next/standalone/.next/server/app/api/data/import/route.js.map +5 -0
  52. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -0
  53. package/.next/standalone/.next/server/app/api/data/import/route_client-reference-manifest.js +3 -0
  54. package/.next/standalone/.next/server/app/api/data/restart/route/app-paths-manifest.json +3 -0
  55. package/.next/standalone/.next/server/app/api/data/restart/route/build-manifest.json +12 -0
  56. package/.next/standalone/.next/server/app/api/data/restart/route/server-reference-manifest.json +4 -0
  57. package/.next/standalone/.next/server/app/api/data/restart/route.js +8 -0
  58. package/.next/standalone/.next/server/app/api/data/restart/route.js.map +5 -0
  59. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -0
  60. package/.next/standalone/.next/server/app/api/data/restart/route_client-reference-manifest.js +3 -0
  61. package/.next/standalone/.next/server/app/api/data/reveal/route/app-paths-manifest.json +3 -0
  62. package/.next/standalone/.next/server/app/api/data/reveal/route/build-manifest.json +12 -0
  63. package/.next/standalone/.next/server/app/api/data/reveal/route/server-reference-manifest.json +4 -0
  64. package/.next/standalone/.next/server/app/api/data/reveal/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/data/reveal/route.js.map +5 -0
  66. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -0
  67. package/.next/standalone/.next/server/app/api/data/reveal/route_client-reference-manifest.js +3 -0
  68. package/.next/standalone/.next/server/app/api/data/route/app-paths-manifest.json +3 -0
  69. package/.next/standalone/.next/server/app/api/data/route/build-manifest.json +12 -0
  70. package/.next/standalone/.next/server/app/api/data/route/server-reference-manifest.json +4 -0
  71. package/.next/standalone/.next/server/app/api/data/route.js +7 -0
  72. package/.next/standalone/.next/server/app/api/data/route.js.map +5 -0
  73. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -0
  74. package/.next/standalone/.next/server/app/api/data/route_client-reference-manifest.js +3 -0
  75. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +3 -2
  76. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
  78. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/api/network/route.js +3 -2
  80. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/api/notifications/route.js +3 -2
  82. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -2
  84. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/queue/route.js +3 -2
  86. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  88. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/api/settings/route/app-paths-manifest.json +3 -0
  90. package/.next/standalone/.next/server/app/api/settings/route/build-manifest.json +12 -0
  91. package/.next/standalone/.next/server/app/api/settings/route/server-reference-manifest.json +4 -0
  92. package/.next/standalone/.next/server/app/api/settings/route.js +9 -0
  93. package/.next/standalone/.next/server/app/api/settings/route.js.map +5 -0
  94. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -0
  95. package/.next/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +3 -0
  96. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/app-paths-manifest.json +3 -0
  97. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/build-manifest.json +12 -0
  98. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/server-reference-manifest.json +4 -0
  99. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +8 -0
  100. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.map +5 -0
  101. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -0
  102. package/.next/standalone/.next/server/app/api/settings/sector-preview/route_client-reference-manifest.js +3 -0
  103. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/app-paths-manifest.json +3 -0
  104. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/build-manifest.json +12 -0
  105. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/server-reference-manifest.json +4 -0
  106. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +8 -0
  107. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.map +5 -0
  108. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -0
  109. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route_client-reference-manifest.js +3 -0
  110. package/.next/standalone/.next/server/app/api/setup-profile/route.js +3 -2
  111. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/unlock/route.js +3 -2
  113. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/update/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/update-images/route.js +3 -2
  117. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/users/route.js +3 -2
  119. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/avatars/[file]/route.js +2 -1
  121. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/favicon.ico/route.js +2 -1
  123. package/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/import.html +1 -1
  127. package/.next/standalone/.next/server/app/import.rsc +12 -12
  128. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  130. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +11 -11
  131. package/.next/standalone/.next/server/app/index.html +1 -1
  132. package/.next/standalone/.next/server/app/index.rsc +12 -12
  133. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
  134. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +12 -12
  135. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  136. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  137. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  138. package/.next/standalone/.next/server/app/launch.html +2 -2
  139. package/.next/standalone/.next/server/app/launch.rsc +12 -12
  140. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +12 -12
  141. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  142. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +11 -11
  143. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  145. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  147. package/.next/standalone/.next/server/app/paths.html +1 -1
  148. package/.next/standalone/.next/server/app/paths.rsc +12 -12
  149. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +12 -12
  150. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  151. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +11 -11
  152. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  153. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  154. package/.next/standalone/.next/server/app/profile.html +1 -1
  155. package/.next/standalone/.next/server/app/profile.rsc +12 -12
  156. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +12 -12
  157. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  158. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +11 -11
  159. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  160. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/app/queue.html +1 -1
  162. package/.next/standalone/.next/server/app/queue.rsc +12 -12
  163. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +12 -12
  164. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  165. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +11 -11
  166. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  167. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +20 -0
  168. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +11 -0
  169. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  170. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -0
  171. package/.next/standalone/.next/server/app/settings/page.js +13 -0
  172. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  173. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  174. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  175. package/.next/standalone/.next/server/app/settings.html +1 -0
  176. package/.next/standalone/.next/server/app/settings.meta +26 -0
  177. package/.next/standalone/.next/server/app/settings.rsc +24 -0
  178. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -0
  179. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +4 -0
  180. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +28 -0
  181. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  183. package/.next/standalone/.next/server/app/setup.html +1 -1
  184. package/.next/standalone/.next/server/app/setup.rsc +12 -12
  185. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +12 -12
  186. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  187. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +11 -11
  188. package/.next/standalone/.next/server/app-paths-manifest.json +10 -0
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0i5hdu4._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +8 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +3 -0
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +3 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +3 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +3 -0
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +3 -0
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +3 -0
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +3 -0
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +8 -0
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +3 -0
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +3 -0
  214. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +3 -0
  215. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +3 -0
  216. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/_072jl0y._.js +3 -0
  218. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +3 -0
  219. package/.next/standalone/.next/server/chunks/_1_fynia._.js +21 -3
  220. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +3 -0
  221. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -1
  222. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  223. package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +3 -0
  224. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_company-scores_legacy_route_actions_1ncspu_.js +3 -0
  225. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_export_route_actions_1lkimty.js +3 -0
  226. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_import_route_actions_1eji0kd.js +3 -0
  227. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restart_route_actions_00_-u6o.js +3 -0
  228. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_reveal_route_actions_0-moqtn.js +3 -0
  229. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_route_actions_0j0w5ep.js +3 -0
  230. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_route_actions_1bpm9ac.js +3 -0
  231. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-preview_route_actions_0w79454.js +3 -0
  232. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-suggestions_route_actions_1eoqugp.js +3 -0
  233. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +155 -0
  234. package/.next/standalone/.next/server/chunks/{lib_rpc_0mc73lk.js → lib_rpc_00tx2sm.js} +1 -1
  235. package/.next/standalone/.next/server/chunks/lib_rpc_02-qa4d.js +3 -0
  236. package/.next/standalone/.next/server/chunks/lib_rpc_02ndsg2.js +3 -0
  237. package/.next/standalone/.next/server/chunks/lib_rpc_08q5vp6.js +3 -0
  238. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +3 -0
  239. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -2
  240. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +3 -0
  241. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +3 -0
  242. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +3 -0
  243. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +3 -0
  244. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +3 -0
  245. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +3 -0
  246. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +3 -0
  247. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +3 -0
  248. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +3 -0
  249. package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +158 -0
  250. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0mocm6d._.js → [root-of-the-server]__0an6blh._.js} +3 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +3 -0
  252. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bipr5s._.js → [root-of-the-server]__1z1j3ek._.js} +2 -2
  253. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +3 -0
  254. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +3 -0
  255. package/.next/standalone/.next/server/chunks/ssr/{_0ggabjc._.js → _0942v8n._.js} +1 -1
  256. package/.next/standalone/.next/server/chunks/ssr/{_1djpk8t._.js → _0hl_t7r._.js} +2 -2
  257. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +132 -0
  258. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +3 -0
  259. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
  260. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_11x4dq5.js +3 -0
  261. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +2 -2
  262. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +4 -4
  263. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  264. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  265. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +3 -0
  266. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  267. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  268. package/.next/standalone/.next/server/chunks/ssr/node_modules_1f8t_rp._.js +23 -0
  269. package/.next/standalone/.next/server/chunks/ssr/node_modules_1nkgb45._.js +2 -2
  270. package/.next/standalone/.next/server/chunks/ssr/{node_modules_01xbj_9._.js → node_modules_1wax83z._.js} +3 -3
  271. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_00nap8h._.js → node_modules_next_dist_1bunoni._.js} +2 -2
  272. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +2 -2
  273. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  274. package/.next/standalone/.next/server/middleware-manifest.json +15 -7
  275. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  276. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  277. package/.next/standalone/.next/server/pages/404.html +1 -1
  278. package/.next/standalone/.next/server/pages/500.html +1 -1
  279. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  280. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  281. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  282. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  283. package/.next/standalone/.next/static/chunks/{34at5d2ifl62i.js → 0ngx9s6-zgshe.js} +5 -5
  284. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +2 -0
  285. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +1 -0
  286. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +44 -0
  287. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  288. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +1 -0
  289. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  290. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +1 -0
  291. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  292. package/.next/standalone/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  293. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  294. package/.next/standalone/.next/static/qqqcxvzBUizkECrT4swyK/_clientMiddlewareManifest.js +14 -0
  295. package/.next/standalone/CHANGELOG.md +298 -0
  296. package/.next/standalone/CONTRIBUTING.md +5 -2
  297. package/.next/standalone/README.md +138 -30
  298. package/.next/standalone/SECURITY.md +117 -10
  299. package/.next/standalone/app/api/company-scores/legacy/route.js +32 -0
  300. package/.next/standalone/app/api/company-scores/route.js +20 -11
  301. package/.next/standalone/app/api/data/export/route.js +49 -0
  302. package/.next/standalone/app/api/data/import/route.js +83 -0
  303. package/.next/standalone/app/api/data/restart/route.js +37 -0
  304. package/.next/standalone/app/api/data/reveal/route.js +14 -0
  305. package/.next/standalone/app/api/data/route.js +39 -0
  306. package/.next/standalone/app/api/ingest/route.js +15 -47
  307. package/.next/standalone/app/api/scraper/route.js +264 -125
  308. package/.next/standalone/app/api/settings/route.js +58 -0
  309. package/.next/standalone/app/api/settings/sector-preview/route.js +38 -0
  310. package/.next/standalone/app/api/settings/sector-suggestions/route.js +20 -0
  311. package/.next/standalone/app/api/update/route.js +188 -23
  312. package/.next/standalone/app/avatars/[file]/route.js +4 -2
  313. package/.next/standalone/app/components/CompanyScores.js +28 -7
  314. package/.next/standalone/app/components/EmptyState.js +8 -0
  315. package/.next/standalone/app/components/ForceGraph.js +39 -35
  316. package/.next/standalone/app/components/LegacyScoresCard.js +129 -0
  317. package/.next/standalone/app/components/Sidebar.js +11 -10
  318. package/.next/standalone/app/components/UpdatePanel.js +306 -119
  319. package/.next/standalone/app/components/UserProvider.js +4 -3
  320. package/.next/standalone/app/components/settings/DataSection.js +437 -0
  321. package/.next/standalone/app/components/settings/SectorSection.js +445 -0
  322. package/.next/standalone/app/components/ui.js +62 -0
  323. package/.next/standalone/app/launch/page.js +30 -20
  324. package/.next/standalone/app/page.js +13 -0
  325. package/.next/standalone/app/paths/page.js +12 -4
  326. package/.next/standalone/app/profile/page.js +41 -8
  327. package/.next/standalone/app/queue/page.js +8 -2
  328. package/.next/standalone/app/settings/page.js +96 -0
  329. package/.next/standalone/app/setup/page.js +32 -16
  330. package/.next/standalone/bin/args.mjs +44 -0
  331. package/.next/standalone/bin/six-degrees.mjs +9 -15
  332. package/.next/standalone/lib/companies.js +44 -119
  333. package/.next/standalone/lib/csv.js +8 -0
  334. package/.next/standalone/lib/data-export.js +170 -0
  335. package/.next/standalone/lib/data-folder.js +238 -0
  336. package/.next/standalone/lib/data-import.js +878 -0
  337. package/.next/standalone/lib/db-client.js +48 -3
  338. package/.next/standalone/lib/durable.js +73 -0
  339. package/.next/standalone/lib/gate.js +90 -1
  340. package/.next/standalone/lib/ingest.js +40 -0
  341. package/.next/standalone/lib/legacy-offer.js +185 -0
  342. package/.next/standalone/lib/legacy-scores.js +77 -0
  343. package/.next/standalone/lib/linkedin-limits.js +124 -1
  344. package/.next/standalone/lib/rpc.js +121 -13
  345. package/.next/standalone/lib/sample-stats.js +16 -0
  346. package/.next/standalone/lib/scan-progress.js +13 -0
  347. package/.next/standalone/lib/scan-state.js +53 -0
  348. package/.next/standalone/lib/scanner-python.js +685 -0
  349. package/.next/standalone/lib/scanner-setup.js +104 -0
  350. package/.next/standalone/lib/scoring.js +747 -138
  351. package/.next/standalone/lib/scraper-client.js +9 -5
  352. package/.next/standalone/lib/sector-directory.js +1532 -0
  353. package/.next/standalone/lib/sector-focus.js +163 -0
  354. package/.next/standalone/lib/sector-labels.js +81 -0
  355. package/.next/standalone/lib/settings-effects.js +59 -0
  356. package/.next/standalone/lib/settings.js +83 -0
  357. package/.next/standalone/lib/updater-job.js +838 -0
  358. package/.next/standalone/lib/updater.js +494 -0
  359. package/.next/standalone/middleware.js +25 -28
  360. package/.next/standalone/next.config.mjs +19 -3
  361. package/.next/standalone/package-lock.json +2 -2
  362. package/.next/standalone/package.json +1 -2
  363. package/.next/standalone/scripts/apply-update.sh +482 -0
  364. package/.next/standalone/scripts/build-app.mjs +251 -9
  365. package/.next/standalone/scripts/requirements.txt +106 -4
  366. package/.next/standalone/scripts/test-release-server.mjs +143 -0
  367. package/.next/standalone/server.js +1 -1
  368. package/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  369. package/.next/static/chunks/{34at5d2ifl62i.js → 0ngx9s6-zgshe.js} +5 -5
  370. package/.next/static/chunks/0q-_khndfxubv.js +2 -0
  371. package/.next/static/chunks/1c79veqedvubt.js +1 -0
  372. package/.next/static/chunks/1chp3nx45meg5.js +44 -0
  373. package/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  374. package/.next/static/chunks/1uadqylrod5_v.js +1 -0
  375. package/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  376. package/.next/static/chunks/289ktj97di6zg.js +1 -0
  377. package/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  378. package/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  379. package/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  380. package/.next/static/qqqcxvzBUizkECrT4swyK/_clientMiddlewareManifest.js +14 -0
  381. package/CHANGELOG.md +298 -0
  382. package/README.md +138 -30
  383. package/SECURITY.md +117 -10
  384. package/bin/args.mjs +44 -0
  385. package/bin/six-degrees.mjs +9 -15
  386. package/lib/companies.js +44 -119
  387. package/lib/csv.js +8 -0
  388. package/lib/data-export.js +170 -0
  389. package/lib/data-folder.js +238 -0
  390. package/lib/data-import.js +878 -0
  391. package/lib/db-client.js +48 -3
  392. package/lib/durable.js +73 -0
  393. package/lib/gate.js +90 -1
  394. package/lib/ingest.js +40 -0
  395. package/lib/legacy-offer.js +185 -0
  396. package/lib/legacy-scores.js +77 -0
  397. package/lib/linkedin-limits.js +124 -1
  398. package/lib/rpc.js +121 -13
  399. package/lib/sample-stats.js +16 -0
  400. package/lib/scan-progress.js +13 -0
  401. package/lib/scan-state.js +53 -0
  402. package/lib/scanner-python.js +685 -0
  403. package/lib/scanner-setup.js +104 -0
  404. package/lib/scoring.js +747 -138
  405. package/lib/scraper-client.js +9 -5
  406. package/lib/sector-directory.js +1532 -0
  407. package/lib/sector-focus.js +163 -0
  408. package/lib/sector-labels.js +81 -0
  409. package/lib/settings-effects.js +59 -0
  410. package/lib/settings.js +83 -0
  411. package/lib/updater-job.js +838 -0
  412. package/lib/updater.js +494 -0
  413. package/package.json +1 -2
  414. package/scripts/requirements.txt +106 -4
  415. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jrmmj-._.js +0 -150
  416. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qx548g._.js +0 -151
  417. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an3leo._.js +0 -150
  418. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m8zedk._.js +0 -155
  419. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1zor0ys._.js +0 -3
  420. package/.next/standalone/.next/server/chunks/_0_rp-01._.js +0 -3
  421. package/.next/standalone/.next/server/chunks/_0hzdk79._.js +0 -3
  422. package/.next/standalone/.next/server/chunks/_0ow7u5r._.js +0 -3
  423. package/.next/standalone/.next/server/chunks/_0t0zc2d._.js +0 -3
  424. package/.next/standalone/.next/server/chunks/_0wa631d._.js +0 -3
  425. package/.next/standalone/.next/server/chunks/_13ccnao._.js +0 -3
  426. package/.next/standalone/.next/server/chunks/_13es063._.js +0 -3
  427. package/.next/standalone/.next/server/chunks/_142872j._.js +0 -3
  428. package/.next/standalone/.next/server/chunks/_18wsb_a._.js +0 -8
  429. package/.next/standalone/.next/server/chunks/_1e-4h5h._.js +0 -3
  430. package/.next/standalone/.next/server/chunks/_1gvvd7n._.js +0 -158
  431. package/.next/standalone/.next/server/chunks/_1moda66._.js +0 -3
  432. package/.next/standalone/.next/server/chunks/_1p6ghzv._.js +0 -3
  433. package/.next/standalone/.next/server/chunks/_1xmmi_v._.js +0 -3
  434. package/.next/standalone/.next/server/chunks/_1z-yrlf._.js +0 -3
  435. package/.next/standalone/.next/server/chunks/ssr/_0bf3x4q._.js +0 -3
  436. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +0 -4
  437. package/.next/standalone/.next/static/ZGTiDrZ5NHGETgGdlhssu/_clientMiddlewareManifest.js +0 -6
  438. package/.next/standalone/.next/static/chunks/0jk64vo17avsb.js +0 -1
  439. package/.next/standalone/.next/static/chunks/0s65dsrthylo6.js +0 -2
  440. package/.next/standalone/.next/static/chunks/0uyfdhi8dek85.js +0 -16
  441. package/.next/standalone/.next/static/chunks/220-ne82aus0c.js +0 -44
  442. package/.next/standalone/.next/static/chunks/229haorf0f19b.js +0 -2
  443. package/.next/standalone/.next/static/chunks/2g54uafnwuakc.js +0 -1
  444. package/.next/standalone/.next/static/chunks/2spwqs6b6dx13.js +0 -1
  445. package/.next/standalone/.next/static/chunks/38dlepuubiox0.js +0 -1
  446. package/.next/standalone/scripts/score-connections.sql +0 -80
  447. package/.next/standalone/scripts/score_new_connections.sql +0 -77
  448. package/.next/standalone/site/app.js +0 -84
  449. package/.next/standalone/site/index.html +0 -221
  450. package/.next/standalone/site/style.css +0 -155
  451. package/.next/standalone/site/video/install-mac-poster.jpg +0 -0
  452. package/.next/standalone/site/video/install-mac.mp4 +0 -0
  453. package/.next/standalone/site/video/linux-poster.jpg +0 -0
  454. package/.next/standalone/site/video/linux.mp4 +0 -0
  455. package/.next/standalone/site/video/scan-poster.jpg +0 -0
  456. package/.next/standalone/site/video/scan.mp4 +0 -0
  457. package/.next/static/ZGTiDrZ5NHGETgGdlhssu/_clientMiddlewareManifest.js +0 -6
  458. package/.next/static/chunks/0jk64vo17avsb.js +0 -1
  459. package/.next/static/chunks/0s65dsrthylo6.js +0 -2
  460. package/.next/static/chunks/0uyfdhi8dek85.js +0 -16
  461. package/.next/static/chunks/220-ne82aus0c.js +0 -44
  462. package/.next/static/chunks/229haorf0f19b.js +0 -2
  463. package/.next/static/chunks/2g54uafnwuakc.js +0 -1
  464. package/.next/static/chunks/2spwqs6b6dx13.js +0 -1
  465. package/.next/static/chunks/38dlepuubiox0.js +0 -1
  466. package/scripts/score_new_connections.sql +0 -77
  467. /package/.next/standalone/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_buildManifest.js +0 -0
  468. /package/.next/standalone/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_ssgManifest.js +0 -0
  469. /package/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_buildManifest.js +0 -0
  470. /package/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_ssgManifest.js +0 -0
@@ -12,21 +12,29 @@
12
12
  // the first part that names a job, skipping "Ex-" and
13
13
  // "Former" parts; student clubs and fraternities don't
14
14
  // count as company titles.
15
- // company weight 0.45 + 0.055 × company score, so 0.56 (no company)
16
- // up to 1.0 (a company scored 10).
15
+ // company weight 0.45 + 0.055 × company score, so 0.615 when no company
16
+ // is found (score 3) up to 1.0 (a company scored 10);
17
+ // 0.505 is the floor, for a company you score 1.
17
18
  // reach bonus (≤1.5) real signals only, matched as whole words: investor,
18
- // YC, Forbes, audience or revenue in the millions,
19
- // TEDx/keynote/patents/awards.
19
+ // YC, 30 under 30, audience or revenue in the millions,
20
+ // TEDx/keynote/patents, and the top honours of every
21
+ // field (a Nobel, a Pulitzer, an Oscar, an Olympic
22
+ // medal…). Halved at a company scored 4 or less,
23
+ // judged before any sector lean.
20
24
  // bridge boost (≤1) for a 1st-degree person whose circle is mapped, when
21
25
  // their circle is unusually strong. Recomputed each time,
22
26
  // never ratcheted upward.
23
27
  //
24
28
  // Company scores come from, in order: the score you set, a curated list of
25
29
  // well-known companies, and otherwise how many of your people work there.
30
+ // If you picked sectors in Settings, a company in one of them gets +1 or +2
31
+ // on top (never above 10, never on a score you set). That is worked out fresh
32
+ // on every rescore and never written into your company scores.
26
33
  // Everything here is a plain function; tests/scoring.test.mjs pins it down.
27
34
 
28
35
  // Bump when the model changes: the app rescores stored rows once on next load.
29
- export const SCORING_VERSION = 2;
36
+ // 3: one industry per company (not per person), and the sector lean.
37
+ export const SCORING_VERSION = 3;
30
38
 
31
39
  // ── title ────────────────────────────────────────────────────────────────────
32
40
 
@@ -65,12 +73,72 @@ const TITLE_RULES = [
65
73
  ];
66
74
 
67
75
  const INTERN = /\b(intern|internship|trainee|apprentice)\b/;
68
- const STUDENT = /\b(student|undergrad(uate)?|aspiring|phd candidate|recent grad(uate)?|class of 20\d\d|incoming (analyst|intern|associate|swe|engineer)|gpa|freshman|sophomore|b\.?s\.? candidate)\b|^\s*(honors )?(cs|computer science|ms ?cs|mscs|bscs|engineering|finance|marketing|business|economics|accounting|biology|psychology|information technology)\b.*(\s(@|at)\s*)(ucf|uf|university|college)\b/;
76
+ // Matched against lowercase text. "CS @ University of Utah" is a student; a
77
+ // school's short name ("CS @ NYU") is SHORT_NAME's job, below.
78
+ const STUDENT = /\b(student|undergrad(uate)?|aspiring|phd candidate|recent grad(uate)?|class of 20\d\d|incoming (analyst|intern|associate|swe|engineer)|gpa|freshman|sophomore|b\.?s\.? candidate)\b|^\s*(honors )?(cs|computer science|ms ?cs|mscs|bscs|engineering|finance|marketing|business|economics|accounting|biology|psychology|information technology)\b.*(\s(@|at)\s*)(university|college)\b/;
79
+ const MAJOR = /^\s*(honors )?(cs|computer science|ms ?cs|mscs|bscs|engineering|finance|marketing|business|economics|accounting|biology|psychology|information technology)\b/i;
80
+ // A school's short name as people write it, in capitals: two to four letters
81
+ // that start or end with the U of "University" (USC, NYU, UCLA, BYU, UNC). It
82
+ // names no school, so every school's students and clubs read alike. Matched
83
+ // against the text as written: only capitals are a name ("usc" isn't one).
84
+ const SHORT_NAME = /\b(?:U[A-Z]{1,3}|[A-Z]{1,3}U)\b/g;
85
+ const AT_SHORT_NAME = /\s(?:@|[Aa][Tt])\s*(U[A-Z]{1,3}|[A-Z]{1,3}U)\b/;
86
+ // Short names that aren't a school's, whatever they look like: countries and
87
+ // unions of them, the AAU's youth sports clubs and a Unitarian (UU) society;
88
+ // and companies, a league and labor unions people write in capitals (UBS,
89
+ // UFC, UPMC, UnitedHealth Group and UnitedHealthcare, Under Armour, UL, United
90
+ // Launch Alliance, UTC; the UAW, USW, SEIU, UFCW and UFT). A short name the
91
+ // curated list knows as a company is one too (UPS). The price: a school whose
92
+ // short name is on this list (UA, UL, UTC) isn't read from it.
93
+ const NOT_A_SCHOOL = new Set(['US', 'USA', 'UK', 'UAE', 'EU', 'UN', 'UU', 'AAU',
94
+ 'UBS', 'UFC', 'UPMC', 'UHG', 'UHC', 'UA', 'UL', 'ULA', 'UTC', 'UAW', 'USW', 'SEIU', 'UFCW', 'UFT']);
69
95
  const GREEK = '(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega)';
70
96
  const STUDENT_ORG = new RegExp(`\\b(fraternity|sorority|student (government|association|union|org(anization)?|senate)|honor society|campus)\\b|\\b${GREEK}\\s+${GREEK}\\b`);
71
- const SCHOOL_CLUB = /\b(club|chapter|society|association)\b.*\b(ucf|uf|university|college|school|student)\b|\b(ucf|uf|university|college|school|student)\b.*\b(club|chapter|society)\b/;
97
+ // A club named with a school's words, either way round ("Marketing Club at the
98
+ // University of Utah", "University Consulting Club"). An association only
99
+ // counts before the school's words: "University Staff Association" is staff.
100
+ const SCHOOL_WORD_CLUB = /\b(club|chapter|society|association)\b.*\b(university|college|school|student)\b|\b(university|college|school|student)\b.*\b(club|chapter|society)\b/;
101
+ const CLUB = /\b(club|chapter|society|association)\b/;
102
+ // With a school's short name only a club or a society is a student group
103
+ // ("USC Marketing Club", "NYU Finance Society"), or an association named for
104
+ // a field of study ("USC Trojan Marketing Association"). A chapter is as often
105
+ // a union local's or a professional body's, and another association a
106
+ // university's staff ("KU Endowment Association").
107
+ const SHORT_NAME_CLUB = /\b(club|society)\b/;
108
+ 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/;
109
+ // An alumni club or a parents' association is for grown-ups.
110
+ const GROWN_UP = /\b(alumni|alumnae|alumnus|alum|parents?)\b/;
72
111
  const FORMER = /^\s*(ex[-\s]|former(ly)?\b|previously\b|prev\b|past\b|retired\b)/i;
73
112
 
113
+ /** Can this short name ("USC", "UBS") be a school's? Not one on NOT_A_SCHOOL, nor a company the curated list knows. */
114
+ function schoolShortName(name) {
115
+ if (NOT_A_SCHOOL.has(name) || /^US[A-Z]{2}$/.test(name)) return false; // USGA, USTA, USAA, USPS…
116
+ const listed = cleanCompany(name);
117
+ return !KNOWN_INDUSTRY.has(listed) || KNOWN_INDUSTRY.get(listed) === 'education';
118
+ }
119
+
120
+ /** Does this part of a headline say they're studying now? "CS @ NYU", "Economics student". */
121
+ function studies(part) {
122
+ if (STUDENT.test(` ${part.toLowerCase()} `)) return true;
123
+ const at = MAJOR.test(part) && AT_SHORT_NAME.exec(part);
124
+ return !!at && schoolShortName(at[1]);
125
+ }
126
+
127
+ /**
128
+ * Is this part of a headline a role in a school club? "President, USC
129
+ * Marketing Club", "VP, NYU Finance Society", "President of the Marketing Club
130
+ * at the University of Utah". A club, chapter, society or association with a
131
+ * school's words, or a club or society with its short name, and not an alumni
132
+ * or parents' group.
133
+ */
134
+ function isSchoolClub(part) {
135
+ const lower = part.toLowerCase();
136
+ if (!CLUB.test(lower) || GROWN_UP.test(lower)) return false;
137
+ if (SCHOOL_WORD_CLUB.test(lower)) return true;
138
+ if (!SHORT_NAME_CLUB.test(lower) && !FIELD_ASSOCIATION.test(lower)) return false;
139
+ return [...part.matchAll(SHORT_NAME)].some(([name]) => schoolShortName(name));
140
+ }
141
+
74
142
  /** The parts of a headline, in order: "CEO @ X | Ex-Google | Speaker" → three parts. */
75
143
  export function headlineParts(headline = '') {
76
144
  return String(headline || '').split(/\s*(?:\||•|·|⋅|\/\/| – | — | - |;)\s*/).map((s) => s.trim()).filter(Boolean);
@@ -79,7 +147,7 @@ export function headlineParts(headline = '') {
79
147
  function readPart(part) {
80
148
  let s = ` ${part.toLowerCase()} `;
81
149
  if (INTERN.test(s)) return LEVELS.intern;
82
- if (STUDENT.test(s)) return LEVELS.student;
150
+ if (studies(part)) return LEVELS.student;
83
151
  let best = null;
84
152
  for (const [re, key] of TITLE_RULES) {
85
153
  re.lastIndex = 0;
@@ -87,15 +155,14 @@ function readPart(part) {
87
155
  s = s.replace(re, ' ');
88
156
  if (key && (!best || LEVELS[key].level > best.level || (LEVELS[key].level === best.level && LEVELS[key].points > best.points))) best = LEVELS[key];
89
157
  }
90
- const lower = part.toLowerCase();
91
- if (best && best.level >= 3 && (STUDENT_ORG.test(lower) || SCHOOL_CLUB.test(lower))) return { ...LEVELS.student, label: 'Student (club role)' };
158
+ if (best && best.level >= 3 && (STUDENT_ORG.test(part.toLowerCase()) || isSchoolClub(part))) return { ...LEVELS.student, label: 'Student (club role)' };
92
159
  return best;
93
160
  }
94
161
 
95
162
  /**
96
163
  * Every role a headline names: "CEO @ X | Ex-VP at Google | Speaker" gives a
97
164
  * current CEO role at X and a former VP role at Google. A part that names only
98
- * a company ("GTM @ Whatnot") still says they work there.
165
+ * a company ("GTM @ Quillon") still says they work there.
99
166
  */
100
167
  export function readRoles(headline = '', role = '') {
101
168
  const out = [];
@@ -112,7 +179,7 @@ export function readRoles(headline = '', role = '') {
112
179
 
113
180
  /** Is this person a student right now? Then no other title makes them powerful yet. */
114
181
  export function isStudent(headline = '') {
115
- return headlineParts(headline).some((p) => !FORMER.test(p) && (STUDENT.test(` ${p.toLowerCase()} `) || INTERN.test(` ${p.toLowerCase()} `)));
182
+ return headlineParts(headline).some((p) => !FORMER.test(p) && (studies(p) || INTERN.test(` ${p.toLowerCase()} `)));
116
183
  }
117
184
 
118
185
  /** The person's first current title (a quick read; scoring weighs every role). */
@@ -125,109 +192,292 @@ export function readTitle(headline = '', role = '') {
125
192
  // ── company ──────────────────────────────────────────────────────────────────
126
193
 
127
194
  // Well-known companies, scored for what a person there can usually open up.
128
- // These are defaults; a score you set always wins. Aliases are matched from
129
- // the start of the (cleaned) company name, as whole words.
195
+ // These are the defaults for everyone who runs the app, so one written rule
196
+ // decides who is on the list, not anyone's own ties to a company. A company is
197
+ // on it only if most US professionals would recognise it:
198
+ // - a household-name brand;
199
+ // - a Fortune 500 company, or a public company as large;
200
+ // - a top global VC, private equity, consulting, law or accounting firm;
201
+ // - a frontier AI lab;
202
+ // - a top national university.
203
+ // Scores follow one scale: 10 the largest tech platforms and the frontier AI
204
+ // labs, 9 elite (the most sought-after employers), 8 major, 7 well-known.
205
+ // Nothing on the list is below 7. Regional picks, picks from one person's
206
+ // career or network, and small startups are not on it: they are estimated
207
+ // from the network like any other company (4, or 5–6 when many of the
208
+ // network's people work there), and anyone can score them on Paths → Scores,
209
+ // where a score set always wins. When in doubt, a company stays off: the
210
+ // estimate is the neutral default.
211
+ //
212
+ // The rule is applied from named sources, the same way in every field, so
213
+ // anyone can check the list or extend it: the Fortune 500, the largest law
214
+ // firms, one ranking of universities and the others docs/brain/SCORING.md
215
+ // names. An addition is 8 at the size of the Fortune 100 (over about $45
216
+ // billion a year), as nearly every such company already here is, and 7 below
217
+ // it; where the list already scores its kind of firm otherwise, it follows
218
+ // those peers (private equity at 9 with Bain Capital, the largest law firms at
219
+ // 8 with the Big Four, universities by their rank). SCORING.md also says what
220
+ // applying the rule changed; lib/legacy-scores.js keeps the old scores, for
221
+ // the one-time offer to keep them (never for scoring).
222
+ //
223
+ // Aliases are matched from the start of the (cleaned) company name, as whole
224
+ // words, so they are kept narrow. A name that other companies also start with
225
+ // (Ford, Delta, Fidelity, Warner, Apollo, Columbia, Chase) matches only in the
226
+ // forms its company uses, ending there ($), and never as a short form that is
227
+ // also a word or a title: "GM" is a general manager. A dealership, a bottler
228
+ // or a franchise has staff of its own ("Toyota of …", "Coca-Cola
229
+ // Consolidated"), and so do companies that share a name or were spun off
230
+ // (Fidelity National Financial, Warner Music Group, J&J Snack Foods, Merck
231
+ // Millipore, Chase Brass). Only a name no other company starts with stays open
232
+ // (\b): Costco, Pfizer, "Deloitte Digital". tests/known-companies.test.mjs
233
+ // holds the names each must read and the names that must stay their own.
234
+ //
235
+ // The last field is the company's industry, one of lib/companies.js's
236
+ // INDUSTRIES keys (this file imports nothing, so they are plain strings;
237
+ // tests/companies.test.mjs checks each one). Most of these names carry no
238
+ // industry word, so without it Adobe, Pfizer or MIT would take whatever their
239
+ // people's headlines happen to say. Where a name does say something
240
+ // (YouTube → media, Tesla → industry), the field agrees with it.
241
+
242
+ // A brand's name on an arena or a stadium is not the brand's company: "State
243
+ // Farm Arena" and "AT&T Stadium" have staff of their own. Brands that name
244
+ // well-known venues are matched with this.
245
+ const VENUE = String.raw`(?! (performing arts )?(arena|stadium|center|centre|field|park|ballpark|dome|amphitheat(er|re)|theat(er|re)|pavilion|forum|coliseum|speedway)\b)`;
246
+ const sponsor = (names) => new RegExp(String.raw`^(${names})\b${VENUE}`);
247
+ // A university by its own names: the name alone or with "University", and its
248
+ // schools ("Harvard Business School", "Yale School of Management", "Stanford
249
+ // University School of Medicine"). Not a hospital, health plan, lab or
250
+ // company that shares its name (Harvard Pilgrim Health Care, Stanford Health
251
+ // Care, Vanderbilt University Medical Center, Brown University Health), nor
252
+ // another school that does (Princeton High School). `more` adds a form of its own.
253
+ const SCHOOLS = String.raw`( [a-z]+)?( graduate)? school of [a-z ,&]+| (business|law|medical|divinity|nursing|dental|journalism|graduate|extension|kennedy) school`;
254
+ const university = (names, more = '') => new RegExp(String.raw`^(${names})( university)?(${SCHOOLS}${more})?$`);
255
+
130
256
  export const KNOWN_COMPANIES = [
131
- // 10 — the largest platforms and the frontier AI labs
132
- ['Google', 10, /^(google|alphabet|google deepmind|deepmind)\b/],
133
- ['Apple', 10, /^apple( inc)?$/],
134
- ['Microsoft', 10, /^microsoft\b/],
135
- ['Amazon', 10, /^(amazon|aws|amazon web services)\b/],
136
- ['Meta', 10, /^(meta|facebook|instagram|whatsapp|reality labs)\b/],
137
- ['NVIDIA', 10, /^nvidia\b/],
138
- ['OpenAI', 10, /^(openai|open ai)\b/],
139
- ['Anthropic', 10, /^anthropic\b/],
140
- ['Tesla', 10, /^tesla( motors| inc)?$/],
141
- // 9 — elite tech, finance and consulting; Snap (your home turf)
142
- ['Snap', 9, /^(snap|snapchat|snap inc|specs)\b(?!dragon)/],
143
- ['Netflix', 9, /^netflix\b/],
144
- ['YouTube', 9, /^youtube\b/],
145
- ['LinkedIn', 9, /^linkedin\b/],
146
- ['TikTok', 9, /^(tiktok|bytedance)\b/],
147
- ['SpaceX', 9, /^spacex\b/],
148
- ['Stripe', 9, /^stripe\b/],
149
- ['Palantir', 9, /^palantir\b/],
150
- ['Anduril', 9, /^anduril\b/],
151
- ['Polymarket', 9, /^polymarket\b/],
152
- ['Kalshi', 9, /^kalshi\b/],
153
- ['Coca-Cola', 9, /^(the )?coca[-\s]cola\b/],
154
- ['Disney', 9, /^(the walt |walt )?disney\b/],
155
- ['Goldman Sachs', 9, /^goldman( sachs)?\b/],
156
- ['JPMorgan Chase', 9, /^(jp ?morgan|j\.p\. morgan|jpmorgan chase|chase)\b/],
157
- ['Morgan Stanley', 9, /^morgan stanley\b/],
158
- ['BlackRock', 9, /^blackrock\b/],
159
- ['McKinsey', 9, /^mckinsey\b/],
160
- ['BCG', 9, /^(bcg|boston consulting group)\b/],
161
- ['Bain', 9, /^bain( & company| and company| capital)?$/],
162
- ['Procter & Gamble', 9, /^(p&g|procter)\b/],
163
- ['Sequoia', 9, /^sequoia( capital)?\b/],
164
- ['Andreessen Horowitz', 9, /^(a16z|andreessen)\b/],
165
- ['Y Combinator', 9, /^(y combinator|yc)$/],
166
- // 8 — major tech, media, consumer, defense, finance
167
- ['Salesforce', 8, /^salesforce\b/], ['Adobe', 8, /^adobe\b/], ['Oracle', 8, /^oracle\b/], ['IBM', 8, /^ibm\b/],
168
- ['Uber', 8, /^uber\b/], ['Airbnb', 8, /^airbnb\b/], ['DoorDash', 8, /^doordash\b/], ['Spotify', 8, /^spotify\b/],
169
- ['Pinterest', 8, /^pinterest\b/], ['Reddit', 8, /^reddit\b/], ['X', 8, /^(x corp|twitter)\b/], ['Roblox', 8, /^roblox\b/],
170
- ['Shopify', 8, /^shopify\b/], ['Databricks', 8, /^databricks\b/], ['Snowflake', 8, /^snowflake\b/], ['Datadog', 8, /^datadog\b/],
171
- ['CrowdStrike', 8, /^crowdstrike\b/], ['ServiceNow', 8, /^servicenow\b/], ['Figma', 8, /^figma\b/], ['Canva', 8, /^canva\b/],
172
- ['Scale AI', 8, /^(scale ai|scale\.ai|scale\.com)$/], ['Intuit', 8, /^intuit\b/], ['Cisco', 8, /^cisco\b/], ['Qualcomm', 8, /^qualcomm\b/], ['AMD', 8, /^amd\b/],
173
- ['Samsung', 8, /^samsung\b/], ['Sony', 8, /^sony\b/], ['Epic Games', 8, /^epic games\b/], ['Whatnot', 8, /^whatnot\b/],
174
- ['Coinbase', 8, /^coinbase\b/], ['Robinhood', 8, /^robinhood\b/], ['Block', 8, /^(block|square|cash app)$/],
175
- ['Visa', 8, /^visa( inc)?$/], ['Mastercard', 8, /^mastercard\b/], ['American Express', 8, /^(american express|amex)\b/],
176
- ['Capital One', 8, /^capital one\b/], ['BNY', 8, /^(bny|bny mellon|bank of new york)\b/], ['Citi', 8, /^(citi|citigroup|citibank)\b/],
177
- ['Wells Fargo', 8, /^wells fargo\b/], ['Bank of America', 8, /^(bank of america|merrill)\b/], ['Fidelity', 8, /^fidelity\b/],
178
- ['Deloitte', 8, /^deloitte\b/], ['PwC', 8, /^(pwc|pricewaterhouse)\b/], ['EY', 8, /^(ey|ernst & young)$/], ['KPMG', 8, /^kpmg\b/],
179
- ['Accenture', 8, /^accenture\b/], ['Egon Zehnder', 8, /^egon zehnder\b/], ['Heidrick & Struggles', 8, /^heidrick\b/],
180
- ['Nike', 8, /^nike\b/], ['PepsiCo', 8, /^pepsi(co)?\b/], ['Walmart', 8, /^walmart\b/], ['Starbucks', 8, /^starbucks\b/],
181
- ['Unilever', 8, /^unilever\b/], ['LVMH', 8, /^lvmh\b/], ["L'Oréal", 8, /^l'?or[eé]al\b/], ['Red Bull', 8, /^red bull\b/],
182
- ['NBCUniversal', 8, /^(nbcuniversal|nbc universal|nbc)\b/], ['Warner Bros. Discovery', 8, /^(warner|wbd)\b/], ['MrBeast', 8, /^(mrbeast|beast industries)\b/],
183
- ['Lockheed Martin', 8, /^lockheed\b/], ['Northrop Grumman', 8, /^northrop\b/], ['Boeing', 8, /^boeing\b/], ['RTX', 8, /^(rtx|raytheon)\b/],
184
- ['General Dynamics', 8, /^general dynamics\b/], ['Blue Origin', 8, /^blue origin\b/], ['Siemens', 8, /^siemens\b/],
185
- ['Mitsubishi', 8, /^mitsubishi\b/], ['Toyota', 8, /^toyota\b/], ['Johnson & Johnson', 8, /^(johnson & johnson|j&j)\b/],
186
- ['Pfizer', 8, /^pfizer\b/], ['UnitedHealth', 8, /^(unitedhealth|optum)\b/], ['World Bank', 8, /^world bank\b/],
187
- ['NASA', 8, /^nasa\b/], ['Stanford University', 8, /^stanford\b/], ['Harvard University', 8, /^harvard\b/], ['MIT', 8, /^(mit|massachusetts institute of technology)$/],
188
- // 7 — strong, well-known companies
189
- ['Discord', 7, /^discord\b/], ['Whop', 7, /^whop\b/], ['Hims & Hers', 7, /^hims\b/], ['Paramount', 7, /^paramount\b/],
190
- ['Madison Square Garden', 7, /^(madison square garden|msg)\b/], ['Fanatics', 7, /^fanatics\b/], ['Nielsen', 7, /^nielsen\b/],
191
- ['The Trade Desk', 7, /^the trade desk\b/], ['SiriusXM', 7, /^siriusxm\b/], ['Riot Games', 7, /^riot games\b/], ['Twitch', 7, /^twitch\b/],
192
- ['Electronic Arts', 7, /^(electronic arts|ea)$/], ['Activision Blizzard', 7, /^(activision|blizzard)\b/], ['Lyft', 7, /^lyft\b/],
193
- ['Instacart', 7, /^instacart\b/], ['Duolingo', 7, /^duolingo\b/], ['Notion', 7, /^notion\b/], ['Vercel', 7, /^vercel\b/],
194
- ['Intel', 7, /^intel( corporation)?$/], ['Workday', 7, /^workday\b/], ['PayPal', 7, /^paypal\b/], ['Plaid', 7, /^plaid\b/],
195
- ['Brex', 7, /^brex\b/], ['Ramp', 7, /^ramp$/], ['Mercury', 7, /^mercury$/], ['Chime', 7, /^chime$/], ['Credit Karma', 7, /^credit karma\b/],
196
- ['Chick-fil-A', 7, /^chick[-\s]fil[-\s]a\b/], ['Target', 7, /^target( corporation)?$/], ['Booz Allen Hamilton', 7, /^booz allen\b/],
197
- ['L3Harris', 7, /^l3harris\b/], ['Leidos', 7, /^leidos\b/], ['U.S. Space Force', 7, /^(u\.?s\.? )?space force\b/],
198
- ['WPP', 7, /^wpp\b/], ['Omnicom', 7, /^omnicom\b/], ['Publicis', 7, /^publicis\b/], ['Interpublic', 7, /^(interpublic|ipg)\b/], ['Dentsu', 7, /^dentsu\b/],
199
- ['Kellanova', 7, /^(kellanova|kellogg)\b/], ['Campbell', 7, /^campbell\b/], ['Goodyear', 7, /^goodyear\b/],
200
- ['Duke University', 7, /^duke( university)?$/], ['UC Berkeley', 7, /^(uc berkeley|university of california,? berkeley)\b/],
201
- // 6 — notable
202
- ['Grindr', 6, /^grindr\b/], ['Genius Sports', 6, /^genius sports\b/], ['Later', 6, /^later$/], ['Hard Rock Digital', 6, /^hard rock\b/],
203
- ['Vanta', 6, /^vanta\b/], ['Kaseya', 6, /^kaseya\b/], ['Havas', 6, /^havas\b/], ['VaynerMedia', 6, /^vayner/],
204
- ['AdventHealth', 6, /^adventhealth\b/], ['University of Florida', 6, /^(uf|university of florida)$/], ['The Athletic', 6, /^the athletic\b/],
205
- // 5 — your local institutions
206
- ['UCF', 5, /^(ucf|university of central florida)\b/],
257
+ // 10 — the largest tech platforms and the frontier AI labs
258
+ ['Google', 10, /^(google|deepmind)\b|^alphabet$/, 'tech'],
259
+ ['Apple', 10, /^apple( inc)?$/, 'tech'],
260
+ ['Microsoft', 10, /^microsoft\b/, 'tech'],
261
+ ['Amazon', 10, /^(amazon|aws|amazon web services)\b(?! (conservation|watch)\b)/, 'tech'],
262
+ ['Meta', 10, /^meta( platforms)?$|^(facebook|instagram|whatsapp|reality labs)\b/, 'tech'],
263
+ ['NVIDIA', 10, /^nvidia\b/, 'tech'],
264
+ ['OpenAI', 10, /^(openai|open ai)\b/, 'tech'],
265
+ ['Anthropic', 10, /^anthropic\b/, 'tech'],
266
+ ['Tesla', 10, /^tesla( motors| inc)?$/, 'industry'],
267
+ // 9 — elite: the most sought-after employers in tech, finance, consulting,
268
+ // investing and consumer brands
269
+ ['Netflix', 9, /^netflix\b/, 'entertainment'],
270
+ ['YouTube', 9, /^youtube\b/, 'media'],
271
+ ['LinkedIn', 9, /^linkedin\b/, 'tech'],
272
+ ['TikTok', 9, /^(tiktok|bytedance)\b/, 'media'],
273
+ ['SpaceX', 9, /^spacex\b/, 'defense'],
274
+ ['Stripe', 9, /^stripe\b/, 'finance'],
275
+ ['Palantir', 9, /^palantir\b/, 'tech'],
276
+ ['Coca-Cola', 9, /^(the )?coca[-\s]cola( company| north america)?$/, 'consumer'],
277
+ ['Disney', 9, /^(the walt |walt )?disney\b/, 'entertainment'],
278
+ ['Goldman Sachs', 9, /^goldman sachs\b|^goldman$/, 'finance'],
279
+ ['JPMorgan Chase', 9, /^(jp ?morgan|j\.p\. morgan|jpmorgan chase)\b|^chase( bank| home lending| card services| auto| for business)?$/, 'finance'],
280
+ ['Morgan Stanley', 9, /^morgan stanley\b/, 'finance'],
281
+ ['BlackRock', 9, /^blackrock\b/, 'finance'],
282
+ ['McKinsey', 9, /^mckinsey\b/, 'consulting'],
283
+ ['BCG', 9, /^(bcg|boston consulting group)\b/, 'consulting'],
284
+ ['Bain', 9, /^bain( & company| and company)?$/, 'consulting'],
285
+ // A private equity firm, not Bain & Company: its own entry (so its own
286
+ // industry), at the 9 it had when it was an alias of Bain.
287
+ ['Bain Capital', 9, /^bain capital$/, 'finance'],
288
+ ['Procter & Gamble', 9, /^(p&g|procter)\b/, 'consumer'],
289
+ ['Sequoia', 9, /^sequoia( capital)?$/, 'finance'],
290
+ ['Andreessen Horowitz', 9, /^(a16z|andreessen)\b/, 'finance'],
291
+ ['Y Combinator', 9, /^(y combinator|yc)$/, 'finance'],
292
+ // The private equity firms in the Fortune 500, peers of Bain Capital. Apollo
293
+ // only by its full name: Apollo Hospitals, Apollo.io and others share "Apollo".
294
+ ['Blackstone', 9, /^blackstone( group)?$/, 'finance'], ['KKR', 9, /^(kkr|kohlberg kravis roberts)\b/, 'finance'],
295
+ ['Apollo Global Management', 9, /^apollo( global( management)?| management)$/, 'finance'],
296
+ // 8 — major companies and institutions
297
+ ['Salesforce', 8, /^salesforce\b/, 'tech'], ['Adobe', 8, /^adobe( systems)?$/, 'tech'], ['Oracle', 8, /^oracle( america| health| netsuite| cloud( infrastructure)?| financial services( software)?)?$/, 'tech'], ['IBM', 8, /^ibm\b/, 'tech'],
298
+ ['Uber', 8, /^uber( technologies| eats)?$/, 'tech'], ['Airbnb', 8, /^airbnb\b/, 'tech'], ['DoorDash', 8, /^doordash\b/, 'tech'], ['Spotify', 8, /^spotify\b/, 'entertainment'],
299
+ ['Snap', 8, /^(snap|snapchat)( inc)?$/, 'media'], ['Pinterest', 8, /^pinterest\b/, 'media'], ['Reddit', 8, /^reddit\b/, 'media'], ['X', 8, /^(x corp|twitter)\b/, 'media'],
300
+ ['Roblox', 8, /^roblox\b/, 'entertainment'], ['Shopify', 8, /^shopify\b(?! (plus )?partners?\b)/, 'consumer'], ['Snowflake', 8, /^snowflake( computing)?$/, 'tech'], ['Datadog', 8, /^datadog\b/, 'tech'],
301
+ ['CrowdStrike', 8, /^crowdstrike\b/, 'tech'], ['ServiceNow', 8, /^servicenow\b/, 'tech'], ['Canva', 8, /^canva\b/, 'tech'],
302
+ ['Intuit', 8, /^intuit\b/, 'tech'], ['Cisco', 8, /^cisco\b/, 'tech'], ['Qualcomm', 8, /^qualcomm\b/, 'tech'], ['AMD', 8, /^amd\b/, 'tech'],
303
+ ['Samsung', 8, /^samsung\b/, 'tech'], ['Sony', 8, /^sony\b/, 'entertainment'], ['Epic Games', 8, /^epic games\b/, 'entertainment'],
304
+ ['Coinbase', 8, /^coinbase\b/, 'finance'], ['Robinhood', 8, /^robinhood\b/, 'finance'], ['Block', 8, /^(block|square|cash app)$/, 'finance'],
305
+ ['Visa', 8, /^visa( inc)?$/, 'finance'], ['Mastercard', 8, /^mastercard\b/, 'finance'], ['American Express', 8, /^(american express|amex)\b/, 'finance'],
306
+ ['Capital One', 8, sponsor('capital one'), 'finance'], ['BNY', 8, /^(bny|bny mellon|bank of new york)\b/, 'finance'], ['Citi', 8, /^(citi( private bank)?|citigroup|citibank)$/, 'finance'],
307
+ ['Wells Fargo', 8, sponsor('wells fargo'), 'finance'], ['Bank of America', 8, new RegExp(String.raw`^bank of america\b${VENUE}|^merrill( lynch( wealth management)?| edge)?$`), 'finance'], ['Fidelity', 8, /^fidelity( investments| institutional| management (&|and) research( company)?)?$/, 'finance'],
308
+ ['Deloitte', 8, /^deloitte\b/, 'consulting'], ['PwC', 8, /^(pwc|pricewaterhouse)\b/, 'consulting'], ['EY', 8, /^(ey|ernst & young)$/, 'consulting'], ['KPMG', 8, /^kpmg\b/, 'consulting'],
309
+ ['Accenture', 8, /^accenture\b/, 'consulting'],
310
+ ['Nike', 8, /^nike\b/, 'consumer'], ['PepsiCo', 8, /^pepsico\b|^pepsi( beverages( company)?| cola( company)?)?$/, 'consumer'], ['Walmart', 8, /^walmart\b/, 'consumer'], ['Starbucks', 8, /^starbucks\b/, 'consumer'],
311
+ ['Unilever', 8, /^unilever\b/, 'consumer'], ['LVMH', 8, /^lvmh\b/, 'consumer'], ["L'Oréal", 8, /^l'?or[eé]al\b/, 'consumer'], ['Red Bull', 8, sponsor('red bull'), 'consumer'],
312
+ ['NBCUniversal', 8, /^(nbcuniversal|nbc universal|nbc)\b/, 'entertainment'], ['Warner Bros. Discovery', 8, /^(warner (bros|brothers)( discovery| pictures| entertainment| television( studios)?| games| animation| studios)?|warner ?media|wbd)$/, 'entertainment'],
313
+ ['Lockheed Martin', 8, /^lockheed\b/, 'defense'], ['Northrop Grumman', 8, /^northrop\b/, 'defense'], ['Boeing', 8, /^boeing\b/, 'defense'], ['RTX', 8, /^(rtx|raytheon)\b/, 'defense'],
314
+ ['General Dynamics', 8, /^general dynamics\b/, 'defense'], ['Blue Origin', 8, /^blue origin\b/, 'defense'], ['Siemens', 8, /^siemens\b(?! (energy|healthineers|gamesa)\b)/, 'industry'],
315
+ // Mitsubishi's makers, not the group's bank, lender or landlord (MUFG, Mitsubishi HC Capital, Mitsubishi Estate).
316
+ ['Mitsubishi', 8, /^mitsubishi\b(?! (ufj|hc capital|estate)\b)/, 'industry'], ['Toyota', 8, /^toyota( motor( north america| corporation| manufacturing( [a-z]+){0,2}| sales( usa)?)?| north america| financial services| research institute( of north america)?)?$/, 'industry'], ['Johnson & Johnson', 8, /^johnson (&|and) johnson\b|^j&j$/, 'health'],
317
+ ['Pfizer', 8, /^pfizer\b/, 'health'], ['UnitedHealth', 8, /^(unitedhealth(care)?|united ?health ?care|united health group|optum(rx|care|health|insight|serve)?)\b/, 'health'], ['World Bank', 8, /^world bank\b/, 'finance'],
318
+ ['NASA', 8, /^nasa\b(?! federal credit union\b)/, 'defense'], ['Stanford University', 8, university('stanford', '| gsb'), 'education'], ['Harvard University', 8, university('harvard', '| college'), 'education'], ['MIT', 8, /^(mit|massachusetts institute of technology)$/, 'education'],
319
+ // Added from the sources in docs/brain/SCORING.md, at 8: the Fortune 100
320
+ // (2025) and companies as large, whether based elsewhere, private or
321
+ // nonprofit. Health insurers, health systems and drugmakers. Aetna is part
322
+ // of CVS Health but keeps its own name, as people do. Merck KGaA is a
323
+ // different company from Merck.
324
+ ['CVS Health', 8, /^(cvs|cvshealth)\b/, 'health'], ['Aetna', 8, /^aetna\b/, 'health'], ['Cigna', 8, /^cigna\b/, 'health'], ['Humana', 8, /^humana\b/, 'health'],
325
+ ['Elevance Health', 8, /^elevance\b|^anthem( blue cross( (and |& )?blue shield)?| inc| health plans)?$/, 'health'],
326
+ ['HCA Healthcare', 8, /^hca\b/, 'health'], ['Kaiser Permanente', 8, /^kaiser( (permanente|foundation (health plan|hospitals))\b|$)/, 'health'],
327
+ ['Merck', 8, /^merck\b(?! (kgaa|group|millipore)\b)/, 'health'], ['AbbVie', 8, /^abbvie\b/, 'health'], ['Eli Lilly', 8, /^eli lilly\b|^lilly( usa)?$/, 'health'],
328
+ ['Bristol Myers Squibb', 8, /^bristol[-\s]?myers\b/, 'health'], ['AstraZeneca', 8, /^astrazeneca\b/, 'health'],
329
+ ['Bayer', 8, /^bayer( ag| u\.?s\.?| crop ?science| pharmaceuticals| healthcare| consumer health)?$/, 'health'],
330
+ // Retail, grocery, food and drink. Lowes Foods is a different grocer.
331
+ ['Costco', 8, /^costco\b/, 'consumer'], ['The Home Depot', 8, sponsor('home depot'), 'consumer'], ['Kroger', 8, sponsor('kroger'), 'consumer'],
332
+ ['IKEA', 8, /^ikea\b/, 'consumer'], ['Aldi', 8, /^aldi\b/, 'consumer'], ['7-Eleven', 8, /^7[-\s]?eleven\b/, 'consumer'],
333
+ ['Walgreens', 8, /^walgreens?\b/, 'consumer'], ["Lowe's", 8, /^lowe['’]?s( companies| home improvement)?$/, 'consumer'],
334
+ ['Albertsons', 8, /^albertsons( companies)?$/, 'consumer'], ['TJX', 8, /^(tjx( companies)?|tj ?maxx)$/, 'consumer'],
335
+ ['Tyson Foods', 8, /^tyson( foods)?$/, 'consumer'], ['Nestlé', 8, /^nestl[eé](?![a-z])/, 'consumer'],
336
+ ['Anheuser-Busch', 8, /^(anheuser[-\s]busch|ab[-\s]?inbev)\b/, 'consumer'], ['Mars', 8, /^mars( inc| incorporated| wrigley| petcare| snacking)?$/, 'consumer'],
337
+ // Smaller than the Fortune 100, but 8 like Starbucks, the other restaurant company here.
338
+ ["McDonald's", 8, /^mcdonald['’]?s\b/, 'consumer'],
339
+ // Automakers, by the makers' own names only.
340
+ ['General Motors', 8, /^general motors\b/, 'industry'], ['Ford', 8, /^ford( motor( company| credit( company)?)?)?$/, 'industry'],
341
+ ['Volkswagen', 8, /^(volkswagen( group( of america)?| of america| ag)?|vw)$/, 'industry'],
342
+ ['Mercedes-Benz', 8, /^mercedes[-\s]benz( group| usa| ag| u\.s)?$/, 'industry'], ['BMW', 8, /^bmw( group| ag| of north america| manufacturing)?$/, 'industry'],
343
+ ['Honda', 8, /^(american )?honda( motor( company)?| north america| aircraft( company)?| r&d( americas)?)?$/, 'industry'],
344
+ ['Hyundai', 8, /^hyundai( motor( company| america| group| north america)?)?$/, 'industry'], ['Kia', 8, /^kia( motors?| america| motors america| georgia)?$/, 'industry'],
345
+ ['Nissan', 8, /^nissan( motor( company| corporation)?| north america| americas)?$/, 'industry'],
346
+ // Airlines, telecoms, energy and shipping. "Delta" alone is also Delta
347
+ // Dental and a sorority, so only the airline's full name matches.
348
+ ['Delta Air Lines', 8, /^delta (air lines|airlines)\b/, 'consumer'], ['United Airlines', 8, /^united airlines\b/, 'consumer'],
349
+ ['American Airlines', 8, sponsor('american airlines'), 'consumer'],
350
+ ['Verizon', 8, sponsor('verizon'), 'tech'], ['AT&T', 8, sponsor('at ?& ?t'), 'tech'], ['T-Mobile', 8, sponsor(String.raw`t[-\s]?mobile`), 'tech'],
351
+ ['Comcast', 8, sponsor('comcast|xfinity'), 'tech'],
352
+ ['ExxonMobil', 8, /^exxon( ?mobil)?\b/, 'industry'], ['Chevron', 8, /^chevron\b(?! phillips\b)/, 'industry'], ['ConocoPhillips', 8, /^conocophillips\b/, 'industry'],
353
+ ['Shell', 8, /^shell( oil( company)?| usa| energy| global solutions| trading)?$/, 'industry'], ['BP', 8, /^(bp( america| p\.l\.c)?|british petroleum)$/, 'industry'],
354
+ ['UPS', 8, /^ups( supply chain solutions| airlines| healthcare)?$|^united parcel service\b/, 'industry'], ['FedEx', 8, sponsor('fedex|federal express'), 'industry'],
355
+ ['DHL', 8, /^dhl\b/, 'industry'],
356
+ // Insurers and mortgage finance. Berkshire Hathaway by its own name only:
357
+ // its companies keep theirs (GEICO below; its HomeServices brokerages are
358
+ // franchises).
359
+ ['Berkshire Hathaway', 8, /^berkshire hathaway$/, 'finance'],
360
+ ['State Farm', 8, sponsor('state farm'), 'finance'], ['Allstate', 8, sponsor('allstate'), 'finance'], ['MetLife', 8, sponsor('metlife'), 'finance'],
361
+ ['Progressive', 8, /^progressive( insurance| casualty insurance( company)?)?$/, 'finance'],
362
+ ['Prudential Financial', 8, /^prudential( financial| insurance( company of america)?)?$/, 'finance'], ['New York Life', 8, /^new york life\b/, 'finance'],
363
+ ['Nationwide', 8, /^nationwide( insurance| mutual( insurance( company)?)?| financial)?$/, 'finance'], ['Liberty Mutual', 8, /^liberty mutual\b/, 'finance'],
364
+ ['USAA', 8, /^usaa\b/, 'finance'], ['Travelers', 8, /^travelers( insurance| companies)?$/, 'finance'],
365
+ ['Fannie Mae', 8, /^(fannie mae|federal national mortgage association)\b/, 'finance'], ['Freddie Mac', 8, /^(freddie mac|federal home loan mortgage)\b/, 'finance'],
366
+ // Computers and machinery. HP only: Hewlett Packard Enterprise is another company.
367
+ ['Dell', 8, /^dell( technologies| emc)?$/, 'tech'], ['HP', 8, /^(hp|hewlett[-\s]packard)( inc| company)?$/, 'tech'],
368
+ ['Caterpillar', 8, /^caterpillar\b/, 'industry'], ['John Deere', 8, /^(john deere|deere( (&|and) company)?)\b/, 'industry'],
369
+ // Nine of the ten largest US law firms by revenue (the Am Law 100, 2025),
370
+ // 8 like the Big Four, the largest firms in accounting.
371
+ ['Kirkland & Ellis', 8, /^kirkland (&|and) ellis\b/, 'consulting'], ['Latham & Watkins', 8, /^latham (&|and) watkins\b/, 'consulting'],
372
+ ['DLA Piper', 8, /^dla piper\b/, 'consulting'], ['Baker McKenzie', 8, /^baker (& )?mckenzie\b/, 'consulting'], ['Skadden', 8, /^skadden\b/, 'consulting'],
373
+ ['Gibson Dunn', 8, /^gibson dunn\b/, 'consulting'], ['Sidley Austin', 8, /^sidley( austin)?\b/, 'consulting'],
374
+ ['White & Case', 8, /^white (&|and) case\b/, 'consulting'], ['Ropes & Gray', 8, /^ropes (&|and) gray\b/, 'consulting'],
375
+ // The top five of U.S. News's Best National Universities (2025 edition),
376
+ // with Harvard, Stanford and MIT above. Yale New Haven Health is a hospital
377
+ // system of its own.
378
+ ['Princeton University', 8, university('princeton'), 'education'], ['Yale University', 8, university('yale', '| college'), 'education'],
379
+ // 7 — well-known companies and universities
380
+ ['Discord', 7, /^discord\b/, 'tech'], ['Paramount', 7, /^paramount( global| pictures| skydance| streaming| television( studios)?| media networks|\+)?$/, 'entertainment'], ['Madison Square Garden', 7, /^(madison square garden|msg)\b/, 'entertainment'],
381
+ ['Fanatics', 7, /^fanatics\b/, 'consumer'], ['Nielsen', 7, /^nielsen( holdings| company| media research| audience measurement)?$/, 'media'], ['The Trade Desk', 7, /^the trade desk\b/, 'media'], ['SiriusXM', 7, /^sirius ?xm\b/, 'entertainment'],
382
+ ['Twitch', 7, /^twitch\b/, 'entertainment'], ['Electronic Arts', 7, /^(electronic arts|ea)$/, 'entertainment'], ['Activision Blizzard', 7, /^(activision( blizzard)?|blizzard( entertainment)?)$/, 'entertainment'],
383
+ ['MrBeast', 7, /^(mrbeast|beast industries)$/, 'media'], ['Lyft', 7, /^lyft\b/, 'tech'], ['Instacart', 7, /^instacart\b/, 'tech'], ['Duolingo', 7, /^duolingo\b/, 'tech'],
384
+ ['Intel', 7, /^intel( corporation)?$/, 'tech'], ['Workday', 7, /^workday\b/, 'tech'], ['PayPal', 7, /^paypal\b/, 'finance'], ['Credit Karma', 7, /^credit karma\b/, 'finance'],
385
+ ['Chick-fil-A', 7, /^chick[-\s]fil[-\s]a\b/, 'consumer'], ['Target', 7, /^target( corporation)?$/, 'consumer'], ['Booz Allen Hamilton', 7, /^booz allen\b/, 'defense'],
386
+ ['L3Harris', 7, /^l3harris\b/, 'defense'], ['Leidos', 7, /^leidos\b/, 'defense'],
387
+ ['WPP', 7, /^wpp\b/, 'media'], ['Omnicom', 7, /^omnicom\b/, 'media'], ['Publicis', 7, /^publicis\b/, 'media'], ['Interpublic', 7, /^(interpublic|ipg)\b/, 'media'], ['Dentsu', 7, /^dentsu\b/, 'media'],
388
+ ['Kellanova', 7, /^(kellanova|kellogg['’]?s|kellogg( company)?)$/, 'consumer'], ['Campbell', 7, /^campbell( soup( company)?| company|['’]?s)?$/, 'consumer'], ['Goodyear', 7, /^goodyear\b/, 'industry'],
389
+ ['Duke University', 7, /^duke( university)?$/, 'education'], ['UC Berkeley', 7, /^(uc berkeley|university of california,? berkeley)\b/, 'education'],
390
+ // Added from the same sources: household names smaller than the Fortune
391
+ // 100. The best-known health systems, by their own names (Johns Hopkins
392
+ // Medicine apart from the university below).
393
+ ['Mayo Clinic', 7, /^mayo clinic\b/, 'health'], ['Cleveland Clinic', 7, /^cleveland clinic\b/, 'health'],
394
+ ['Johns Hopkins Medicine', 7, /^johns hopkins (medicine|hospital|health( system|care)|all children['’]?s|bayview|community physicians)\b/, 'health'],
395
+ ['Dollar General', 7, /^dollar general\b/, 'consumer'], ['Dollar Tree', 7, /^dollar tree\b/, 'consumer'],
396
+ ['Chipotle', 7, /^chipotle\b/, 'consumer'], ['Kraft Heinz', 7, /^kraft (heinz|foods)\b/, 'consumer'], ['General Mills', 7, /^general mills\b/, 'consumer'],
397
+ ['Marriott', 7, /^marriott( international| hotels( (&|and) resorts)?)?$/, 'consumer'], ['Hilton', 7, /^hilton( worldwide( holdings)?| hotels( (&|and) resorts)?)?$/, 'consumer'],
398
+ ['Southwest Airlines', 7, /^southwest airlines\b/, 'consumer'], ['Union Pacific', 7, /^union pacific\b/, 'industry'],
399
+ // Part of Berkshire Hathaway, named on its own.
400
+ ['GEICO', 7, /^(geico|government employees insurance)\b/, 'finance'],
401
+ // Fox Corporation reads as "Fox" once "Corporation" is trimmed, like any company name.
402
+ ['Fox', 7, /^fox( news( channel| media)?| sports| entertainment| broadcasting( company)?| television stations| business( network)?)?$/, 'entertainment'],
403
+ ['The New York Times', 7, /^new york times\b|^nyt$/, 'media'],
404
+ ['Bloomberg', 7, /^bloomberg( lp| l\.p| news| media| industry group| law| tax| government| intelligence)?$/, 'media'],
405
+ // Ranks 6 to 20 of U.S. News's Best National Universities (2025 edition),
406
+ // with Duke and UC Berkeley above. A school named apart from its university
407
+ // (Wharton, Kellogg, Booth) keeps its own name, as MIT Sloan does.
408
+ ['Caltech', 7, /^(caltech|california institute of technology)$/, 'education'],
409
+ ['Johns Hopkins University', 7, /^johns hopkins( university\b|$| (carey|bloomberg|whiting|krieger|peabody|sais)\b| school of\b)|^jhu$/, 'education'],
410
+ ['Northwestern University', 7, university('northwestern'), 'education'], ['University of Pennsylvania', 7, university('university of pennsylvania|upenn|penn'), 'education'],
411
+ ['Cornell University', 7, university('cornell', '| tech'), 'education'], ['University of Chicago', 7, university('university of chicago|uchicago'), 'education'],
412
+ ['Brown University', 7, university('brown'), 'education'], ['Columbia University', 7, university('columbia university|columbia(?= (business|law|journalism) school$)'), 'education'],
413
+ ['Dartmouth College', 7, university('dartmouth', '| college'), 'education'], ['UCLA', 7, /^(ucla|university of california,? los angeles)$|^ucla anderson\b/, 'education'],
414
+ ['Rice University', 7, university('rice university'), 'education'], ['University of Notre Dame', 7, university('university of notre dame|notre dame'), 'education'],
415
+ ['Vanderbilt University', 7, university('vanderbilt'), 'education'],
207
416
  ];
208
417
 
209
418
  const JUNK_COMPANY = /^(self[-\s]?employed|freelance|various|multiple|confidential|n\/a|none|tbd|open to work|looking|seeking)$/i;
210
- // "at scale", "at the intersection of…", "at heart": phrases, not employers.
211
- const NOT_A_COMPANY = /^(scale$|heart|large|night|home|work|speed|best|the intersection|intersection|the crossroads|the forefront|the core|every stage|all levels|your service)\b/i;
419
+ // "at scale", "at the intersection of…", "at heart", "at home", "at best",
420
+ // "at best-in-class companies": phrases, not employers. The Home Depot, Home
421
+ // Instead and Home Chef are employers, and so are Best Buy, Best Western and
422
+ // Best Friends Animal Society.
423
+ const NOT_A_COMPANY = /^(scale$|heart|large|night|home(?! (depot|instead|chef)\b)|work|speed|best(?=$|[-\s]in[-\s]class|\s(of|practices?)\b)|the intersection|intersection|the crossroads|the forefront|the core|every stage|all levels|your service)\b/i;
424
+ // Where a company's name stops: a sentence's end ("Acme Corp. We build…") or a
425
+ // comma, but not the full stop of an initial or a short form ("J.P. Morgan",
426
+ // "J. Crew", "T. Rowe Price", "U.S. Bank", "St. Jude Children's Research
427
+ // Hospital"), which used to cut the name to "J.P" or nothing.
428
+ const NAME_END = /(?<!\b(?:[a-z]|st|mt|ft))\.\s|,\s/i;
429
+ // A company's legal form, trimmed so "Acme, Inc." and "Acme" are one company.
430
+ const LEGAL_FORM = /,?\s+(inc|incorporated|llc|ltd|l\.l\.c|corp|corporation|co|gmbh|plc|pbc)\.?$/i;
431
+ // Names that are another company with their legal form than without it: Chase
432
+ // Corporation (coatings) isn't Chase the bank, nor Merrill Corporation
433
+ // (financial printing) Bank of America's Merrill. Kept whole, so no alias takes them.
434
+ const WHOLE_NAME = /^(chase|merrill) corp(oration)?$/i;
435
+ // A name that says it is a school only matches a school on the list: the
436
+ // aliases match from the start of a name, so "Kellogg School of Management"
437
+ // read as Kellanova, "Warner University" as Warner Bros., "Campbell
438
+ // University" as Campbell's and "Chase College of Law" as JPMorgan Chase.
439
+ const SCHOOL_NAME = /\b(school|college|university)\b/;
440
+ // A credential, a program or gig work on a listed company's platform, or a fan
441
+ // of it, is not a job there: "AWS Certified Solutions Architect", "Google
442
+ // Developer Expert", "Uber Driver", "Airbnb Superhost", "Twitch Streamer",
443
+ // "TikTok Shop Seller", "LinkedIn Top Voice", "Google Alum", "Disney Fan".
444
+ // People write those as the first part of a headline, where a listed name
445
+ // reads as their employer; none of the listed companies' own names says them.
446
+ const NOT_ITS_STAFF = /\b(certified|certifications?|mvps?|champions?|experts?|ambassadors?|developers?|instructors?|trainers?|moderators?|superhosts?|hosts?|drivers?|dashers?|couriers?|shoppers?|sellers?|fba|kdp|dsp|franchisees?|streamers?|affiliates?|creators?|influencers?|athletes?|scholars?|shareholders?|alum|alumni|alumnus|alumna|alumnae|top voice|users? groups?|for startups|fans?|enthusiasts?|lovers?|nerds?|geeks?|junkies?|buffs?|addicts?|aficionados?|10,?000 (small businesses|women)|10ksb)\b/;
447
+ // A tech or media platform's partners, consultants and admins are other
448
+ // companies' staff ("Google Premier Partner", "Salesforce Consultant",
449
+ // "Workday Admin"). At a bank, a firm or a shop they work there ("Deloitte
450
+ // Partner", "Starbucks Partner"), so this reads only against those industries.
451
+ const PLATFORM_ROLE = /\b(partners?|consultants?|admins?|administrators?)\b/;
452
+ const PLATFORMS = new Set(['tech', 'media', 'entertainment']);
212
453
 
213
454
  /** A company name as people write it → one clean, canonical name (or null). */
214
455
  export function cleanCompany(raw) {
215
456
  let s = String(raw || '')
216
457
  .replace(/[\p{Extended_Pictographic}\u{FE0F}‍]/gu, '')
217
458
  .split(/\s*(?:\||•|·| – | — | - |!|\(|\/| {2,})\s*/)[0]
218
- .split(/\.\s|,\s/)[0]
219
- .replace(/[\s,.:;'"]+$/, '')
220
- .replace(/,?\s+(inc|llc|ltd|l\.l\.c|corp|corporation|co|gmbh|plc|pbc)\.?$/i, '')
221
- .trim();
459
+ .split(NAME_END)[0]
460
+ .replace(/[\s,.:;'"]+$/, '');
461
+ if (!WHOLE_NAME.test(s)) s = s.replace(LEGAL_FORM, '');
462
+ s = s.trim();
222
463
  if (!s || s.length < 2 || JUNK_COMPANY.test(s) || NOT_A_COMPANY.test(s)) return null;
223
464
  if (s.length > 50 || s.split(/\s+/).length > 6 || /\$|\d+%|\d+[kmb]\+/i.test(s)) return null;
224
465
  const lower = s.toLowerCase();
225
466
  const bare = lower.replace(/^the /, '');
226
- for (const [name, , alias] of KNOWN_COMPANIES) if (alias.test(lower) || alias.test(bare) || lower === name.toLowerCase()) return name;
467
+ const school = SCHOOL_NAME.test(lower);
468
+ const notStaff = NOT_ITS_STAFF.test(bare);
469
+ const platformRole = PLATFORM_ROLE.test(bare);
470
+ for (const [name, , alias, industry] of KNOWN_COMPANIES) {
471
+ if (school && industry !== 'education') continue;
472
+ if (lower === name.toLowerCase()) return name;
473
+ if (notStaff || (platformRole && PLATFORMS.has(industry))) continue;
474
+ if (alias.test(lower) || alias.test(bare)) return name;
475
+ }
227
476
  return s;
228
477
  }
229
478
 
230
479
  const KNOWN_BY_NAME = new Map(KNOWN_COMPANIES.map(([name, score]) => [name, score]));
480
+ const KNOWN_INDUSTRY = new Map(KNOWN_COMPANIES.map(([name, , , industry]) => [name, industry]));
231
481
 
232
482
  const isKnownCompany = (text) => {
233
483
  const c = cleanCompany(text);
@@ -262,6 +512,149 @@ function companyIn(part = '') {
262
512
  return m ? cleanCompany(m[1]) : null;
263
513
  }
264
514
 
515
+ // ── industry ─────────────────────────────────────────────────────────────────
516
+
517
+ export const UNCLEAR = 'unknown';
518
+
519
+ /** The curated list's industry for a company, by its name or any alias of it. */
520
+ export function knownIndustry(name) {
521
+ if (!name) return null;
522
+ return KNOWN_INDUSTRY.get(name) || KNOWN_INDUSTRY.get(cleanCompany(name)) || null;
523
+ }
524
+
525
+ /** The answer most of them give, ignoring "unclear"; a tie stays unclear. */
526
+ function mostSaid(answers = []) {
527
+ const counts = new Map();
528
+ for (const a of answers) if (a && a !== UNCLEAR) counts.set(a, (counts.get(a) || 0) + 1);
529
+ let best = UNCLEAR;
530
+ let top = 0;
531
+ let tied = false;
532
+ for (const [key, n] of counts) {
533
+ if (n > top) { best = key; top = n; tied = false; } else if (n === top) tied = true;
534
+ }
535
+ return tied ? UNCLEAR : best;
536
+ }
537
+
538
+ /**
539
+ * One industry per company, so its colour in Paths, its row in Scores and any
540
+ * sector lean agree. In order: the curated list's own industry; what the name
541
+ * says; otherwise what most of the people who work there now say in their
542
+ * headlines. A tie stays unclear rather than guessed, which also keeps the
543
+ * answer the same whichever order the rows arrive in (the server and Paths
544
+ * read them in different orders).
545
+ *
546
+ * `industryOf(company, headline)` returns an industry key; it is
547
+ * lib/companies.js's inference, passed in so this file needs no imports.
548
+ * Without it only the curated list can answer.
549
+ */
550
+ export function companyIndustry(name, options) {
551
+ return industryAndSource(name, options).industry;
552
+ }
553
+
554
+ /**
555
+ * companyIndustry, with where the answer came from: 'list' (the curated
556
+ * list), 'name' (the company's name) or 'people' (their headlines), or null
557
+ * when it is unclear. Your sector's broad picks count only the first two
558
+ * (leanToward): the people's vote is over job words every kind of company
559
+ * employs, so "Recruiter at Acme Widgets" alone would put Acme in consulting.
560
+ * @returns { industry, from }
561
+ */
562
+ export function industryAndSource(name, { industryOf, headlines = [] } = {}) {
563
+ if (!name) return { industry: UNCLEAR, from: null };
564
+ const known = knownIndustry(name);
565
+ if (known) return { industry: known, from: 'list' };
566
+ if (!industryOf) return { industry: UNCLEAR, from: null };
567
+ const byName = industryOf(name, null);
568
+ if (byName && byName !== UNCLEAR) return { industry: byName, from: 'name' };
569
+ const voted = mostSaid(headlines.map((h) => industryOf(null, h)));
570
+ return { industry: voted, from: voted === UNCLEAR ? null : 'people' };
571
+ }
572
+
573
+ /** The broad industries a company's directory sectors sit under, each once, in their order. */
574
+ const industriesUnder = (keys, groupOf) => [...new Set(keys.map(groupOf).filter(Boolean))];
575
+
576
+ /**
577
+ * Per company, from the people who work there now (each person once): how
578
+ * many there are, and the company's one industry. `rolesOf` lets a caller
579
+ * that also reads each row's roles (Paths) parse every headline once.
580
+ * `sectorsOf(name, headlines)` is Settings' sector directory
581
+ * (lib/sector-directory.js sectorMatcher), passed in like `industryOf`; with
582
+ * it, each company also gets the directory's sectors it matches, and with
583
+ * `groupOf(sector key)` (lib/sector-directory.js sectorGroup) the broad
584
+ * industries those sectors sit under.
585
+ * @returns { headcount: Map name → n, industries: Map name → industry key,
586
+ * industryFrom: Map name → 'list' | 'name' | 'people' | null (industryAndSource),
587
+ * sectors: Map name → sector keys, sectorIndustries: Map name → industry keys }
588
+ */
589
+ export function networkCompanies(rows = [], { industryOf, sectorsOf, groupOf, rolesOf = rolesWithCompanies } = {}) {
590
+ const at = new Map();
591
+ const seen = new Set();
592
+ for (const r of rows) {
593
+ for (const role of rolesOf(r)) {
594
+ const key = `${role.company}|${r.profile_url || r.id}`;
595
+ if (!role.company || role.former || seen.has(key)) continue;
596
+ seen.add(key);
597
+ const c = at.get(role.company) || { n: 0, headlines: [] };
598
+ c.n++;
599
+ c.headlines.push(r.headline);
600
+ at.set(role.company, c);
601
+ }
602
+ }
603
+ const headcount = new Map();
604
+ const industries = new Map();
605
+ const industryFrom = new Map();
606
+ const sectors = new Map();
607
+ const sectorIndustries = new Map();
608
+ for (const [name, c] of at) {
609
+ headcount.set(name, c.n);
610
+ const { industry, from } = industryAndSource(name, { industryOf, headlines: c.headlines });
611
+ industries.set(name, industry);
612
+ industryFrom.set(name, from);
613
+ if (!sectorsOf) continue;
614
+ const keys = sectorsOf(name, c.headlines);
615
+ sectors.set(name, keys);
616
+ if (groupOf) sectorIndustries.set(name, industriesUnder(keys, groupOf));
617
+ }
618
+ return { headcount, industries, industryFrom, sectors, sectorIndustries };
619
+ }
620
+
621
+ // ── your sector ──────────────────────────────────────────────────────────────
622
+
623
+ // Points a company in one of your sectors gets on top of its score (Settings →
624
+ // Your sector). A point of company score is worth 0.055 × title points: +0.55
625
+ // for a founder, +0.41 for a director, +0.22 for an IC.
626
+ export const SECTOR_BONUS = { lean: 1, strong: 2 };
627
+
628
+ /**
629
+ * A company score leaned toward your sectors: `focus` is { sectors, strength:
630
+ * 'lean' | 'strong' } as saved in Settings, where a sector is a broad industry
631
+ * key or a key from the sector directory. A directory sector matches when it
632
+ * is among the company's `sectors` (the directory's matches, injected). A
633
+ * broad industry includes its sectors: it matches any industry the company's
634
+ * sectors sit under (`sectorIndustries`), so Healthcare & Biotech picks up a
635
+ * practice only the directory calls dental, and the company's one `industry`
636
+ * when the curated list or its name gave it (`industryFrom`). An industry
637
+ * voted by its people's headlines doesn't count: the vote is over job words
638
+ * every kind of company employs (recruiter, attorney, engineer, data), so a
639
+ * lone "Recruiter at Acme Widgets" would make Acme consulting. The
640
+ * directory's sectors, which don't read a job for work every company has, are
641
+ * what count for such a company. However many match, the bonus is added once;
642
+ * `sector` names the pick that matched, a directory sector before a broad
643
+ * industry. Capped at 10. The extra fields appear only when the score actually
644
+ * moved, so everything that reads a plain { score, source } keeps working.
645
+ */
646
+ function leanToward(base, industry, industryFrom, sectors, sectorIndustries, focus) {
647
+ const bonus = SECTOR_BONUS[focus?.strength] || 0;
648
+ if (!bonus || !Array.isArray(focus.sectors)) return base;
649
+ const byIndustry = industryFrom !== 'people';
650
+ const pick = (sectors?.length && focus.sectors.find((k) => sectors.includes(k)))
651
+ || focus.sectors.find((k) => (byIndustry && k === industry) || sectorIndustries?.includes(k));
652
+ if (!pick) return base;
653
+ const score = Math.min(10, base.score + bonus);
654
+ if (score === base.score) return base;
655
+ return { ...base, score, base: base.score, sector: pick, sectorBonus: score - base.score };
656
+ }
657
+
265
658
  /**
266
659
  * A company's score and where it came from.
267
660
  * yours you set it
@@ -269,40 +662,108 @@ function companyIn(part = '') {
269
662
  * network unknown to the list; nudged up when several of your people work there
270
663
  * default unknown, and few of your people there
271
664
  * none no company found for this person
665
+ * With a sector focus, a company in one of your sectors gets +1 or +2 on top
666
+ * ({ base, sector, sectorBonus } say so). A score you set is never changed.
667
+ * `industry` is the company's one industry (companyIndustry); a curated
668
+ * company's own is used when none is given. `industryFrom` says where it
669
+ * came from (industryAndSource): one from its people's headlines doesn't lean
670
+ * it (leanToward). `sectors` are the sector directory's keys it matches
671
+ * (lib/sector-directory.js), when known, and `sectorIndustries` the broad
672
+ * industries those sit under. companyScoreIn() passes all of these from a
673
+ * read of the network.
272
674
  */
273
- export function companyScore(name, { overrides = new Map(), headcount = 0, industry } = {}) {
675
+ export function companyScore(name, { overrides = new Map(), headcount = 0, industry, industryFrom, sectors, sectorIndustries, focus } = {}) {
274
676
  if (!name) return { score: 3, source: 'none' };
275
677
  if (overrides.has(name)) return { score: overrides.get(name), source: 'yours' };
276
- if (KNOWN_BY_NAME.has(name)) return { score: KNOWN_BY_NAME.get(name), source: 'known' };
277
- if (industry !== 'education' && headcount >= 15) return { score: 6, source: 'network' };
278
- if (industry !== 'education' && headcount >= 5) return { score: 5, source: 'network' };
279
- return { score: 4, source: 'default' };
678
+ const ind = industry ?? KNOWN_INDUSTRY.get(name);
679
+ let base;
680
+ if (KNOWN_BY_NAME.has(name)) base = { score: KNOWN_BY_NAME.get(name), source: 'known' };
681
+ else if (ind !== 'education' && headcount >= 15) base = { score: 6, source: 'network' };
682
+ else if (ind !== 'education' && headcount >= 5) base = { score: 5, source: 'network' };
683
+ else base = { score: 4, source: 'default' };
684
+ return focus ? leanToward(base, ind, industryFrom, sectors, sectorIndustries, focus) : base;
685
+ }
686
+
687
+ /**
688
+ * A company's score from a read of the network (readNetwork): its headcount,
689
+ * industry and where that came from, and its sectors, as scoreNetwork scores
690
+ * it. Rescoring, Settings' preview and Paths → Scores all score a company
691
+ * through this, so they give it the same score.
692
+ */
693
+ export function companyScoreIn(read, name, { overrides, focus } = {}) {
694
+ const c = name ? read?.companies.get(name) : null;
695
+ return companyScore(name, {
696
+ overrides, headcount: c?.headcount || 0, industry: c?.industry, industryFrom: c?.industryFrom,
697
+ sectors: c?.sectors, sectorIndustries: c?.sectorIndustries, focus,
698
+ });
280
699
  }
281
700
 
282
701
  // ── reach bonus ──────────────────────────────────────────────────────────────
283
702
 
703
+ // The top honours of every field, so recognition doesn't depend on which field
704
+ // someone is in: science, economics, peace and literature (Nobel), journalism
705
+ // (Pulitzer), broadcasting (Peabody), TV (Emmy), music (Grammy), film (Oscar),
706
+ // theatre (Tony), the web (Webby), advertising (Clio, Cannes Lions), food
707
+ // (James Beard), computing (Turing), mathematics (Fields), architecture
708
+ // (Pritzker), sport (the Olympics and Paralympics), and the MacArthur and
709
+ // Rhodes. An award's name alone can be a company's, a person's or its own
710
+ // organisation's ("Oscar Health", "Peabody Energy", Clio the legal software,
711
+ // "Tony", "Emmy's Bakery", "the Peabody Awards", "Pulitzer Center"), so those
712
+ // count only as claimed: "… Award", "…-winning", "… winner", "… nominee".
713
+ // docs/brain/SCORING.md says what the list replaced.
714
+ const HONOURS = [
715
+ String.raw`nobel (prize|laureate)`, String.raw`pulitzer( prize|[-\s](winning|winner|finalist))`,
716
+ String.raw`(emmy|grammy|oscar|tony|peabody|clio|james beard|turing)[-\s](award|winning|winner|nominated|nominee|honoree)`,
717
+ 'academy award', 'webby', 'cannes lions', 'fields medal', 'pritzker prize', 'macarthur fellow', 'rhodes scholar',
718
+ String.raw`(olympi|paralympi)(an|c (gold|silver|bronze|medalist|champion))`,
719
+ ].join('|');
720
+
284
721
  const BONUSES = [
285
722
  [/\b(angel investor|investor|venture capital(ist)?|vc|general partner|limited partner)\b(?! relations)/i, 1, 'investor'],
286
723
  [/\by ?combinator\b|\(yc\)|\byc\b ?[wsfx]?\d{0,2}\b/i, 1, 'YC'],
287
724
  [/\b30 under 30\b/i, 1, '30 under 30'],
288
725
  [/\b\d[\d.,]*\s?(m|b|million|billion)\+?\s*(views|followers|subscribers|impressions|users|downloads|installs|listeners|students|customers|members|reach)\b|\$?\s?\d[\d.,]*\s?(m|b|million|billion)\+?\s*(in )?(sales|revenue|arr|gmv|raised|funding)\b|\braised \$?\s?\d[\d.,]*\s?(m|b|million|billion)\b/i, 0.75, 'reach in the millions'],
289
- [/\b(tedx?|keynote|patents?|author of|best[-\s]?selling|award[-\s]winning|cannes lions|emmy|grammy|webby)\b/i, 0.5, 'recognition'],
726
+ [new RegExp(String.raw`\b(tedx?|keynote|patents?|author of|best[-\s]?selling|award[-\s]winning|prize[-\s]winning|${HONOURS})\b`, 'i'), 0.5, 'recognition'],
290
727
  ];
291
728
 
729
+ /** The headline's reach signals before any halving: points (capped at 1.5) and reasons. */
730
+ function readReach(headline = '') {
731
+ let points = 0;
732
+ const reasons = [];
733
+ for (const [re, p, label] of BONUSES) if (re.test(headline || '')) { points += p; reasons.push(label); }
734
+ return { points: Math.min(1.5, points), reasons };
735
+ }
736
+
292
737
  /**
293
738
  * Bonus points from signals in the headline, capped at 1.5, with the reasons.
294
739
  * Headlines are self-written: claims from someone at a company nobody knows
295
- * (company score 4 or less) count half.
740
+ * (company score 4 or less) count half. `found` is the headline's readReach(),
741
+ * when it has been read already.
296
742
  */
297
- export function reachBonus(headline = '', companyScoreValue = 10) {
298
- let points = 0;
299
- const reasons = [];
300
- for (const [re, p, label] of BONUSES) if (re.test(headline || '')) { points += p; reasons.push(label); }
301
- points = Math.min(1.5, points);
743
+ export function reachBonus(headline = '', companyScoreValue = 10, found = readReach(headline)) {
744
+ let { points } = found;
745
+ const reasons = [...found.reasons];
302
746
  if (points && companyScoreValue <= 4) { points /= 2; reasons.push('halved: unknown company'); }
303
747
  return { points: round1(points), reasons };
304
748
  }
305
749
 
750
+ // ── what stored scores were read with ────────────────────────────────────────
751
+
752
+ // Every table of words and rules above that reads a headline or a company
753
+ // name: the title ladder and its rules, the student, club and former rules,
754
+ // what isn't a company or a job there, and the reach bonuses. lib/rpc.js
755
+ // fingerprints them, with the curated list and the industries' words, into
756
+ // the stamp stored scores carry, so editing any of them rescores stored scores
757
+ // once, with no SCORING_VERSION to remember. How they're combined (the
758
+ // formula, the weights, the order of the steps) is still a SCORING_VERSION.
759
+ export const RULE_TABLES = {
760
+ levels: LEVELS, titles: TITLE_RULES, intern: INTERN, student: STUDENT, major: MAJOR, shortName: SHORT_NAME,
761
+ atShortName: AT_SHORT_NAME, notASchool: NOT_A_SCHOOL, studentOrg: STUDENT_ORG, schoolWordClub: SCHOOL_WORD_CLUB,
762
+ club: CLUB, shortNameClub: SHORT_NAME_CLUB, fieldAssociation: FIELD_ASSOCIATION, grownUp: GROWN_UP, former: FORMER,
763
+ junkCompany: JUNK_COMPANY, notACompany: NOT_A_COMPANY, nameEnd: NAME_END, legalForm: LEGAL_FORM, wholeName: WHOLE_NAME,
764
+ schoolName: SCHOOL_NAME, notItsStaff: NOT_ITS_STAFF, platformRole: PLATFORM_ROLE, platforms: PLATFORMS, bonuses: BONUSES,
765
+ };
766
+
306
767
  // ── putting it together ──────────────────────────────────────────────────────
307
768
 
308
769
  // S is the top ~3–4%: VP and up at strong companies, founders and C-suite at
@@ -320,28 +781,125 @@ const round1 = (n) => Math.round(n * 10) / 10;
320
781
  export const FORMER_WEIGHT = 0.7;
321
782
  export const STUDENT_CAP = 3;
322
783
 
784
+ /**
785
+ * What scoring reads from one person's row: every role with its company,
786
+ * whether they are a student now, and the headline's reach signals. Reading
787
+ * headlines is most of the work, so a network read once (readNetwork) can be
788
+ * scored several ways: Settings' preview scores it with two sector focuses.
789
+ */
790
+ export function readPerson(row) {
791
+ return { roles: rolesWithCompanies(row), student: isStudent(row?.headline), reach: readReach(row?.headline) };
792
+ }
793
+
323
794
  /**
324
795
  * One person's score, before any bridge boost: their strongest role, where a
325
796
  * role is worth title × company weight. Former roles count at 70% — an ex-SVP
326
797
  * still knows the people an SVP knows. A current student is capped at 3 title
327
- * points whatever else they list. `companyFor(name)` returns companyScore().
798
+ * points whatever else they list. `companyFor(name)` returns companyScore();
799
+ * `read` is the row's readPerson(), when it has been read already.
328
800
  */
329
- export function scorePerson(row, companyFor = (name) => companyScore(name)) {
330
- const student = isStudent(row?.headline);
801
+ /** A role's title points as they count: 70% for a former role, capped for a student. */
802
+ function rolePoints(role, student) {
803
+ const points = role.title.points * (role.former ? FORMER_WEIGHT : 1);
804
+ return student ? Math.min(points, STUDENT_CAP) : points;
805
+ }
806
+
807
+ export function scorePerson(row, companyFor = (name) => companyScore(name), read = readPerson(row)) {
808
+ const { roles, student, reach } = read;
331
809
  let best = null;
332
- for (const role of rolesWithCompanies(row)) {
810
+ for (const role of roles) {
333
811
  const co = companyFor(role.company);
334
- let points = role.title.points * (role.former ? FORMER_WEIGHT : 1);
335
- if (student) points = Math.min(points, STUDENT_CAP);
812
+ const points = rolePoints(role, student);
336
813
  const core = points * (0.45 + 0.055 * co.score);
337
814
  if (!best || core > best.core) best = { role, co, points, core };
338
815
  }
339
- const bonus = reachBonus(row?.headline, best.co.score);
816
+ // Halved or not by the company's own score, before any sector lean: that you
817
+ // like a sector says nothing about whether a self-written claim is true.
818
+ const bonus = reachBonus(row?.headline, best.co.base ?? best.co.score, reach);
340
819
  const power = round1(best.core + bonus.points);
341
820
  const title = { ...best.role.title, former: best.role.former, student, points: round1(best.points) };
342
821
  if (best.role.former) title.label = `Former ${title.label}`;
343
822
  if (student && best.role.title.level > 0) title.label = `Student · ${title.label}`;
344
- return { title, company: best.role.company, companyScore: best.co.score, companySource: best.co.source, bonus, power, tier: tierFor(power) };
823
+ const out = { title, company: best.role.company, companyScore: best.co.score, companySource: best.co.source, bonus, power, tier: tierFor(power) };
824
+ // Only when your sector moved the company's score, so explainScore can show it.
825
+ if (best.co.sectorBonus) out.companySector = { key: best.co.sector, base: best.co.base, bonus: best.co.sectorBonus };
826
+ return out;
827
+ }
828
+
829
+ // ── for the views ────────────────────────────────────────────────────────────
830
+ // Views that rank or describe people by where they work (the Queue's order,
831
+ // the person panel's notes) read the model's own company scores through these,
832
+ // so none keeps a list of famous names of its own.
833
+
834
+ // A company scored this high is a top company wherever a view says so: the
835
+ // curated list's major companies and up, or any company your own score or
836
+ // your sector puts there.
837
+ export const TOP_COMPANY = 8;
838
+
839
+ /**
840
+ * The company score a row's power was computed with: the row's stored
841
+ * company_prestige_score (a score you set, the curated list's or the
842
+ * network's estimate, and any sector lean), or companyScore()'s for a row not
843
+ * scored yet, which only the curated list can raise.
844
+ */
845
+ export function rowCompanyScore(row, read) {
846
+ const stored = Number(row?.company_prestige_score);
847
+ return stored > 0 ? stored : scorePerson(row, undefined, read ?? readPerson(row)).companyScore;
848
+ }
849
+
850
+ /**
851
+ * The company someone's score is built on, their strongest role's, with its
852
+ * score (rowCompanyScore) and whether that role is a former one. A scored row
853
+ * says which role it was by its title points (seniority_score): a former role
854
+ * counts at 70%, so it never has a current role's points. Two roles with the
855
+ * same points go to the one the stored working names (score_why, "… · Beta
856
+ * Labs (9/10, your score)"), since a score you set or your sector can make
857
+ * either the stronger; then to the one whose company the list scores as
858
+ * stored, else the first, as scorePerson keeps the first of equals. A row not
859
+ * scored yet, or whose headline no longer has those points, goes by
860
+ * scorePerson with the list alone.
861
+ * @returns { name, score, former } — name is null when no company was found
862
+ */
863
+ export function scoredCompany(row, read = readPerson(row)) {
864
+ const stored = Number(row?.company_prestige_score);
865
+ if (stored > 0) {
866
+ const points = Number(row?.seniority_score);
867
+ const same = read.roles.filter((r) => Math.abs(round1(rolePoints(r, read.student)) - points) < 0.05);
868
+ const why = String(row?.score_why || '');
869
+ const role = same.find((r) => r.company && why.includes(` · ${r.company} (${stored}/10`))
870
+ || same.find((r) => companyScore(r.company).score === stored) || same[0];
871
+ if (role) return { name: role.company, score: stored, former: role.former };
872
+ }
873
+ const s = scorePerson(row, undefined, read);
874
+ return { name: s.company, score: stored > 0 ? stored : s.companyScore, former: s.title.former };
875
+ }
876
+
877
+ /**
878
+ * The top companies (TOP_COMPANY and up) someone works at now and worked at
879
+ * before, for the person panel's notes: `now` is the company their score is
880
+ * built on when that is a current role, else their first current role's;
881
+ * `before` the best of their former roles' (not the one they're at now), each
882
+ * { name, score } or null. The company their score is built on gets the
883
+ * score it was scored with (scoredCompany); any other gets companyScore()'s,
884
+ * which only the curated list can raise.
885
+ */
886
+ export function topCompanies(row) {
887
+ const read = readPerson(row);
888
+ const scored = scoredCompany(row, read);
889
+ const top = (name) => {
890
+ if (!name) return null;
891
+ const score = name === scored.name ? scored.score : companyScore(name).score;
892
+ return score >= TOP_COMPANY ? { name, score } : null;
893
+ };
894
+ const current = read.roles.filter((r) => !r.former && r.company);
895
+ const now = top(!scored.former && current.some((r) => r.company === scored.name) ? scored.name : current[0]?.company);
896
+ let before = null;
897
+ for (const r of read.roles) {
898
+ if (!r.former || r.company === now?.name) continue;
899
+ const t = top(r.company);
900
+ if (t && (!before || t.score > before.score)) before = t;
901
+ }
902
+ return { now, before };
345
903
  }
346
904
 
347
905
  /**
@@ -360,36 +918,87 @@ export function bridgeBoost(circle = []) {
360
918
  return { boost: round1(boost), total, sCount, aCount, elitePct, circlePower, isCatalyst };
361
919
  }
362
920
 
363
- /** A plain-English line for why someone scores what they do. */
364
- export function explainScore(s, boost = 0) {
365
- const bits = [`${s.title.label} (${s.title.points})`, `${s.company || 'no company found'} (${s.companyScore}/10${s.companySource === 'yours' ? ', your score' : ''})`];
921
+ /**
922
+ * A plain-English line for why someone scores what they do, e.g.
923
+ * "VP / Partner / GM (9) · Quillon (5/10: 4 + 1 your sector: Dental) · +0.7 strong circle".
924
+ * `sectorLabel(key)` names the sector that leaned the company (this file
925
+ * imports nothing, so the labels are passed in); without it the line says
926
+ * "your sector" alone.
927
+ */
928
+ export function explainScore(s, boost = 0, { sectorLabel } = {}) {
929
+ const which = s.companySector && sectorLabel?.(s.companySector.key);
930
+ const co = s.companySector
931
+ ? `${s.company} (${s.companyScore}/10: ${s.companySector.base} + ${s.companySector.bonus} your sector${which ? `: ${which}` : ''})`
932
+ : `${s.company || 'no company found'} (${s.companyScore}/10${s.companySource === 'yours' ? ', your score' : ''})`;
933
+ const bits = [`${s.title.label} (${s.title.points})`, co];
366
934
  if (s.bonus.points) bits.push(`+${s.bonus.points} ${s.bonus.reasons.join(', ')}`);
367
935
  if (boost) bits.push(`+${boost} strong circle`);
368
936
  return bits.join(' · ');
369
937
  }
370
938
 
371
939
  /**
372
- * Score a whole network. People first, with company scores that can use how
373
- * many of your people work at each company; then 1st-degree bridges get their
374
- * circle boost from their scored circle. Returns one result per row id.
940
+ * A network read once, to be scored any number of ways: each row's
941
+ * readPerson(), and every company anyone names (in a current role or a former
942
+ * one) with what its score depends on besides your own choices: how many of
943
+ * your people work there now, and its one industry. With `sectorsOf` (the
944
+ * sector directory, as networkCompanies takes it), also the directory's
945
+ * sectors it matches, and with `groupOf` the broad industries those sit
946
+ * under, worked out here once however many ways the read is scored. Pass it
947
+ * to scoreNetwork() with the same rows.
948
+ * @returns { people: Map row → readPerson, headcount, industries,
949
+ * companies: Map name → { headcount, industry, industryFrom, sectors?, sectorIndustries? } }
375
950
  */
376
- export function scoreNetwork(rows = [], { overrides = new Map(), industryOf } = {}) {
377
- // How many of your people work at each company (currently, once each).
378
- const headcount = new Map();
379
- const seen = new Set();
380
- for (const r of rows) {
381
- for (const role of rolesWithCompanies(r)) {
382
- const key = `${role.company}|${r.profile_url || r.id}`;
383
- if (!role.company || role.former || seen.has(key)) continue;
384
- seen.add(key);
385
- headcount.set(role.company, (headcount.get(role.company) || 0) + 1);
951
+ export function readNetwork(rows = [], { industryOf, sectorsOf, groupOf } = {}) {
952
+ const people = new Map();
953
+ for (const r of rows) people.set(r, readPerson(r));
954
+ const { headcount, industries, industryFrom, sectors, sectorIndustries } = networkCompanies(rows, {
955
+ industryOf, sectorsOf, groupOf, rolesOf: (r) => people.get(r).roles,
956
+ });
957
+ const companies = new Map();
958
+ for (const [name, n] of headcount) {
959
+ companies.set(name, {
960
+ headcount: n, industry: industries.get(name), industryFrom: industryFrom.get(name),
961
+ ...(sectorsOf && { sectors: sectors.get(name) }),
962
+ ...(sectorsOf && groupOf && { sectorIndustries: sectorIndustries.get(name) }),
963
+ });
964
+ }
965
+ // A company only in someone's former roles has no current people to count or
966
+ // ask; its name (or the curated list) is all there is to go on.
967
+ for (const { roles } of people.values()) {
968
+ for (const { company } of roles) {
969
+ if (!company || companies.has(company)) continue;
970
+ const keys = sectorsOf ? sectorsOf(company, []) : null;
971
+ const { industry, from } = industryAndSource(company, { industryOf });
972
+ companies.set(company, {
973
+ headcount: 0, industry, industryFrom: from,
974
+ ...(keys && { sectors: keys }),
975
+ ...(keys && groupOf && { sectorIndustries: industriesUnder(keys, groupOf) }),
976
+ });
386
977
  }
387
978
  }
979
+ return { people, headcount, industries, companies };
980
+ }
981
+
982
+ /**
983
+ * Score a whole network. People first, with company scores that can use how
984
+ * many of your people work at each company and each company's one industry;
985
+ * then 1st-degree bridges get their circle boost from their scored circle.
986
+ * `focus` is the sector focus from Settings, or nothing. `read` is
987
+ * readNetwork() of these rows, when they have been read already. Returns one
988
+ * result per row id, plus the per-company facts it used and each company's
989
+ * score (companyScore(), by name).
990
+ */
991
+ export function scoreNetwork(rows = [], {
992
+ overrides = new Map(), industryOf, sectorsOf, groupOf, focus, read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
993
+ } = {}) {
994
+ // Each company is scored once; everyone there shares the answer.
995
+ const companyScores = new Map();
996
+ const companyFor = (name) => {
997
+ if (!companyScores.has(name)) companyScores.set(name, companyScoreIn(read, name, { overrides, focus }));
998
+ return companyScores.get(name);
999
+ };
388
1000
  const out = new Map();
389
- for (const r of rows) {
390
- const companyFor = (name) => companyScore(name, { overrides, headcount: headcount.get(name) || 0, industry: name ? industryOf?.(name, r.headline) : undefined });
391
- out.set(r.id, { ...scorePerson(r, companyFor), boost: 0 });
392
- }
1001
+ for (const r of rows) out.set(r.id, { ...scorePerson(r, companyFor, read.people.get(r)), boost: 0 });
393
1002
  const circles = new Map();
394
1003
  for (const r of rows) {
395
1004
  if (r.degree !== 2 || !r.source_connection_id) continue;
@@ -406,5 +1015,5 @@ export function scoreNetwork(rows = [], { overrides = new Map(), industryOf } =
406
1015
  s.power = round1(s.power + b.boost);
407
1016
  s.tier = tierFor(s.power);
408
1017
  }
409
- return { scores: out, headcount };
1018
+ return { scores: out, headcount: read.headcount, industries: read.industries, companyScores };
410
1019
  }