@@ -24,145 +24,6 @@ defmodule Algora.Admin do
24
24
25
25
require Logger
26
26
27
- defmodule JobPostingPrediction do
28
- @ moduledoc false
29
- use Ecto.Schema
30
- use Instructor.Validator
31
-
32
- alias Algora.Organizations
33
-
34
- @ llm_doc """
35
- ## Field Descriptions:
36
- - tech_stack: List of technologies used in the job posting (e.g. ["Ruby", "Rails", "PostgreSQL"])
37
- - countries: List of 2-letter ISO country codes (e.g. ["US", "CA"])
38
- - regions: List of regions (e.g. ["EMEA", "LATAM"])
39
- - location: Location of the job posting (e.g. "Remote", "San Francisco, CA", "London/Berlin")
40
- - seniority: Seniority level (e.g. "Senior", "Mid-Senior", "Entry-Level")
41
- - company_url: Company website URL which can be derived from email (e.g. example.com)
42
- """
43
- @ primary_key false
44
- embedded_schema do
45
- embeds_many :job_postings , JobPosting , primary_key: false do
46
- field ( :title , :string )
47
- field ( :description , :string )
48
- field ( :tech_stack , { :array , :string } )
49
- field ( :company_name , :string )
50
- field ( :company_url , :string )
51
- field ( :location , :string )
52
- field ( :countries , { :array , :string } )
53
- field ( :regions , { :array , :string } )
54
- field ( :compensation , :string )
55
- field ( :seniority , :string )
56
- end
57
- end
58
-
59
- @ impl true
60
- def validate_changeset ( changeset ) do
61
- changeset
62
- end
63
-
64
- def seed_jobs ( jobs ) do
65
- jobs
66
- |> Task . async_stream ( & seed / 1 , timeout: :infinity , max_concurrency: 50 )
67
- |> Enum . to_list ( )
68
- end
69
-
70
- def seed ( job ) do
71
- with domain when not is_nil ( domain ) <- Util . to_domain ( job . company_url ) ,
72
- { :ok , org } <- fetch_or_create_user ( domain , % { hiring: true , tech_stack: job . tech_stack } ) ,
73
- { :ok , org } <-
74
- org
75
- |> change (
76
- Map . merge (
77
- % {
78
- domain: org . domain || domain ,
79
- hiring_subscription: :trial ,
80
- billing_name: org . billing_name || job . company_name ,
81
- billing_address: org . billing_address || job . location ,
82
- executive_name: org . executive_name || job . company_name ,
83
- executive_role: org . executive_role || job . seniority
84
- } ,
85
- if org . handle do
86
- % { }
87
- else
88
- % { handle: Organizations . ensure_unique_org_handle ( job . company_name ) }
89
- end
90
- )
91
- )
92
- |> Repo . update ( ) do
93
- Repo . insert ( % JobPosting {
94
- status: :processing ,
95
- id: Nanoid . generate ( ) ,
96
- user_id: org . id ,
97
- company_name: org . name ,
98
- company_url: org . website_url ,
99
- title: job . title ,
100
- description: job . description ,
101
- tech_stack: job . tech_stack ,
102
- location: job . location ,
103
- compensation: job . compensation ,
104
- seniority: job . seniority ,
105
- countries: job . countries ,
106
- regions: job . regions
107
- } )
108
- end
109
- end
110
-
111
- def fetch_or_create_user ( domain , opts ) do
112
- case Repo . one ( from o in User , where: o . domain == ^ domain , limit: 1 ) do
113
- % User { } = user ->
114
- { :ok , user }
115
-
116
- _ ->
117
- res = Organizations . onboard_organization_from_domain ( domain , opts )
118
- res
119
- end
120
- end
121
- end
122
-
123
- def classify_jobs ( jobs ) do
124
- batches = Enum . chunk_every ( jobs , 10 )
125
-
126
- batches
127
- |> Enum . with_index ( )
128
- |> Enum . flat_map ( fn { jobs , index } ->
129
- case classify_batch ( jobs , index ) do
130
- { :ok , predictions } ->
131
- predictions . job_postings
132
-
133
- { :error , error } ->
134
- Logger . error ( "Failed to classify batch #{ index } : #{ inspect ( error ) } " )
135
- [ ]
136
- end
137
- end )
138
- end
139
-
140
- def classify_batch ( jobs , index ) do
141
- text = Enum . join ( jobs , "\n \n " )
142
-
143
- Github.Client . run_cached ( "classify_jobs_#{ index } " , fn ->
144
- Instructor . chat_completion (
145
- model: "gpt-4o-mini" ,
146
- response_model: JobPostingPrediction ,
147
- max_retries: 2 ,
148
- messages: [
149
- % {
150
- role: "user" ,
151
- content: """
152
- Your purpose is to turn arbitrary job postings into structured data.
153
-
154
- Return a distinct entry for each job. Some paragraphs may contain multiple jobs.
155
-
156
- Turn following job postings into structured data:
157
-
158
- #{ text }
159
- """
160
- }
161
- ]
162
- )
163
- end )
164
- end
165
-
166
27
def seed_job ( opts \\ % { } ) do
167
28
with { :ok , user } <- Repo . fetch_by ( User , handle: opts . org . handle ) ,
168
29
{ :ok , user } <- user |> change ( opts . org ) |> Repo . update ( ) ,
0 commit comments