@@ -24,145 +24,6 @@ defmodule Algora.Admin do
2424
2525 require Logger
2626
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-
16627 def seed_job ( opts \\ % { } ) do
16728 with { :ok , user } <- Repo . fetch_by ( User , handle: opts . org . handle ) ,
16829 { :ok , user } <- user |> change ( opts . org ) |> Repo . update ( ) ,
0 commit comments