stage-cli 1.0.0__py3-none-any.whl
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.
- stage/__init__.py +1 -0
- stage/__main__.py +8 -0
- stage/banner.py +32 -0
- stage/bootstrap/__init__.py +0 -0
- stage/bootstrap/openjobs.py +392 -0
- stage/classify/__init__.py +29 -0
- stage/classify/eligibility.py +115 -0
- stage/classify/internship.py +64 -0
- stage/classify/role.py +91 -0
- stage/classify/scope.py +47 -0
- stage/cli/__init__.py +0 -0
- stage/cli/app.py +4 -0
- stage/cli/commands/__init__.py +8 -0
- stage/cli/commands/discovery.py +294 -0
- stage/cli/commands/insight.py +494 -0
- stage/cli/commands/pipeline.py +337 -0
- stage/cli/commands/postings.py +473 -0
- stage/cli/commands/schedule.py +171 -0
- stage/cli/housekeeping.py +64 -0
- stage/cli/logfile.py +56 -0
- stage/cli/notify.py +170 -0
- stage/cli/options.py +678 -0
- stage/cli/render.py +1398 -0
- stage/cli/runlock.py +74 -0
- stage/cli/schedule.py +702 -0
- stage/cli/schedule_state.py +363 -0
- stage/cli/selection.py +83 -0
- stage/cli/serialize.py +196 -0
- stage/companies.py +542 -0
- stage/data/companies/a.yaml +1289 -0
- stage/data/companies/b.yaml +900 -0
- stage/data/companies/c.yaml +1377 -0
- stage/data/companies/d.yaml +497 -0
- stage/data/companies/e.yaml +519 -0
- stage/data/companies/f.yaml +454 -0
- stage/data/companies/g.yaml +601 -0
- stage/data/companies/h.yaml +446 -0
- stage/data/companies/i.yaml +503 -0
- stage/data/companies/j.yaml +138 -0
- stage/data/companies/k.yaml +278 -0
- stage/data/companies/l.yaml +402 -0
- stage/data/companies/m.yaml +937 -0
- stage/data/companies/n.yaml +549 -0
- stage/data/companies/o.yaml +371 -0
- stage/data/companies/other.yaml +58 -0
- stage/data/companies/p.yaml +825 -0
- stage/data/companies/q.yaml +121 -0
- stage/data/companies/r.yaml +583 -0
- stage/data/companies/s.yaml +1140 -0
- stage/data/companies/t.yaml +817 -0
- stage/data/companies/u.yaml +196 -0
- stage/data/companies/v.yaml +325 -0
- stage/data/companies/w.yaml +353 -0
- stage/data/companies/x.yaml +67 -0
- stage/data/companies/y.yaml +36 -0
- stage/data/companies/z.yaml +146 -0
- stage/data/fonts/DejaVuSans.LICENSE.txt +99 -0
- stage/data/fonts/DejaVuSans.ttf +0 -0
- stage/data/lexicon/company_tokens.yaml +228 -0
- stage/data/lexicon/eligibility.yaml +455 -0
- stage/data/lexicon/inclusive_suffixes.yaml +37 -0
- stage/data/lexicon/internship.yaml +187 -0
- stage/data/lexicon/language.yaml +226 -0
- stage/data/lexicon/locations.yaml +1159 -0
- stage/data/lexicon/roles.yaml +2012 -0
- stage/data/lexicon/terms.yaml +76 -0
- stage/data/lexicon/workday_facets.yaml +27 -0
- stage/data/seed_companies.yaml +198 -0
- stage/dedup/__init__.py +19 -0
- stage/dedup/identity.py +113 -0
- stage/dedup/resolve.py +97 -0
- stage/domain/__init__.py +244 -0
- stage/domain/company.py +49 -0
- stage/domain/coverage.py +86 -0
- stage/domain/custom_board.py +92 -0
- stage/domain/discovery.py +94 -0
- stage/domain/enums.py +114 -0
- stage/domain/events.py +204 -0
- stage/domain/filters.py +27 -0
- stage/domain/health.py +169 -0
- stage/domain/ids.py +48 -0
- stage/domain/job.py +47 -0
- stage/domain/matching.py +15 -0
- stage/domain/priority.py +34 -0
- stage/domain/quarantine.py +39 -0
- stage/domain/rate_state.py +78 -0
- stage/domain/retention.py +20 -0
- stage/domain/rotation.py +46 -0
- stage/domain/signals.py +12 -0
- stage/domain/sync_run.py +35 -0
- stage/domain/text.py +113 -0
- stage/domain/validator.py +14 -0
- stage/domain/visits.py +60 -0
- stage/domain/workday.py +38 -0
- stage/http/__init__.py +58 -0
- stage/http/breaker.py +53 -0
- stage/http/cache.py +44 -0
- stage/http/client.py +725 -0
- stage/http/profiles.py +101 -0
- stage/lexicon.py +370 -0
- stage/normalize/__init__.py +16 -0
- stage/normalize/language.py +47 -0
- stage/normalize/location.py +271 -0
- stage/normalize/terms.py +153 -0
- stage/normalize/urls.py +122 -0
- stage/paths.py +86 -0
- stage/py.typed +0 -0
- stage/services/__init__.py +0 -0
- stage/services/canary.py +120 -0
- stage/services/coverage.py +231 -0
- stage/services/discover.py +747 -0
- stage/services/export.py +274 -0
- stage/services/health.py +237 -0
- stage/services/maintenance.py +225 -0
- stage/services/quarantine.py +20 -0
- stage/services/query.py +86 -0
- stage/services/sync.py +1257 -0
- stage/sources/__init__.py +82 -0
- stage/sources/_text.py +79 -0
- stage/sources/ashby.py +93 -0
- stage/sources/bamboohr.py +80 -0
- stage/sources/base.py +225 -0
- stage/sources/breezy.py +90 -0
- stage/sources/collage.py +60 -0
- stage/sources/community_feeds.py +142 -0
- stage/sources/curated_markdown.py +289 -0
- stage/sources/custom_json.py +610 -0
- stage/sources/espresso.py +154 -0
- stage/sources/feed.py +44 -0
- stage/sources/greenhouse.py +104 -0
- stage/sources/jobbank.py +147 -0
- stage/sources/jobvite.py +133 -0
- stage/sources/lever.py +76 -0
- stage/sources/oracle_cloud.py +187 -0
- stage/sources/platforms.py +609 -0
- stage/sources/quebec_emploi.py +146 -0
- stage/sources/recruitee.py +96 -0
- stage/sources/simplify.py +110 -0
- stage/sources/smartrecruiters.py +216 -0
- stage/sources/speedyapply.py +200 -0
- stage/sources/themuse.py +157 -0
- stage/sources/workable.py +83 -0
- stage/sources/workday.py +524 -0
- stage/sources/zshah.py +99 -0
- stage/storage/__init__.py +29 -0
- stage/storage/migrations/0001_initial.sql +239 -0
- stage/storage/migrations/__init__.py +135 -0
- stage/storage/repository.py +213 -0
- stage/storage/search.py +28 -0
- stage/storage/sqlite_repo.py +1586 -0
- stage/storage/writer.py +249 -0
- stage/tui/__init__.py +0 -0
- stage/tui/app.py +82 -0
- stage/tui/help.py +26 -0
- stage/tui/safe.py +21 -0
- stage/tui/screens/__init__.py +0 -0
- stage/tui/screens/boards.py +186 -0
- stage/tui/screens/postings.py +509 -0
- stage/tui/screens/review.py +209 -0
- stage/tui/screens/splash.py +37 -0
- stage/tui/screens/stats.py +124 -0
- stage/tui/screens/sync.py +194 -0
- stage/tui/state.py +160 -0
- stage/tui/theme.tcss +205 -0
- stage/tui/widgets/__init__.py +0 -0
- stage_cli-1.0.0.dist-info/METADATA +379 -0
- stage_cli-1.0.0.dist-info/RECORD +170 -0
- stage_cli-1.0.0.dist-info/WHEEL +4 -0
- stage_cli-1.0.0.dist-info/entry_points.txt +2 -0
- stage_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
summer:
|
|
2
|
+
- summer
|
|
3
|
+
- été
|
|
4
|
+
- ete
|
|
5
|
+
- estival
|
|
6
|
+
- estivale
|
|
7
|
+
- summer term
|
|
8
|
+
- session d'été
|
|
9
|
+
- stage d'été
|
|
10
|
+
|
|
11
|
+
fall:
|
|
12
|
+
- fall
|
|
13
|
+
- autumn
|
|
14
|
+
- automne
|
|
15
|
+
- fall term
|
|
16
|
+
- session d'automne
|
|
17
|
+
- stage d'automne
|
|
18
|
+
|
|
19
|
+
winter:
|
|
20
|
+
- winter
|
|
21
|
+
- hiver
|
|
22
|
+
- hivernale
|
|
23
|
+
- winter term
|
|
24
|
+
- session d'hiver
|
|
25
|
+
- stage d'hiver
|
|
26
|
+
|
|
27
|
+
spring:
|
|
28
|
+
- spring
|
|
29
|
+
- printemps
|
|
30
|
+
- spring term
|
|
31
|
+
- session du printemps
|
|
32
|
+
- stage du printemps
|
|
33
|
+
|
|
34
|
+
fillers:
|
|
35
|
+
- of
|
|
36
|
+
- de
|
|
37
|
+
- du
|
|
38
|
+
- des
|
|
39
|
+
- d
|
|
40
|
+
- the
|
|
41
|
+
- la
|
|
42
|
+
- le
|
|
43
|
+
- term
|
|
44
|
+
- session
|
|
45
|
+
- start
|
|
46
|
+
- starting
|
|
47
|
+
- intake
|
|
48
|
+
- cohort
|
|
49
|
+
- programme
|
|
50
|
+
- program
|
|
51
|
+
|
|
52
|
+
blocked_bigrams:
|
|
53
|
+
- spring boot
|
|
54
|
+
- spring framework
|
|
55
|
+
- spring frameworks
|
|
56
|
+
- spring security
|
|
57
|
+
- spring mvc
|
|
58
|
+
- spring data
|
|
59
|
+
- spring cloud
|
|
60
|
+
- spring batch
|
|
61
|
+
- spring actuator
|
|
62
|
+
- spring health
|
|
63
|
+
- spring environments
|
|
64
|
+
- spring integration
|
|
65
|
+
- spring webflux
|
|
66
|
+
- summer camp
|
|
67
|
+
- winter garden
|
|
68
|
+
- fall protection
|
|
69
|
+
- fall risk
|
|
70
|
+
- fall arrest
|
|
71
|
+
- spring loaded
|
|
72
|
+
- spring valley
|
|
73
|
+
- winter park
|
|
74
|
+
- summer street
|
|
75
|
+
- spring street
|
|
76
|
+
- spring hill
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
facet_parameters:
|
|
2
|
+
- timeType
|
|
3
|
+
- workerSubType
|
|
4
|
+
- jobFamily
|
|
5
|
+
- employmentType
|
|
6
|
+
|
|
7
|
+
intern_descriptors:
|
|
8
|
+
- intern
|
|
9
|
+
- interns
|
|
10
|
+
- internship
|
|
11
|
+
- internships
|
|
12
|
+
- intern/co-op
|
|
13
|
+
- co-op
|
|
14
|
+
- coop
|
|
15
|
+
- co-op/intern
|
|
16
|
+
- co-op student
|
|
17
|
+
- stage
|
|
18
|
+
- stagiaire
|
|
19
|
+
- stage etudiant
|
|
20
|
+
- stagiaire etudiant
|
|
21
|
+
- student
|
|
22
|
+
- student worker
|
|
23
|
+
- etudiant
|
|
24
|
+
- alternance
|
|
25
|
+
- alternant
|
|
26
|
+
- summer intern
|
|
27
|
+
- summer student
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
- { name: Behaviour Interactive, section: montreal, priority: high }
|
|
2
|
+
- { name: Bombardier, section: montreal, priority: high }
|
|
3
|
+
- { name: CAE, section: montreal, priority: high }
|
|
4
|
+
- { name: Coveo, section: montreal, priority: high }
|
|
5
|
+
- { name: Desjardins, section: montreal, priority: high }
|
|
6
|
+
- { name: Eidos-Montréal, section: montreal, priority: high }
|
|
7
|
+
- { name: Gameloft, section: montreal, priority: high }
|
|
8
|
+
- { name: Genetec, section: montreal, priority: high }
|
|
9
|
+
- { name: Hydro-Québec, section: montreal, priority: high }
|
|
10
|
+
- { name: Intact, section: montreal, priority: high }
|
|
11
|
+
- { name: Laurentian Bank, section: montreal, priority: high }
|
|
12
|
+
- { name: Lightspeed, section: montreal, priority: high }
|
|
13
|
+
- { name: Matrox, section: montreal, priority: high }
|
|
14
|
+
- { name: Mila, section: montreal, priority: high }
|
|
15
|
+
- { name: National Bank of Canada, section: montreal, priority: high }
|
|
16
|
+
- { name: Nuvei, section: montreal, priority: high, note: not listed, consider }
|
|
17
|
+
- { name: Pratt & Whitney Canada, section: montreal, priority: high }
|
|
18
|
+
- { name: Squarepoint Capital, section: montreal, priority: high }
|
|
19
|
+
- { name: Ubisoft, section: montreal, priority: high }
|
|
20
|
+
- { name: Valsoft, section: montreal, priority: high }
|
|
21
|
+
- { name: Videotron, section: montreal, priority: high }
|
|
22
|
+
- { name: Warner Bros Games, section: montreal, priority: high }
|
|
23
|
+
|
|
24
|
+
- { name: BlackBerry, section: canada, priority: high }
|
|
25
|
+
- { name: BlackBerry QNX, section: canada, priority: high }
|
|
26
|
+
- { name: BMO, section: canada, priority: high }
|
|
27
|
+
- { name: Bell, section: canada, priority: high }
|
|
28
|
+
- { name: CGI, section: canada, priority: high }
|
|
29
|
+
- { name: CIBC, section: canada, priority: high }
|
|
30
|
+
- { name: Ciena, section: canada, priority: high }
|
|
31
|
+
- { name: Interac, section: canada, priority: high }
|
|
32
|
+
- { name: Mackenzie Investments, section: canada, priority: high }
|
|
33
|
+
- { name: Manulife, section: canada, priority: high }
|
|
34
|
+
- { name: MDA Space, section: canada, priority: high }
|
|
35
|
+
- { name: OpenText, section: canada, priority: high, note: not listed, consider }
|
|
36
|
+
- { name: RBC, section: canada, priority: high }
|
|
37
|
+
- { name: Rogers, section: canada, priority: high }
|
|
38
|
+
- { name: Scotiabank, section: canada, priority: high }
|
|
39
|
+
- { name: Shopify, section: canada, priority: high }
|
|
40
|
+
- { name: Sun Life, section: canada, priority: high }
|
|
41
|
+
- { name: TD Bank, section: canada, priority: high }
|
|
42
|
+
- { name: Telus, section: canada, priority: high }
|
|
43
|
+
- { name: Wealthsimple, section: canada, priority: high }
|
|
44
|
+
|
|
45
|
+
- { name: Akuna Capital, section: quant, priority: normal }
|
|
46
|
+
- { name: Citadel, section: quant, priority: normal }
|
|
47
|
+
- { name: Citadel Securities, section: quant, priority: normal }
|
|
48
|
+
- { name: D.E. Shaw, section: quant, priority: normal }
|
|
49
|
+
- { name: DRW, section: quant, priority: normal }
|
|
50
|
+
- { name: Hudson River Trading, section: quant, priority: normal }
|
|
51
|
+
- { name: Jane Street, section: quant, priority: normal }
|
|
52
|
+
- { name: Jump Trading, section: quant, priority: normal }
|
|
53
|
+
- { name: Maven Securities, section: quant, priority: normal }
|
|
54
|
+
- { name: Optiver, section: quant, priority: normal }
|
|
55
|
+
- { name: Renaissance Technologies, section: quant, priority: low, note: does not recruit publicly }
|
|
56
|
+
- { name: Tower Research Capital, section: quant, priority: normal }
|
|
57
|
+
- { name: Two Sigma, section: quant, priority: normal }
|
|
58
|
+
|
|
59
|
+
- { name: Adobe, section: bigtech, priority: normal }
|
|
60
|
+
- { name: Amazon, section: bigtech, priority: normal }
|
|
61
|
+
- { name: AMD, section: bigtech, priority: normal }
|
|
62
|
+
- { name: Apple, section: bigtech, priority: normal }
|
|
63
|
+
- { name: Broadcom, section: bigtech, priority: normal }
|
|
64
|
+
- { name: ByteDance, section: bigtech, priority: normal }
|
|
65
|
+
- { name: Cisco, section: bigtech, priority: normal }
|
|
66
|
+
- { name: Cisco Meraki, section: bigtech, priority: normal }
|
|
67
|
+
- { name: Cisco ThousandEyes, section: bigtech, priority: normal }
|
|
68
|
+
- { name: Dell, section: bigtech, priority: normal }
|
|
69
|
+
- { name: Ericsson, section: bigtech, priority: normal }
|
|
70
|
+
- { name: Google, section: bigtech, priority: normal }
|
|
71
|
+
- { name: Hewlett Packard Enterprise, section: bigtech, priority: normal }
|
|
72
|
+
- { name: Hitachi, section: bigtech, priority: low, note: low yield for a Montreal student }
|
|
73
|
+
- { name: Huawei, section: bigtech, priority: normal }
|
|
74
|
+
- { name: IBM, section: bigtech, priority: normal }
|
|
75
|
+
- { name: Intel, section: bigtech, priority: normal }
|
|
76
|
+
- { name: LinkedIn, section: bigtech, priority: normal }
|
|
77
|
+
- { name: Meta, section: bigtech, priority: normal }
|
|
78
|
+
- { name: Microsoft, section: bigtech, priority: normal }
|
|
79
|
+
- { name: Motorola, section: bigtech, priority: low, note: low yield for a Montreal student }
|
|
80
|
+
- { name: Netflix, section: bigtech, priority: normal }
|
|
81
|
+
- { name: Nokia, section: bigtech, priority: normal }
|
|
82
|
+
- { name: NVIDIA, section: bigtech, priority: normal }
|
|
83
|
+
- { name: Oracle, section: bigtech, priority: normal }
|
|
84
|
+
- { name: Qualcomm, section: bigtech, priority: normal, note: not listed }
|
|
85
|
+
- { name: Salesforce, section: bigtech, priority: normal }
|
|
86
|
+
- { name: Samsung, section: bigtech, priority: normal }
|
|
87
|
+
- { name: SAP, section: bigtech, priority: normal }
|
|
88
|
+
- { name: Slack, section: bigtech, priority: normal }
|
|
89
|
+
- { name: Sony Interactive Entertainment, section: bigtech, priority: normal }
|
|
90
|
+
- { name: TikTok, section: bigtech, priority: normal }
|
|
91
|
+
- { name: Tesla, section: bigtech, priority: normal }
|
|
92
|
+
- { name: Uber, section: bigtech, priority: normal }
|
|
93
|
+
- { name: VMware, section: bigtech, priority: normal }
|
|
94
|
+
- { name: X (Twitter), section: bigtech, priority: normal }
|
|
95
|
+
- { name: Zoom, section: bigtech, priority: normal }
|
|
96
|
+
|
|
97
|
+
- { name: American Express, section: finance, priority: normal }
|
|
98
|
+
- { name: Bank of America, section: finance, priority: normal }
|
|
99
|
+
- { name: BlackRock, section: finance, priority: normal }
|
|
100
|
+
- { name: Bloomberg, section: finance, priority: normal }
|
|
101
|
+
- { name: BNP Paribas, section: finance, priority: normal }
|
|
102
|
+
- { name: Capital One, section: finance, priority: normal }
|
|
103
|
+
- { name: Citigroup, section: finance, priority: normal }
|
|
104
|
+
- { name: Fidelity Investments, section: finance, priority: normal }
|
|
105
|
+
- { name: Goldman Sachs, section: finance, priority: normal }
|
|
106
|
+
- { name: JPMorgan Chase, section: finance, priority: normal }
|
|
107
|
+
- { name: Mastercard, section: finance, priority: normal }
|
|
108
|
+
- { name: Morgan Stanley, section: finance, priority: normal }
|
|
109
|
+
- { name: PayPal, section: finance, priority: normal }
|
|
110
|
+
- { name: Visa, section: finance, priority: normal }
|
|
111
|
+
- { name: Wells Fargo, section: finance, priority: normal }
|
|
112
|
+
|
|
113
|
+
- { name: Accenture, section: consulting, priority: normal, note: not listed, consider }
|
|
114
|
+
- { name: Deloitte, section: consulting, priority: normal }
|
|
115
|
+
- { name: KPMG, section: consulting, priority: normal }
|
|
116
|
+
- { name: PwC, section: consulting, priority: normal }
|
|
117
|
+
- { name: Red Hat, section: consulting, priority: normal }
|
|
118
|
+
- { name: Splunk, section: consulting, priority: normal }
|
|
119
|
+
|
|
120
|
+
- { name: Analog Devices, section: industrial, priority: normal }
|
|
121
|
+
- { name: Anduril Industries, section: industrial, priority: normal }
|
|
122
|
+
- { name: BAE Systems, section: industrial, priority: normal }
|
|
123
|
+
- { name: Ford, section: industrial, priority: low, note: low yield for a Montreal student }
|
|
124
|
+
- { name: General Motors, section: industrial, priority: low, note: low yield for a Montreal student }
|
|
125
|
+
- { name: Lockheed Martin, section: industrial, priority: normal }
|
|
126
|
+
- { name: SpaceX, section: industrial, priority: normal }
|
|
127
|
+
- { name: Waymo, section: industrial, priority: normal }
|
|
128
|
+
|
|
129
|
+
- { name: 1Password, section: security, priority: normal }
|
|
130
|
+
- { name: Akamai, section: security, priority: normal }
|
|
131
|
+
- { name: Arctic Wolf, section: security, priority: normal }
|
|
132
|
+
- { name: Cloudflare, section: security, priority: normal }
|
|
133
|
+
- { name: CrowdStrike, section: security, priority: normal }
|
|
134
|
+
- { name: Fortinet, section: security, priority: normal }
|
|
135
|
+
- { name: Okta, section: security, priority: normal, note: not listed }
|
|
136
|
+
- { name: Palo Alto Networks, section: security, priority: normal }
|
|
137
|
+
- { name: Wiz, section: security, priority: normal }
|
|
138
|
+
|
|
139
|
+
- { name: Affirm, section: startups, priority: normal }
|
|
140
|
+
- { name: Airbnb, section: startups, priority: normal }
|
|
141
|
+
- { name: Atlassian, section: startups, priority: normal }
|
|
142
|
+
- { name: Autodesk, section: startups, priority: normal }
|
|
143
|
+
- { name: Bumble, section: startups, priority: normal }
|
|
144
|
+
- { name: Canva, section: startups, priority: normal }
|
|
145
|
+
- { name: CircleCI, section: startups, priority: normal }
|
|
146
|
+
- { name: Coinbase, section: startups, priority: normal }
|
|
147
|
+
- { name: Databricks, section: startups, priority: normal }
|
|
148
|
+
- { name: Datadog, section: startups, priority: normal }
|
|
149
|
+
- { name: DigitalOcean, section: startups, priority: normal }
|
|
150
|
+
- { name: Discord, section: startups, priority: normal }
|
|
151
|
+
- { name: Docker, section: startups, priority: normal }
|
|
152
|
+
- { name: DoorDash, section: startups, priority: normal }
|
|
153
|
+
- { name: Dropbox, section: startups, priority: normal }
|
|
154
|
+
- { name: Duolingo, section: startups, priority: normal }
|
|
155
|
+
- { name: eBay, section: startups, priority: normal }
|
|
156
|
+
- { name: Expedia, section: startups, priority: normal }
|
|
157
|
+
- { name: Figma, section: startups, priority: normal }
|
|
158
|
+
- { name: GitHub, section: startups, priority: normal }
|
|
159
|
+
- { name: Grammarly, section: startups, priority: normal }
|
|
160
|
+
- { name: HashiCorp, section: startups, priority: normal }
|
|
161
|
+
- { name: Hugging Face, section: startups, priority: normal }
|
|
162
|
+
- { name: Instacart, section: startups, priority: normal }
|
|
163
|
+
- { name: Lyft, section: startups, priority: normal }
|
|
164
|
+
- { name: MongoDB, section: startups, priority: normal }
|
|
165
|
+
- { name: Notion, section: startups, priority: normal }
|
|
166
|
+
- { name: Palantir, section: startups, priority: normal }
|
|
167
|
+
- { name: Pinterest, section: startups, priority: normal }
|
|
168
|
+
- { name: Postman, section: startups, priority: normal }
|
|
169
|
+
- { name: Quora, section: startups, priority: normal }
|
|
170
|
+
- { name: Reddit, section: startups, priority: normal }
|
|
171
|
+
- { name: Roblox, section: startups, priority: normal }
|
|
172
|
+
- { name: Scale AI, section: startups, priority: normal }
|
|
173
|
+
- { name: Snowflake, section: startups, priority: normal }
|
|
174
|
+
- { name: Spotify, section: startups, priority: normal }
|
|
175
|
+
- { name: Stripe, section: startups, priority: normal }
|
|
176
|
+
- { name: Supabase, section: startups, priority: normal }
|
|
177
|
+
- { name: Twilio, section: startups, priority: normal }
|
|
178
|
+
- { name: Unity, section: startups, priority: normal }
|
|
179
|
+
- { name: Wayfair, section: startups, priority: normal }
|
|
180
|
+
- { name: Yelp, section: startups, priority: normal }
|
|
181
|
+
- { name: Zillow, section: startups, priority: normal }
|
|
182
|
+
|
|
183
|
+
- { name: Activision Blizzard, section: gaming, priority: normal }
|
|
184
|
+
- { name: Bandai Namco, section: gaming, priority: low, note: negligible North American CS pipeline }
|
|
185
|
+
- { name: Blackbird Interactive, section: gaming, priority: normal }
|
|
186
|
+
- { name: Bungie, section: gaming, priority: normal }
|
|
187
|
+
- { name: Capcom, section: gaming, priority: low, note: negligible North American CS pipeline }
|
|
188
|
+
- { name: CD Projekt Red, section: gaming, priority: normal }
|
|
189
|
+
- { name: EA, section: gaming, priority: normal }
|
|
190
|
+
- { name: Epic Games, section: gaming, priority: normal }
|
|
191
|
+
- { name: FromSoftware, section: gaming, priority: low, note: negligible North American CS pipeline }
|
|
192
|
+
- { name: Halo Studios, section: gaming, priority: normal }
|
|
193
|
+
- { name: Microsoft Xbox Game Studios, section: gaming, priority: normal }
|
|
194
|
+
- { name: Nintendo, section: gaming, priority: low, note: negligible North American CS pipeline }
|
|
195
|
+
- { name: Riot Games, section: gaming, priority: normal }
|
|
196
|
+
- { name: Square Enix, section: gaming, priority: low, note: negligible North American CS pipeline }
|
|
197
|
+
- { name: Supercell, section: gaming, priority: low, note: negligible North American CS pipeline }
|
|
198
|
+
- { name: Take-Two Interactive, section: gaming, priority: normal }
|
stage/dedup/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from stage.dedup.identity import (
|
|
2
|
+
SOURCE_PRIORITY,
|
|
3
|
+
MatchKind,
|
|
4
|
+
MatchResult,
|
|
5
|
+
title_canonical,
|
|
6
|
+
would_merge,
|
|
7
|
+
)
|
|
8
|
+
from stage.dedup.resolve import DuplicateLink, rank, resolve_duplicates
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"SOURCE_PRIORITY",
|
|
12
|
+
"DuplicateLink",
|
|
13
|
+
"MatchKind",
|
|
14
|
+
"MatchResult",
|
|
15
|
+
"rank",
|
|
16
|
+
"resolve_duplicates",
|
|
17
|
+
"title_canonical",
|
|
18
|
+
"would_merge",
|
|
19
|
+
]
|
stage/dedup/identity.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
|
|
4
|
+
from stage.domain import (
|
|
5
|
+
SOURCE_PRIORITY,
|
|
6
|
+
Job,
|
|
7
|
+
Language,
|
|
8
|
+
location_agrees,
|
|
9
|
+
term_agrees,
|
|
10
|
+
)
|
|
11
|
+
from stage.lexicon import (
|
|
12
|
+
division_qualifiers,
|
|
13
|
+
fold,
|
|
14
|
+
generic_company_tokens,
|
|
15
|
+
name_root_tokens,
|
|
16
|
+
role_lexicon,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"SOURCE_PRIORITY",
|
|
21
|
+
"MatchKind",
|
|
22
|
+
"MatchResult",
|
|
23
|
+
"title_canonical",
|
|
24
|
+
"would_merge",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MatchKind(StrEnum):
|
|
29
|
+
NONE = "none"
|
|
30
|
+
URL = "url"
|
|
31
|
+
SAME_LANGUAGE = "same-language"
|
|
32
|
+
CROSS_LANGUAGE = "cross-language"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class MatchResult:
|
|
37
|
+
kind: MatchKind = MatchKind.NONE
|
|
38
|
+
evidence: str = ""
|
|
39
|
+
|
|
40
|
+
def __bool__(self) -> bool:
|
|
41
|
+
return self.kind is not MatchKind.NONE
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def title_canonical(title: str) -> str:
|
|
45
|
+
folded = fold(title)
|
|
46
|
+
if not folded:
|
|
47
|
+
return ""
|
|
48
|
+
padded = f" {folded} "
|
|
49
|
+
hits: list[tuple[int, str]] = []
|
|
50
|
+
for category, phrases in role_lexicon().items():
|
|
51
|
+
matched = [phrase for phrase in phrases if f" {phrase} " in padded]
|
|
52
|
+
if matched:
|
|
53
|
+
hits.append((max(len(phrase) for phrase in matched), category))
|
|
54
|
+
if not hits:
|
|
55
|
+
return ""
|
|
56
|
+
best = max(length for length, _ in hits)
|
|
57
|
+
winners = sorted(category for length, category in hits if length == best)
|
|
58
|
+
return winners[0] if len(winners) == 1 else ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _title_tokens(job: Job) -> frozenset[str]:
|
|
62
|
+
return frozenset(fold(job.title_raw).split())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _qualified_prefix(shorter: tuple[str, ...], longer: tuple[str, ...]) -> bool:
|
|
66
|
+
if not shorter or longer[: len(shorter)] != shorter:
|
|
67
|
+
return False
|
|
68
|
+
if not set(longer[len(shorter) :]) <= division_qualifiers():
|
|
69
|
+
return False
|
|
70
|
+
return bool(set(shorter) - generic_company_tokens())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _company_matches(left: Job, right: Job) -> bool:
|
|
74
|
+
if fold(left.company) == fold(right.company):
|
|
75
|
+
return True
|
|
76
|
+
first, second = name_root_tokens(left.company), name_root_tokens(right.company)
|
|
77
|
+
if first == second:
|
|
78
|
+
return True
|
|
79
|
+
shorter, longer = sorted((first, second), key=len)
|
|
80
|
+
return _qualified_prefix(shorter, longer)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def would_merge(left: Job, right: Job) -> MatchResult:
|
|
84
|
+
if left.board_key == right.board_key:
|
|
85
|
+
return MatchResult()
|
|
86
|
+
|
|
87
|
+
if left.apply_url_canonical and left.apply_url_canonical == right.apply_url_canonical:
|
|
88
|
+
return MatchResult(MatchKind.URL, left.apply_url_canonical)
|
|
89
|
+
|
|
90
|
+
if not _company_matches(left, right):
|
|
91
|
+
return MatchResult()
|
|
92
|
+
|
|
93
|
+
if not location_agrees(left.location, right.location):
|
|
94
|
+
return MatchResult()
|
|
95
|
+
|
|
96
|
+
left_tokens, right_tokens = _title_tokens(left), _title_tokens(right)
|
|
97
|
+
if left_tokens and left_tokens == right_tokens:
|
|
98
|
+
return MatchResult(MatchKind.SAME_LANGUAGE, " ".join(sorted(left_tokens)))
|
|
99
|
+
|
|
100
|
+
return _cross_language(left, right)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _cross_language(left: Job, right: Job) -> MatchResult:
|
|
104
|
+
pair = {left.language, right.language}
|
|
105
|
+
if Language.EN not in pair or Language.FR not in pair:
|
|
106
|
+
return MatchResult()
|
|
107
|
+
if not term_agrees(left.term, right.term):
|
|
108
|
+
return MatchResult()
|
|
109
|
+
|
|
110
|
+
canonical = title_canonical(left.title_raw)
|
|
111
|
+
if not canonical or canonical != title_canonical(right.title_raw):
|
|
112
|
+
return MatchResult()
|
|
113
|
+
return MatchResult(MatchKind.CROSS_LANGUAGE, canonical)
|
stage/dedup/resolve.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from stage.dedup.identity import MatchKind, would_merge
|
|
5
|
+
from stage.domain import Job, source_rank
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class DuplicateLink:
|
|
10
|
+
duplicate_id: str
|
|
11
|
+
canonical_id: str
|
|
12
|
+
kind: str
|
|
13
|
+
evidence: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def rank(job: Job) -> tuple[int, str]:
|
|
17
|
+
return source_rank(job.source, job.id)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _ambiguous_urls(jobs: Sequence[Job]) -> frozenset[str]:
|
|
21
|
+
seen: dict[tuple[str, str], int] = {}
|
|
22
|
+
for job in jobs:
|
|
23
|
+
if not job.apply_url_canonical:
|
|
24
|
+
continue
|
|
25
|
+
key = (job.source, job.apply_url_canonical)
|
|
26
|
+
seen[key] = seen.get(key, 0) + 1
|
|
27
|
+
return frozenset(url for (_, url), count in seen.items() if count > 1)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_duplicates(
|
|
31
|
+
incoming: Sequence[Job], existing: Sequence[Job]
|
|
32
|
+
) -> tuple[DuplicateLink, ...]:
|
|
33
|
+
pool: dict[str, Job] = {job.id: job for job in existing}
|
|
34
|
+
for job in incoming:
|
|
35
|
+
pool[job.id] = job
|
|
36
|
+
jobs = sorted(pool.values(), key=rank)
|
|
37
|
+
ambiguous = _ambiguous_urls(jobs)
|
|
38
|
+
|
|
39
|
+
parent: dict[str, str] = {job.id: job.id for job in jobs}
|
|
40
|
+
boards: dict[str, set[str]] = {job.id: {job.board_key} for job in jobs}
|
|
41
|
+
sources: dict[str, set[str]] = {job.id: {job.source} for job in jobs}
|
|
42
|
+
reason: dict[str, tuple[str, str]] = {}
|
|
43
|
+
|
|
44
|
+
def find(job_id: str) -> str:
|
|
45
|
+
while parent[job_id] != job_id:
|
|
46
|
+
parent[job_id] = parent[parent[job_id]]
|
|
47
|
+
job_id = parent[job_id]
|
|
48
|
+
return job_id
|
|
49
|
+
|
|
50
|
+
order = {job.id: index for index, job in enumerate(jobs)}
|
|
51
|
+
|
|
52
|
+
def union(left_id: str, right_id: str) -> None:
|
|
53
|
+
left_root, right_root = find(left_id), find(right_id)
|
|
54
|
+
if left_root == right_root:
|
|
55
|
+
return
|
|
56
|
+
if order[right_root] < order[left_root]:
|
|
57
|
+
left_root, right_root = right_root, left_root
|
|
58
|
+
parent[right_root] = left_root
|
|
59
|
+
boards[left_root] |= boards[right_root]
|
|
60
|
+
sources[left_root] |= sources[right_root]
|
|
61
|
+
|
|
62
|
+
for job in jobs:
|
|
63
|
+
if job.duplicate_of and job.duplicate_of in parent:
|
|
64
|
+
union(job.id, job.duplicate_of)
|
|
65
|
+
|
|
66
|
+
touched = {job.id for job in incoming}
|
|
67
|
+
for index, left in enumerate(jobs):
|
|
68
|
+
for right in jobs[index + 1 :]:
|
|
69
|
+
if left.id not in touched and right.id not in touched:
|
|
70
|
+
continue
|
|
71
|
+
left_root, right_root = find(left.id), find(right.id)
|
|
72
|
+
if left_root == right_root:
|
|
73
|
+
continue
|
|
74
|
+
if boards[left_root] & boards[right_root] or sources[left_root] & sources[right_root]:
|
|
75
|
+
continue
|
|
76
|
+
match = would_merge(left, right)
|
|
77
|
+
if not match or (match.kind is MatchKind.URL and left.apply_url_canonical in ambiguous):
|
|
78
|
+
continue
|
|
79
|
+
union(left.id, right.id)
|
|
80
|
+
demoted = right.id if find(right.id) != right_root else left.id
|
|
81
|
+
reason[demoted] = (match.kind.value, match.evidence)
|
|
82
|
+
|
|
83
|
+
links: list[DuplicateLink] = []
|
|
84
|
+
for job in jobs:
|
|
85
|
+
canonical = find(job.id)
|
|
86
|
+
if canonical == job.id:
|
|
87
|
+
continue
|
|
88
|
+
kind, evidence = reason.get(job.id, ("", ""))
|
|
89
|
+
links.append(
|
|
90
|
+
DuplicateLink(
|
|
91
|
+
duplicate_id=job.id,
|
|
92
|
+
canonical_id=canonical,
|
|
93
|
+
kind=kind,
|
|
94
|
+
evidence=evidence,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
return tuple(links)
|