six-degrees 0.4.10 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +9 -9
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/crm/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js +10 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/crm/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +4 -3
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__05_3-22._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__06mi9k2._.js → [root-of-the-server]__13dx4sd._.js} +7 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1oqfuh8._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_crm_route_actions_0d1aflv.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
- package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -5
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +19 -3
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/{static/chunks/1xptlvtcz6654.css → standalone/.next/static/chunks/1x_ysly42-teq.css} +1 -1
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/.next/standalone/CHANGELOG.md +235 -0
- package/.next/standalone/app/api/data/export/route.js +2 -1
- package/.next/standalone/app/api/ingest/route.js +40 -1
- package/.next/standalone/app/api/scraper/route.js +12 -2
- package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
- package/.next/standalone/app/api/social/crm/route.js +37 -0
- package/.next/standalone/app/api/social/messages/route.js +44 -8
- package/.next/standalone/app/api/social/route.js +80 -22
- package/.next/standalone/app/components/AppTabs.js +59 -0
- package/.next/standalone/app/components/ChainView.js +303 -89
- package/.next/standalone/app/components/CirclePanel.js +162 -0
- package/.next/standalone/app/components/CompanyScores.js +2 -2
- package/.next/standalone/app/components/FieldStep.js +4 -4
- package/.next/standalone/app/components/FilterPanel.js +121 -183
- package/.next/standalone/app/components/ForceGraph.js +41 -3
- package/.next/standalone/app/components/GalaxyLab.js +26 -31
- package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
- package/.next/standalone/app/components/MapControls.js +154 -0
- package/.next/standalone/app/components/NoteDetail.js +118 -0
- package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
- package/.next/standalone/app/components/PeopleMap.js +308 -0
- package/.next/standalone/app/components/ScanRadar.js +129 -0
- package/.next/standalone/app/components/ScanStatusBar.js +72 -27
- package/.next/standalone/app/components/SeparationView.js +626 -118
- package/.next/standalone/app/components/Sidebar.js +28 -2
- package/.next/standalone/app/components/settings/DataSection.js +12 -3
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
- package/.next/standalone/app/components/settings/SectorSection.js +2 -2
- package/.next/standalone/app/components/settings/TierSection.js +1 -1
- package/.next/standalone/app/components/settings/TitleSection.js +149 -0
- package/.next/standalone/app/components/views.js +15 -14
- package/.next/standalone/app/globals.css +11 -0
- package/.next/standalone/app/page.js +89 -156
- package/.next/standalone/app/paths/page.js +39 -27
- package/.next/standalone/app/profile/page.js +33 -5
- package/.next/standalone/app/queue/page.js +56 -34
- package/.next/standalone/app/scores/page.js +16 -66
- package/.next/standalone/app/settings/page.js +27 -13
- package/.next/standalone/app/setup/page.js +298 -205
- package/.next/standalone/app/social/Crm.js +683 -0
- package/.next/standalone/app/social/SocialHub.js +374 -0
- package/.next/standalone/app/social/Thread.js +90 -0
- package/.next/standalone/app/social/page.js +10 -351
- package/.next/standalone/lib/chain-layout.js +105 -35
- package/.next/standalone/lib/data-export.js +10 -4
- package/.next/standalone/lib/data-folder.js +9 -0
- package/.next/standalone/lib/data-import.js +13 -3
- package/.next/standalone/lib/degrees.js +0 -6
- package/.next/standalone/lib/dot-rings.js +25 -0
- package/.next/standalone/lib/galaxy-lab.js +65 -6
- package/.next/standalone/lib/ingest.js +2 -0
- package/.next/standalone/lib/island.js +40 -0
- package/.next/standalone/lib/linkedin-export.js +267 -46
- package/.next/standalone/lib/linkedin-limits.js +6 -2
- package/.next/standalone/lib/map-heat.js +20 -0
- package/.next/standalone/lib/network-health.js +1 -1
- package/.next/standalone/lib/notifications.js +121 -0
- package/.next/standalone/lib/people-map.js +82 -0
- package/.next/standalone/lib/rarity.js +45 -3
- package/.next/standalone/lib/reach.js +7 -6
- package/.next/standalone/lib/rpc.js +15 -4
- package/.next/standalone/lib/scan-pace.js +46 -0
- package/.next/standalone/lib/scoring.js +39 -8
- package/.next/standalone/lib/scraper-client.js +19 -6
- package/.next/standalone/lib/sector-focus.js +4 -4
- package/.next/standalone/lib/separation.js +137 -10
- package/.next/standalone/lib/settings-effects.js +20 -11
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/social-crm.js +415 -0
- package/.next/standalone/lib/social-store.js +13 -6
- package/.next/standalone/lib/tier-grid.js +99 -0
- package/.next/standalone/lib/title-ranking.js +83 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +177 -35
- package/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/{standalone/.next/static/chunks/1xptlvtcz6654.css → static/chunks/1x_ysly42-teq.css} +1 -1
- package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/CHANGELOG.md +235 -0
- package/lib/chain-layout.js +105 -35
- package/lib/data-export.js +10 -4
- package/lib/data-folder.js +9 -0
- package/lib/data-import.js +13 -3
- package/lib/degrees.js +0 -6
- package/lib/dot-rings.js +25 -0
- package/lib/galaxy-lab.js +65 -6
- package/lib/ingest.js +2 -0
- package/lib/island.js +40 -0
- package/lib/linkedin-export.js +267 -46
- package/lib/linkedin-limits.js +6 -2
- package/lib/map-heat.js +20 -0
- package/lib/network-health.js +1 -1
- package/lib/notifications.js +121 -0
- package/lib/people-map.js +82 -0
- package/lib/rarity.js +45 -3
- package/lib/reach.js +7 -6
- package/lib/rpc.js +15 -4
- package/lib/scan-pace.js +46 -0
- package/lib/scoring.js +39 -8
- package/lib/scraper-client.js +19 -6
- package/lib/sector-focus.js +4 -4
- package/lib/separation.js +137 -10
- package/lib/settings-effects.js +20 -11
- package/lib/settings.js +3 -0
- package/lib/social-crm.js +415 -0
- package/lib/social-store.js +13 -6
- package/lib/tier-grid.js +99 -0
- package/lib/title-ranking.js +83 -0
- package/package.json +1 -1
- package/scripts/scrape.py +177 -35
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
- package/.next/standalone/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/standalone/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/standalone/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- package/.next/standalone/app/components/OrbitGraph.js +0 -737
- package/.next/standalone/app/social/Conversations.js +0 -246
- package/.next/static/chunks/07dyt47mp4s9-.js +0 -1
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/static/chunks/206rm6j42gl_a.js +0 -1
- package/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/static/chunks/2coxzjm_k6ilr.js +0 -1
- package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +0 -1
- package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{7zWJnSkfSoRhRsqY6ZKtD → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Notifications worth opening (Blake, 2026-10-02: "the notifications should be
|
|
2
|
+
// able to be clicked and … take over the right panel and show a more detailed
|
|
3
|
+
// version … what person added back from who and that they are a value person
|
|
4
|
+
// if the score is S … a new person added back, or we got a person from a
|
|
5
|
+
// high-ranking company, or that our scan is done").
|
|
6
|
+
//
|
|
7
|
+
// Each notification carries `data` naming who it is about, so the right panel
|
|
8
|
+
// can show them: { personId?, profileUrl?, viaId?, personIds?, action? }.
|
|
9
|
+
// Older notifications have no data; noteSubject finds their person by the name
|
|
10
|
+
// in the title. Plain functions, no React; tests/notifications.test.mjs.
|
|
11
|
+
|
|
12
|
+
const HIGH = new Set(['S', 'A']);
|
|
13
|
+
// A company's own score (company_prestige_score, lib/scoring.js) from here up is "a top company".
|
|
14
|
+
export const TOP_COMPANY = 8;
|
|
15
|
+
|
|
16
|
+
const score1 = (row) => {
|
|
17
|
+
const n = parseFloat(row?.power_score);
|
|
18
|
+
return Number.isFinite(n) ? n.toFixed(1) : null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Someone you met through a bridge who is now one of your connections. */
|
|
22
|
+
export function addedBackNotification({ person, via, userId = null }) {
|
|
23
|
+
if (!person) return null;
|
|
24
|
+
const tier = person.tier || null;
|
|
25
|
+
const valued = HIGH.has(tier);
|
|
26
|
+
return {
|
|
27
|
+
user_id: userId || null,
|
|
28
|
+
type: 'added_back',
|
|
29
|
+
title: `${person.name || 'Someone'} added you back${via?.name ? `, through ${via.name}` : ''}`,
|
|
30
|
+
message: [tier ? `${tier}-tier${score1(person) ? ` · ${score1(person)}` : ''}` : null, valued ? 'a valuable person to know' : null, person.headline || null]
|
|
31
|
+
.filter(Boolean).join(' · ').slice(0, 140),
|
|
32
|
+
icon: valued ? '👑' : '🤝',
|
|
33
|
+
data: { personId: person.id, viaId: via?.id ?? null, tier },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** People a circle scan found at top companies, as one notification. */
|
|
38
|
+
export function topCompanyNotification({ people = [], via = null, userId = null, threshold = TOP_COMPANY }) {
|
|
39
|
+
const top = people.filter((p) => Number(p?.company_prestige_score) >= threshold)
|
|
40
|
+
.sort((a, b) => Number(b.company_prestige_score) - Number(a.company_prestige_score) || (parseFloat(b.power_score) || 0) - (parseFloat(a.power_score) || 0));
|
|
41
|
+
if (!top.length) return null;
|
|
42
|
+
const companies = [...new Set(top.map((p) => p.company).filter(Boolean))];
|
|
43
|
+
return {
|
|
44
|
+
user_id: userId || null,
|
|
45
|
+
type: 'top_company',
|
|
46
|
+
title: `${top.length} ${top.length === 1 ? 'person' : 'people'} at top companies${via?.name ? ` in ${via.name}’s circle` : ''}`,
|
|
47
|
+
message: companies.slice(0, 3).join(', ') + (companies.length > 3 ? ` +${companies.length - 3} more` : ''),
|
|
48
|
+
icon: '🏢',
|
|
49
|
+
data: { personIds: top.slice(0, 25).map((p) => p.id), personId: top[0].id, viaId: via?.id ?? null },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// What each scanner job is called when it's done. Setting up, signing in and
|
|
54
|
+
// saving settings aren't scans, so they leave nothing.
|
|
55
|
+
const SCAN_DONE = {
|
|
56
|
+
bridge: (t) => `Scan done: ${t?.name ? `${t.name}’s circle` : 'their circle'}`,
|
|
57
|
+
resume: (t) => `Scan done: ${t?.name ? `${t.name}’s circle` : 'their circle'}`,
|
|
58
|
+
rescrape: (t) => `Scan done: ${t?.name ? `${t.name}’s circle` : 'their circle'}`,
|
|
59
|
+
'auto-bridge': () => 'Mapping the 2nd degree: done for now',
|
|
60
|
+
'auto-bridge-retry': () => 'Retrying hidden lists: done',
|
|
61
|
+
'resume-all': () => 'Every paused circle: done for now',
|
|
62
|
+
full: () => 'Your whole network is scanned',
|
|
63
|
+
refresh: () => 'Check for new: done',
|
|
64
|
+
company: (t) => `Company scan done${t?.name ? `: ${t.name}` : ''}`,
|
|
65
|
+
messages: () => 'Messages synced',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** The notification a finished scan leaves, or null (not a scan, stopped, or it failed). */
|
|
69
|
+
export function scanDoneNotification({ action, target = null, exitCode, stopped = false, log = [], userId = null }) {
|
|
70
|
+
const name = SCAN_DONE[action];
|
|
71
|
+
if (!name || stopped || exitCode !== 0) return null;
|
|
72
|
+
// The last thing the scanner said that sums it up ("Done. 5/5 bridges mapped.").
|
|
73
|
+
const last = [...log].reverse().map((l) => String(l).trim()).find((l) => l && l !== 'Finished.' && !/^Speed:/.test(l)) || '';
|
|
74
|
+
return {
|
|
75
|
+
user_id: userId || null,
|
|
76
|
+
type: 'scan_done',
|
|
77
|
+
title: name(target),
|
|
78
|
+
message: last.slice(0, 140),
|
|
79
|
+
icon: '📡',
|
|
80
|
+
data: { action, personId: target?.id ?? null },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Older notifications name their person only in the title.
|
|
85
|
+
const NAME_IN_TITLE = [
|
|
86
|
+
[/^(.+?) accepted!/, 1],
|
|
87
|
+
[/^High-value connection: (.+)$/, 1],
|
|
88
|
+
[/^Catalyst discovered: (.+)$/, 1],
|
|
89
|
+
[/^(.+?) added you back/, 1],
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Who a notification is about, from the rows on screen: { person, via, people }.
|
|
94
|
+
* `via` is who you met them through: the notification's own, else the person's
|
|
95
|
+
* recorded introducer. `people` are everyone it names (a top-company list).
|
|
96
|
+
*/
|
|
97
|
+
export function noteSubject(note, connections = [], degree2 = []) {
|
|
98
|
+
const all = [...(connections || []), ...(degree2 || [])];
|
|
99
|
+
const byId = new Map(all.map((r) => [r.id, r]));
|
|
100
|
+
const data = typeof note?.data === 'string' ? safeJson(note.data) : note?.data || {};
|
|
101
|
+
let person = data.personId != null ? byId.get(data.personId) : null;
|
|
102
|
+
if (!person && data.profileUrl) person = all.find((r) => r.profile_url === data.profileUrl) || null;
|
|
103
|
+
if (!person) {
|
|
104
|
+
for (const [re, i] of NAME_IN_TITLE) {
|
|
105
|
+
const m = String(note?.title || '').match(re);
|
|
106
|
+
if (!m) continue;
|
|
107
|
+
const name = m[i].trim().toLowerCase();
|
|
108
|
+
person = (connections || []).find((r) => String(r.name || '').toLowerCase() === name)
|
|
109
|
+
|| (degree2 || []).find((r) => String(r.name || '').toLowerCase() === name) || null;
|
|
110
|
+
if (person) break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const viaId = data.viaId ?? person?.unlocked_from_bridge_id ?? (person?.degree === 2 ? person.source_connection_id : null);
|
|
114
|
+
const via = viaId != null ? byId.get(viaId) || null : null;
|
|
115
|
+
const people = (data.personIds || []).map((id) => byId.get(id)).filter(Boolean);
|
|
116
|
+
return { person: person || null, via, people, data };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function safeJson(s) {
|
|
120
|
+
try { return JSON.parse(s); } catch { return {}; }
|
|
121
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Paths → People: the Paths map with people instead of companies (Blake,
|
|
2
|
+
// 2026-10-02: "basically going to look like map but people based and the
|
|
3
|
+
// bigger circle is their cluster with value"). One bubble per connection of
|
|
4
|
+
// yours, sized by the cluster behind them; a line joins two connections whose
|
|
5
|
+
// clusters share people.
|
|
6
|
+
//
|
|
7
|
+
// Plain functions over the rows the app already has, no React, so
|
|
8
|
+
// tests/people-map.test.mjs runs them as they are.
|
|
9
|
+
|
|
10
|
+
import { keyFor } from './separation.js';
|
|
11
|
+
|
|
12
|
+
/** What a cluster is worth: S tier counts 3, A tier 2, everyone else 1. */
|
|
13
|
+
export const clusterValue = ({ size = 0, S = 0, A = 0 } = {}) => size + 2 * S + A;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One entry per connection: { person, id, key, size, S, A, value, only, share }.
|
|
17
|
+
* `size` is the people in their cluster (each once), `only` how many of those
|
|
18
|
+
* none of your other connections' clusters hold, `share` = only / size.
|
|
19
|
+
* Also `links`: [{ a, b, shared }] for every two connections whose clusters
|
|
20
|
+
* share people, most shared first, and `rank(id)`: the share of your scanned
|
|
21
|
+
* connections whose cluster is worth less.
|
|
22
|
+
*/
|
|
23
|
+
export function peopleMap(d1 = [], d2 = [], { maxLinks = 400 } = {}) {
|
|
24
|
+
const ids = new Set((d1 || []).map((c) => c.id));
|
|
25
|
+
// Each person once per connection whose scan found them.
|
|
26
|
+
const byConnection = new Map(); // id -> Map(key -> row)
|
|
27
|
+
const holders = new Map(); // key -> Set(connection id)
|
|
28
|
+
for (const row of d2 || []) {
|
|
29
|
+
const from = row?.source_connection_id;
|
|
30
|
+
if (from == null || !ids.has(from)) continue;
|
|
31
|
+
const key = keyFor(row);
|
|
32
|
+
let circle = byConnection.get(from);
|
|
33
|
+
if (!circle) byConnection.set(from, (circle = new Map()));
|
|
34
|
+
if (!circle.has(key)) circle.set(key, row);
|
|
35
|
+
let who = holders.get(key);
|
|
36
|
+
if (!who) holders.set(key, (who = new Set()));
|
|
37
|
+
who.add(from);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const people = (d1 || []).map((c) => {
|
|
41
|
+
const circle = byConnection.get(c.id);
|
|
42
|
+
let S = 0, A = 0, only = 0;
|
|
43
|
+
if (circle) {
|
|
44
|
+
for (const [key, row] of circle) {
|
|
45
|
+
if (row.tier === 'S') S++;
|
|
46
|
+
else if (row.tier === 'A') A++;
|
|
47
|
+
if (holders.get(key)?.size === 1) only++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const size = circle ? circle.size : 0;
|
|
51
|
+
return { person: c, id: c.id, key: keyFor(c), size, S, A, value: clusterValue({ size, S, A }), only, share: size ? only / size : 0 };
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Two connections whose clusters share people. A person known to many of
|
|
55
|
+
// your connections would make a line between every two of them, so past
|
|
56
|
+
// eight holders they are left out of the lines (they still count in `only`).
|
|
57
|
+
const pairs = new Map();
|
|
58
|
+
for (const who of holders.values()) {
|
|
59
|
+
if (who.size < 2 || who.size > 8) continue;
|
|
60
|
+
const list = [...who].sort();
|
|
61
|
+
for (let i = 0; i < list.length; i++) {
|
|
62
|
+
for (let j = i + 1; j < list.length; j++) {
|
|
63
|
+
const k = `${list[i]}\u0000${list[j]}`;
|
|
64
|
+
pairs.set(k, (pairs.get(k) || 0) + 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const links = [...pairs].map(([k, shared]) => {
|
|
69
|
+
const [a, b] = k.split('\u0000');
|
|
70
|
+
return { a, b, shared };
|
|
71
|
+
}).sort((x, y) => y.shared - x.shared || (x.a < y.a ? -1 : 1)).slice(0, maxLinks);
|
|
72
|
+
|
|
73
|
+
const values = people.filter((p) => p.size > 0).map((p) => p.value).sort((x, y) => x - y);
|
|
74
|
+
const rank = (id) => {
|
|
75
|
+
const p = people.find((x) => x.id === id);
|
|
76
|
+
if (!p || !p.size || values.length < 2) return null;
|
|
77
|
+
let below = 0;
|
|
78
|
+
while (below < values.length && values[below] < p.value) below++;
|
|
79
|
+
return below / (values.length - 1);
|
|
80
|
+
};
|
|
81
|
+
return { people, links, rank };
|
|
82
|
+
}
|
package/lib/rarity.js
CHANGED
|
@@ -39,12 +39,15 @@ export function rarityInfo(key) {
|
|
|
39
39
|
/**
|
|
40
40
|
* How many mutual connections you share with someone, and where that number
|
|
41
41
|
* came from: { count, from: 'linkedin' | 'scans' }. `waysIn` is how many of
|
|
42
|
-
* your scanned connections know them (lib/separation.js).
|
|
42
|
+
* your scanned connections know them (lib/separation.js): never fewer than that.
|
|
43
43
|
*/
|
|
44
44
|
export function mutualsOf(row, waysIn = 1) {
|
|
45
45
|
const saved = Number(row?.mutual_count);
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const seen = Math.max(1, Math.floor(Number(waysIn) || 1));
|
|
47
|
+
// Everyone the app found them through is a mutual connection, so a saved
|
|
48
|
+
// count lower than that is out of date: "only way in" with two ways drawn.
|
|
49
|
+
if (Number.isFinite(saved) && saved >= seen) return { count: Math.floor(saved), from: 'linkedin' };
|
|
50
|
+
return { count: seen, from: 'scans' };
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/** Someone's rarity: { key, count, from }. */
|
|
@@ -78,3 +81,42 @@ export function toggle(set, key) {
|
|
|
78
81
|
else next.add(key);
|
|
79
82
|
return next;
|
|
80
83
|
}
|
|
84
|
+
|
|
85
|
+
// ── The Separation slider ──────────────────────────────────────────────────
|
|
86
|
+
// Blake, 2026-10-01: the rare person reachable through one door at one end,
|
|
87
|
+
// the easier, well-connected ones at the other, "so it's seamless"; 2026-10-02:
|
|
88
|
+
// "i want it to be slider ajusted and can be dynmaic". One number, 0 to 100:
|
|
89
|
+
// 0 puts the rarest ways in first, 100 the easiest, and 50 is each person's own
|
|
90
|
+
// score alone, as the list has always been. It reorders; it never changes a
|
|
91
|
+
// score, a tier or a rank. No odds are claimed: the app hasn't measured how
|
|
92
|
+
// often anyone accepts.
|
|
93
|
+
|
|
94
|
+
export const SLIDER_MIDDLE = 50;
|
|
95
|
+
|
|
96
|
+
/** How easy the way in is: 0 with one mutual connection, 1 with 31 or more, on a log scale (1→3 counts like 10→30). */
|
|
97
|
+
export function easeOf(count) {
|
|
98
|
+
const n = Math.max(1, Math.floor(Number(count) || 1));
|
|
99
|
+
return Math.min(1, Math.log(n) / Math.log(31));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Where someone sorts with the slider at `at`: their score, weighted by how
|
|
104
|
+
* well their way in fits the end the slider leans to. At 50 it is the score.
|
|
105
|
+
*/
|
|
106
|
+
export function slideValue(score, count, at = SLIDER_MIDDLE) {
|
|
107
|
+
const lean = (Math.max(0, Math.min(100, Number(at) || 0)) - SLIDER_MIDDLE) / SLIDER_MIDDLE; // -1 rare … +1 easy
|
|
108
|
+
if (!lean) return score;
|
|
109
|
+
const ease = easeOf(count);
|
|
110
|
+
const pull = Math.abs(lean);
|
|
111
|
+
return score * (1 - pull + pull * (lean < 0 ? 1 - ease : ease));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** What the slider is doing at `at`, in words: { name, detail }. */
|
|
115
|
+
export function slideLabel(at = SLIDER_MIDDLE) {
|
|
116
|
+
const v = Math.max(0, Math.min(100, Math.round(Number(at) || 0)));
|
|
117
|
+
if (v <= 10) return { name: 'Rarest first', detail: 'Strong people with one way in: the connection who knows them is the only door you have.' };
|
|
118
|
+
if (v < 45) return { name: 'Leaning rare', detail: 'Strong people with few mutual connections move up; the ones everybody knows move down.' };
|
|
119
|
+
if (v <= 55) return { name: 'By power', detail: 'Each person’s own score alone. How many of your connections know them doesn’t change the order.' };
|
|
120
|
+
if (v < 90) return { name: 'Leaning easy', detail: 'Strong people you share more mutual connections with move up: more people who could introduce you.' };
|
|
121
|
+
return { name: 'Easiest first', detail: 'Strong people you share the most mutual connections with: the warmest introductions. All the way right, the map aims at whoever you share the most with, with the ones in circles you haven’t scanned yet as grey dots.' };
|
|
122
|
+
}
|
package/lib/reach.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
//
|
|
21
21
|
// Plain functions, no React; tests/reach.test.mjs.
|
|
22
22
|
|
|
23
|
+
import { paceSeconds } from './scan-pace.js';
|
|
23
24
|
import { keyFor, compareBridges } from './separation.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -107,13 +108,13 @@ export function readyByCircle(reach) {
|
|
|
107
108
|
* page, a minute more after every 10), so about 55 minutes for a whole list.
|
|
108
109
|
* The Scan page and the Degrees panel both say it from here.
|
|
109
110
|
*/
|
|
110
|
-
export function circleScanCost(pages = 100) {
|
|
111
|
+
export function circleScanCost(pages = 100, pace = 'fast') {
|
|
111
112
|
const searches = Math.max(1, Math.min(100, Math.round(pages) || 100));
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
searches,
|
|
115
|
-
|
|
116
|
-
};
|
|
113
|
+
// At Fast, as it has always been said; slower speeds add their waits (lib/scan-pace.js).
|
|
114
|
+
const minutes = !pace || pace === 'fast'
|
|
115
|
+
? (searches >= 100 ? 55 : Math.max(5, Math.round(searches * 0.55)))
|
|
116
|
+
: Math.max(5, Math.round(paceSeconds(pace, searches) / 60));
|
|
117
|
+
return { profileViews: 1, searches, minutes };
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
/**
|
package/lib/rpc.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getDb, newId, nowIso } from './db-client.js';
|
|
|
7
7
|
// for the routes that call it; it rescores with lib/scoring.js like the rest.
|
|
8
8
|
|
|
9
9
|
import { readNetwork, scoreNetwork, explainScore, SCORING_VERSION, KNOWN_COMPANIES, COMPANY_DATA, RULE_TABLES, TIER_SCALE_SETTING } from './scoring.js';
|
|
10
|
+
import { TITLE_RANKING_SETTING, isDefaultRanking, rankingFingerprint } from './title-ranking.js';
|
|
10
11
|
import { industryKeyOf, INDUSTRIES } from './companies.js';
|
|
11
12
|
import { readSettings } from './settings.js';
|
|
12
13
|
import { SECTOR_FOCUS_SETTING, focusFingerprint, previewSectorFocus } from './sector-focus.js';
|
|
@@ -69,6 +70,12 @@ export function tierScaleOf(db = getDb()) {
|
|
|
69
70
|
return readSettings(db, { tierScale: TIER_SCALE_SETTING }).tierScale;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/** Your title ranking (Settings → Scores → Titles), or null when you haven't changed one. */
|
|
74
|
+
export function titleRankingOf(db = getDb()) {
|
|
75
|
+
const r = readSettings(db, { titleRanking: TITLE_RANKING_SETTING }).titleRanking;
|
|
76
|
+
return isDefaultRanking(r) ? null : r;
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
/**
|
|
73
80
|
* What rescoring reads: every row, as scoring needs it. `withPeople` adds each
|
|
74
81
|
* row's name, for naming who a change would move.
|
|
@@ -111,12 +118,13 @@ export function rescoreAll({ compareWith } = {}) {
|
|
|
111
118
|
const overrides = companyOverrides(db);
|
|
112
119
|
const focus = sectorFocusOf(db);
|
|
113
120
|
const tierScale = tierScaleOf(db);
|
|
121
|
+
const titles = titleRankingOf(db);
|
|
114
122
|
// Every headline is read once, however many ways the rows are then scored.
|
|
115
123
|
const read = readForScoring(rows);
|
|
116
|
-
const { scores } = scoreNetwork(rows, { overrides, focus, tierScale, read });
|
|
124
|
+
const { scores } = scoreNetwork(rows, { overrides, focus, tierScale, titles, read });
|
|
117
125
|
// Worked out before anything is written, so a failure leaves nothing half done.
|
|
118
126
|
const change = compareWith === undefined ? null
|
|
119
|
-
: previewSectorFocus(rows, { overrides, read, from: compareWith, to: focus, tierScale, examples: 0 });
|
|
127
|
+
: previewSectorFocus(rows, { overrides, read, from: compareWith, to: focus, tierScale, titles, examples: 0 });
|
|
120
128
|
const update = db.prepare(`UPDATE linkedin_connections SET seniority_score = ?, company_prestige_score = ?, power_score = ?, tier = ?,
|
|
121
129
|
score_why = ?, circle_power = ?, circle_s_count = ?, circle_a_count = ?, circle_elite_pct = ?, is_catalyst = ?, catalyst_score = ?, updated_at = ?
|
|
122
130
|
WHERE id = ?`);
|
|
@@ -144,6 +152,7 @@ export function rescoreAll({ compareWith } = {}) {
|
|
|
144
152
|
meta.run('scoring_list', KNOWN_LIST_STAMP);
|
|
145
153
|
meta.run('scoring_focus', focusFingerprint(focus));
|
|
146
154
|
meta.run('scoring_tiers', tierScale);
|
|
155
|
+
meta.run('scoring_titles', rankingFingerprint(titles || undefined));
|
|
147
156
|
db.exec('COMMIT');
|
|
148
157
|
} catch (err) {
|
|
149
158
|
db.exec('ROLLBACK');
|
|
@@ -154,7 +163,7 @@ export function rescoreAll({ compareWith } = {}) {
|
|
|
154
163
|
|
|
155
164
|
/**
|
|
156
165
|
* Rescore once if the stored scores came from an older scoring model or
|
|
157
|
-
* curated list, or from a sector focus
|
|
166
|
+
* curated list, or from a sector focus, tier scale or title ranking other than the one saved
|
|
158
167
|
* now: an update, a database restored or brought from another computer, or a
|
|
159
168
|
* Settings save whose rescore never finished.
|
|
160
169
|
*/
|
|
@@ -164,7 +173,9 @@ export function rescoreIfStale() {
|
|
|
164
173
|
const fresh = meta('scoring_version') === String(SCORING_VERSION)
|
|
165
174
|
&& meta('scoring_list') === KNOWN_LIST_STAMP
|
|
166
175
|
&& meta('scoring_focus') === focusFingerprint(sectorFocusOf(db))
|
|
167
|
-
&& meta('scoring_tiers') === tierScaleOf(db)
|
|
176
|
+
&& meta('scoring_tiers') === tierScaleOf(db)
|
|
177
|
+
// Scores from before title rankings existed have no stamp, and none is the same as no ranking.
|
|
178
|
+
&& (meta('scoring_titles') ?? 'none') === rankingFingerprint(titleRankingOf(db) || undefined);
|
|
168
179
|
return fresh ? { scored: 0 } : rescoreAll();
|
|
169
180
|
}
|
|
170
181
|
|
package/lib/scan-pace.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// The Scan page's speed: Fast, Medium or Slow (Blake, 2026-09-30: "a bar that
|
|
2
|
+
// will adjust the scanner from slow, medium and fast… the current is
|
|
3
|
+
// considered fast, and then we lower to medium and slow, bogging it down,
|
|
4
|
+
// making it less likely to get caught, and estimated time for each").
|
|
5
|
+
//
|
|
6
|
+
// Fast is today's pacing and nothing is faster; Medium and Slow only add
|
|
7
|
+
// waiting. Speed changes searches an hour, never searches a day: the daily
|
|
8
|
+
// budget is still the volume cap. scripts/scrape.py SCAN_PACES holds the same
|
|
9
|
+
// numbers (tests/scan-pace.test.mjs checks they agree). Fixed waits, never
|
|
10
|
+
// random "human" timing: the point is fewer searches an hour, not disguise.
|
|
11
|
+
|
|
12
|
+
export const PACES = {
|
|
13
|
+
fast: { label: 'Fast', pagePause: 20, chunkCooldown: 60, profileGap: 60 },
|
|
14
|
+
medium: { label: 'Medium', pagePause: 45, chunkCooldown: 180, profileGap: 90 },
|
|
15
|
+
slow: { label: 'Slow', pagePause: 90, chunkCooldown: 300, profileGap: 120 },
|
|
16
|
+
};
|
|
17
|
+
export const PACE_NAMES = ['slow', 'medium', 'fast'];
|
|
18
|
+
export const DEFAULT_PACE = 'fast';
|
|
19
|
+
// About how long reading one page of results takes, measured in 0.1.6.
|
|
20
|
+
export const WORK_SECONDS = 6;
|
|
21
|
+
const EVERY = 10; // the longer rest comes after every 10 pages (scrape.py SAVE_EVERY_PAGES)
|
|
22
|
+
|
|
23
|
+
export const paceOf = (name) => PACES[name] || PACES[DEFAULT_PACE];
|
|
24
|
+
|
|
25
|
+
/** Seconds to run `searches` searches at a speed: the work, the rest before each, the longer rest every 10. */
|
|
26
|
+
export function paceSeconds(name, searches) {
|
|
27
|
+
const p = paceOf(name);
|
|
28
|
+
const n = Math.max(0, Math.floor(Number(searches) || 0));
|
|
29
|
+
return n * (WORK_SECONDS + p.pagePause) + Math.floor(n / EVERY) * p.chunkCooldown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** About how many searches an hour a speed makes. */
|
|
33
|
+
export function searchesPerHour(name) {
|
|
34
|
+
const p = paceOf(name);
|
|
35
|
+
return Math.round(3600 / (WORK_SECONDS + p.pagePause + p.chunkCooldown / EVERY));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** "about 1 h 7 min", "about 27 min", "under a minute" */
|
|
39
|
+
export function durationText(seconds) {
|
|
40
|
+
if (!(seconds >= 60)) return 'under a minute';
|
|
41
|
+
const m = Math.round(seconds / 60);
|
|
42
|
+
if (m < 60) return `about ${m} min`;
|
|
43
|
+
const h = Math.floor(m / 60);
|
|
44
|
+
const rest = m % 60;
|
|
45
|
+
return `about ${h} h${rest ? ` ${rest} min` : ''}`;
|
|
46
|
+
}
|
package/lib/scoring.js
CHANGED
|
@@ -2512,18 +2512,47 @@ export function readPerson(row) {
|
|
|
2512
2512
|
* points whatever else they list. `companyFor(name)` returns companyScore();
|
|
2513
2513
|
* `read` is the row's readPerson(), when it has been read already.
|
|
2514
2514
|
*/
|
|
2515
|
-
/**
|
|
2516
|
-
|
|
2517
|
-
|
|
2515
|
+
/** Does this title text have the words? Whole words only: "vc" isn't in "dvc", "cto" isn't in "doctor". */
|
|
2516
|
+
const titlePatterns = new Map();
|
|
2517
|
+
export function titleHas(text, words) {
|
|
2518
|
+
let re = titlePatterns.get(words);
|
|
2519
|
+
if (!re) {
|
|
2520
|
+
const esc = words.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2521
|
+
re = new RegExp(`(^|[^a-z0-9])${esc}($|[^a-z0-9])`);
|
|
2522
|
+
if (titlePatterns.size > 500) titlePatterns.clear();
|
|
2523
|
+
titlePatterns.set(words, re);
|
|
2524
|
+
}
|
|
2525
|
+
return re.test(String(text || '').toLowerCase());
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
/**
|
|
2529
|
+
* A title's points under your title ranking (Settings → Scores → Titles,
|
|
2530
|
+
* lib/title-ranking.js): a role whose words are in it counts what you gave
|
|
2531
|
+
* that role (the highest, if several match), else the level counts your
|
|
2532
|
+
* points for it, else the model's. No ranking: the model's.
|
|
2533
|
+
*/
|
|
2534
|
+
export function rankedPoints(role, titles) {
|
|
2535
|
+
if (!titles) return role.title.points;
|
|
2536
|
+
let match = null;
|
|
2537
|
+
for (const r of titles.roles || []) {
|
|
2538
|
+
if (titleHas(role.part, r.text) && (match == null || r.points > match)) match = r.points;
|
|
2539
|
+
}
|
|
2540
|
+
if (match != null) return match;
|
|
2541
|
+
return titles.points?.[role.title.key] ?? role.title.points;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
/** A role's title points as they count: yours where you ranked them, 70% for a former role, capped for a student. */
|
|
2545
|
+
function rolePoints(role, student, titles) {
|
|
2546
|
+
const points = rankedPoints(role, titles) * (role.former ? FORMER_WEIGHT : 1);
|
|
2518
2547
|
return student ? Math.min(points, STUDENT_CAP) : points;
|
|
2519
2548
|
}
|
|
2520
2549
|
|
|
2521
|
-
export function scorePerson(row, companyFor = (name) => companyScore(name), read = readPerson(row)) {
|
|
2550
|
+
export function scorePerson(row, companyFor = (name) => companyScore(name), read = readPerson(row), titles = null) {
|
|
2522
2551
|
const { roles, student, reach } = read;
|
|
2523
2552
|
let best = null;
|
|
2524
2553
|
for (const role of roles) {
|
|
2525
2554
|
const co = companyFor(role.company);
|
|
2526
|
-
const points = rolePoints(role, student);
|
|
2555
|
+
const points = rolePoints(role, student, titles);
|
|
2527
2556
|
const core = points * (0.45 + 0.055 * co.score);
|
|
2528
2557
|
if (!best || core > best.core) best = { role, co, points, core };
|
|
2529
2558
|
}
|
|
@@ -2535,6 +2564,8 @@ export function scorePerson(row, companyFor = (name) => companyScore(name), read
|
|
|
2535
2564
|
const bonus = reachBonus(row?.headline, unknown ? 4 : best.co.base ?? best.co.score, reach);
|
|
2536
2565
|
const power = round1(best.core + bonus.points);
|
|
2537
2566
|
const title = { ...best.role.title, former: best.role.former, student, points: round1(best.points) };
|
|
2567
|
+
// Your ranking moved it: the card's "why" says so.
|
|
2568
|
+
if (titles && rankedPoints(best.role, titles) !== best.role.title.points) title.ranked = true;
|
|
2538
2569
|
if (best.role.former) title.label = `Former ${title.label}`;
|
|
2539
2570
|
if (student && best.role.title.level > 0) title.label = `Student · ${title.label}`;
|
|
2540
2571
|
const out = { title, company: best.role.company, companyScore: best.co.score, companySource: best.co.source, bonus, power, tier: tierFor(power) };
|
|
@@ -2652,7 +2683,7 @@ export function explainScore(s, boost = 0, { sectorLabel } = {}) {
|
|
|
2652
2683
|
const co = s.companySector
|
|
2653
2684
|
? `${s.company} (${s.companyScore}/10: ${s.companySector.base} + ${s.companySector.bonus} your sector${which ? `: ${which}` : ''})`
|
|
2654
2685
|
: `${s.company || 'no company found'} (${s.companyScore}/10${s.companySource === 'yours' ? ', your score' : ''})`;
|
|
2655
|
-
const bits = [`${s.title.label} (${s.title.points})`, co];
|
|
2686
|
+
const bits = [`${s.title.label} (${s.title.points}${s.title.ranked ? ', your ranking' : ''})`, co];
|
|
2656
2687
|
if (s.bonus.points) bits.push(`+${s.bonus.points} ${s.bonus.reasons.join(', ')}`);
|
|
2657
2688
|
if (boost) bits.push(`+${boost} strong circle`);
|
|
2658
2689
|
return bits.join(' · ');
|
|
@@ -2716,7 +2747,7 @@ export function readNetwork(rows = [], { industryOf, sectorsOf, groupOf } = {})
|
|
|
2716
2747
|
* (companyScore(), by name) and the curve's cut-offs (null on the fixed scale).
|
|
2717
2748
|
*/
|
|
2718
2749
|
export function scoreNetwork(rows = [], {
|
|
2719
|
-
overrides = new Map(), industryOf, sectorsOf, groupOf, focus, tierScale = 'fixed',
|
|
2750
|
+
overrides = new Map(), industryOf, sectorsOf, groupOf, focus, tierScale = 'fixed', titles = null,
|
|
2720
2751
|
read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
|
|
2721
2752
|
} = {}) {
|
|
2722
2753
|
// Each company is scored once; everyone there shares the answer.
|
|
@@ -2726,7 +2757,7 @@ export function scoreNetwork(rows = [], {
|
|
|
2726
2757
|
return companyScores.get(name);
|
|
2727
2758
|
};
|
|
2728
2759
|
const out = new Map();
|
|
2729
|
-
for (const r of rows) out.set(r.id, { ...scorePerson(r, companyFor, read.people.get(r)), boost: 0 });
|
|
2760
|
+
for (const r of rows) out.set(r.id, { ...scorePerson(r, companyFor, read.people.get(r), titles), boost: 0 });
|
|
2730
2761
|
// The curve's cut-offs come from your 1st-degree people before any circle
|
|
2731
2762
|
// boost, so a boost (which reads the graded tiers) can't move its own cut-off.
|
|
2732
2763
|
const cutoffs = tierScale === 'fixed' ? null
|
package/lib/scraper-client.js
CHANGED
|
@@ -15,11 +15,27 @@ export async function scraperStatus() {
|
|
|
15
15
|
return r.json();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Scan page → "Hide the Chrome window while scanning", remembered in this
|
|
20
|
+
* browser. Every way a scan starts asks here: the Scan page's own buttons used
|
|
21
|
+
* to post straight to the scanner without it, so the switch did nothing for
|
|
22
|
+
* the scans started from the very page it sits on.
|
|
23
|
+
*/
|
|
24
|
+
export const HIDE_CHROME_KEY = 'six-degrees-hide-chrome';
|
|
25
|
+
export function hideChromeOn(storage = typeof localStorage !== 'undefined' ? localStorage : null) {
|
|
26
|
+
try { return JSON.parse(storage?.getItem(HIDE_CHROME_KEY) || 'false') === true; } catch { return false; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What a scan start sends: the action, its details, and `headless` when Chrome is to stay hidden. */
|
|
30
|
+
export function scanRequest(action, extra = {}, hidden = hideChromeOn()) {
|
|
31
|
+
return { action, ...extra, ...(hidden ? { headless: true } : {}) };
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
export async function startScrape(action, name, extra = {}) {
|
|
19
35
|
const r = await fetch('/api/scraper', {
|
|
20
36
|
method: 'POST',
|
|
21
37
|
headers: { 'Content-Type': 'application/json' },
|
|
22
|
-
body: JSON.stringify(
|
|
38
|
+
body: JSON.stringify(scanRequest(action, { ...(name ? { name } : {}), ...extra })),
|
|
23
39
|
});
|
|
24
40
|
const d = await r.json().catch(() => ({}));
|
|
25
41
|
if (!r.ok) throw new Error(d.error || 'Could not start the scanner.');
|
|
@@ -211,11 +227,8 @@ export async function beginScrape(action, { name, id, onLog, ...extra } = {}) {
|
|
|
211
227
|
});
|
|
212
228
|
let started;
|
|
213
229
|
try {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
let hidden = false;
|
|
217
|
-
try { hidden = JSON.parse(localStorage.getItem('six-degrees-hide-chrome') || 'false') === true; } catch {}
|
|
218
|
-
started = await startScrape(action, name, { ...(id ? { id } : {}), ...(hidden ? { headless: true } : {}), ...extra });
|
|
230
|
+
// startScrape adds "Hide the Chrome window" itself (scanRequest), however the scan was started.
|
|
231
|
+
started = await startScrape(action, name, { ...(id ? { id } : {}), ...extra });
|
|
219
232
|
} catch (err) {
|
|
220
233
|
// Refused, often because something else runs: ask what, straight away.
|
|
221
234
|
// Until the answer comes, only our own claim is taken back.
|
package/lib/sector-focus.js
CHANGED
|
@@ -141,11 +141,11 @@ export function tierMoves(rows, tierBefore, tierAfter, { examples = 5, companyOf
|
|
|
141
141
|
*/
|
|
142
142
|
export function previewSectorFocus(rows = [], {
|
|
143
143
|
overrides = new Map(), industryOf, sectorsOf = sectorMatcher(), groupOf = sectorGroup, from = NO_FOCUS, to = NO_FOCUS, examples = 5,
|
|
144
|
-
tierScale, read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
|
|
144
|
+
tierScale, titles = null, read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
|
|
145
145
|
} = {}) {
|
|
146
|
-
// Both on the tier scale saved now (Scores → Tiers), as rescoring grades them.
|
|
147
|
-
const before = scoreNetwork(rows, { overrides, focus: from, tierScale, read });
|
|
148
|
-
const after = scoreNetwork(rows, { overrides, focus: to, tierScale, read });
|
|
146
|
+
// Both on the tier scale and title ranking saved now (Scores → Tiers, Titles), as rescoring grades them.
|
|
147
|
+
const before = scoreNetwork(rows, { overrides, focus: from, tierScale, titles, read });
|
|
148
|
+
const after = scoreNetwork(rows, { overrides, focus: to, tierScale, titles, read });
|
|
149
149
|
|
|
150
150
|
// Companies whose score moves, biggest first (by your people there now).
|
|
151
151
|
// Every company anyone names counts, a former employer too: its score moves
|