@@ -32,38 +32,207 @@ export async function fetchJobDetails(boardToken, jobId) {
3232 } ;
3333}
3434
35+ // Standard Greenhouse form fields — already set via form.set(), skip in question loop
36+ const STANDARD_FIELDS = new Set ( [
37+ 'first_name' , 'last_name' , 'email' , 'phone' , 'resume' , 'cover_letter' ,
38+ ] ) ;
39+
3540/**
3641 * Build auto-answerable question map from profile.
42+ * Returns Map<keyword, { text: string, selectMatch?: string }>
3743 */
3844function buildAutoAnswers ( profile , autoApplyConfig = { } ) {
3945 const answers = new Map ( ) ;
4046 const cand = profile . candidate || { } ;
47+ const loc = profile . location || { } ;
48+
49+ // Text answers
50+ answers . set ( 'linkedin' , { text : cand . linkedin || '' } ) ;
51+ answers . set ( 'github' , { text : cand . github || '' } ) ;
52+ answers . set ( 'gitlab' , { text : cand . github || '' } ) ;
53+ answers . set ( 'portfolio' , { text : cand . portfolio_url || '' } ) ;
54+ answers . set ( 'website' , { text : cand . portfolio_url || '' } ) ;
55+ answers . set ( 'phone' , { text : cand . phone || '' } ) ;
56+ answers . set ( 'location' , { text : cand . location || '' } ) ;
57+ answers . set ( 'city' , { text : cand . location || '' } ) ;
58+ answers . set ( 'preferred' , { text : ( cand . full_name || '' ) . split ( / \s + / ) [ 0 ] || '' } ) ;
59+ answers . set ( 'preferred name' , { text : ( cand . full_name || '' ) . split ( / \s + / ) [ 0 ] || '' } ) ;
60+ answers . set ( 'nickname' , { text : ( cand . full_name || '' ) . split ( / \s + / ) [ 0 ] || '' } ) ;
61+
62+ // Visa / sponsorship — text + select matching
63+ answers . set ( 'visa' , { text : autoApplyConfig . visa_status || 'No' , selectMatch : 'no' } ) ;
64+ answers . set ( 'authorized' , { text : 'Yes' , selectMatch : 'yes' } ) ;
65+ answers . set ( 'sponsorship' , { text : 'No' , selectMatch : 'no' } ) ;
66+ answers . set ( 'require sponsorship' , { text : 'No' , selectMatch : 'no' } ) ;
67+ answers . set ( 'sponsor you' , { text : 'No' , selectMatch : 'no' } ) ;
68+ answers . set ( 'sponsor' , { text : 'No' , selectMatch : 'no' } ) ;
69+
70+ // Start date / availability
71+ answers . set ( 'start' , { text : autoApplyConfig . start_date || '30 days notice period' } ) ;
72+ answers . set ( 'available' , { text : autoApplyConfig . start_date || '30 days notice period' } ) ;
73+ answers . set ( 'notice period' , { text : autoApplyConfig . start_date || '30 days' } ) ;
74+
75+ // Referral / source
76+ answers . set ( 'hear about' , { text : autoApplyConfig . referral_source || 'Company career page' } ) ;
77+ answers . set ( 'how did you' , { text : autoApplyConfig . referral_source || 'Company career page' } ) ;
78+ answers . set ( 'source' , { text : autoApplyConfig . referral_source || 'Company career page' } ) ;
79+ answers . set ( 'referral' , { text : autoApplyConfig . referral_source || 'Company career page' } ) ;
80+
81+ // Salary
82+ answers . set ( 'salary' , { text : autoApplyConfig . salary_expectation || 'Competitive, based on role' } ) ;
83+ answers . set ( 'compensation' , { text : autoApplyConfig . salary_expectation || 'Competitive, based on role' } ) ;
84+
85+ // Employment restrictions
86+ answers . set ( 'employment agreement' , { text : 'No' , selectMatch : 'no' } ) ;
87+ answers . set ( 'post-employment' , { text : 'No' , selectMatch : 'no' } ) ;
88+ answers . set ( 'non-compete' , { text : 'No' , selectMatch : 'no' } ) ;
89+ answers . set ( 'restrictive covenant' , { text : 'No' , selectMatch : 'no' } ) ;
90+
91+ // Country of residence
92+ answers . set ( 'country of residence' , { text : loc . country || 'India' , selectMatch : loc . country || 'india' } ) ;
93+ answers . set ( 'current country' , { text : loc . country || 'India' , selectMatch : loc . country || 'india' } ) ;
94+ answers . set ( 'where are you located' , { text : loc . country || 'India' , selectMatch : loc . country || 'india' } ) ;
95+ answers . set ( 'country where you currently reside' , { text : loc . country || 'India' , selectMatch : loc . country || 'india' } ) ;
96+ answers . set ( 'anticipate working in' , { text : loc . country || 'India' , selectMatch : loc . country || 'india' } ) ;
97+ answers . set ( 'country' , { text : loc . country || 'India' , selectMatch : loc . country || 'india' } ) ;
98+
99+ // Work authorization
100+ answers . set ( 'legally authorized' , { text : 'Yes' , selectMatch : 'yes' } ) ;
101+ answers . set ( 'work authorization' , { text : 'Yes' , selectMatch : 'yes' } ) ;
102+ answers . set ( 'right to work' , { text : 'Yes' , selectMatch : 'yes' } ) ;
103+ answers . set ( 'eligible to work' , { text : 'Yes' , selectMatch : 'yes' } ) ;
104+
105+ // Years of experience
106+ answers . set ( 'years of experience' , { text : '4' , selectMatch : '3-5' } ) ;
107+ answers . set ( 'experience' , { text : '4 years' } ) ;
108+ answers . set ( 'over 5 years' , { text : 'No' , selectMatch : 'no' } ) ;
109+ answers . set ( '5 years of professional' , { text : 'No' , selectMatch : 'no' } ) ;
110+
111+ // Current employer details (from profile narrative/proof_points)
112+ answers . set ( 'current firm' , { text : 'Zomato Limited' } ) ;
113+ answers . set ( 'current company' , { text : 'Zomato Limited' } ) ;
114+ answers . set ( 'current employer' , { text : 'Zomato Limited' } ) ;
115+ answers . set ( 'previous employer' , { text : 'Zomato Limited' } ) ;
116+ answers . set ( 'current title' , { text : 'Software Development Engineer II' } ) ;
117+ answers . set ( 'current or previous job title' , { text : 'Software Development Engineer II' } ) ;
118+ answers . set ( 'previous job title' , { text : 'Software Development Engineer II' } ) ;
119+
120+ // Education
121+ answers . set ( 'school' , { text : 'Indian Institute of Information Technology, Nagpur' } ) ;
122+ answers . set ( 'most recent school' , { text : 'Indian Institute of Information Technology, Nagpur' } ) ;
123+ answers . set ( 'university' , { text : 'Indian Institute of Information Technology, Nagpur' } ) ;
124+ answers . set ( 'degree' , { text : 'BTech in Computer Science & Engineering' } ) ;
125+ answers . set ( 'most recent degree' , { text : 'BTech in Computer Science & Engineering' } ) ;
126+
127+ // Previously worked at company X? → No
128+ answers . set ( 'previously worked' , { text : 'No' , selectMatch : 'no' } ) ;
129+ answers . set ( 'have you ever been employed' , { text : 'No' , selectMatch : 'no' } ) ;
130+ answers . set ( 'have you previously worked' , { text : 'No' , selectMatch : 'no' } ) ;
131+ answers . set ( 'do you currently work for' , { text : 'No' , selectMatch : 'no' } ) ;
132+ answers . set ( 'currently or have you previously' , { text : 'No' , selectMatch : 'no' } ) ;
133+ answers . set ( 'been involved in procurement' , { text : 'No' , selectMatch : 'no' } ) ;
134+
135+ // Do you live in sanctioned country? → No
136+ answers . set ( 'belarus, cuba' , { text : 'No' , selectMatch : 'no' } ) ;
137+ answers . set ( 'sanctioned' , { text : 'No' , selectMatch : 'no' } ) ;
138+ answers . set ( 'citizenship one of the following' , { text : 'No' , selectMatch : 'no' } ) ;
139+ answers . set ( 'permanent residency in one of' , { text : 'No' , selectMatch : 'no' } ) ;
140+
141+ // Acknowledge / confirm
142+ answers . set ( 'i acknowledge' , { text : 'I acknowledge' , selectMatch : 'i acknowledge' } ) ;
143+ answers . set ( 'acknowledge' , { text : 'I acknowledge' , selectMatch : 'i acknowledge' } ) ;
144+ answers . set ( 'privacy policy' , { text : 'I acknowledge' , selectMatch : 'i acknowledge' } ) ;
145+ answers . set ( 'confidential information' , { text : 'I acknowledge' , selectMatch : 'i acknowledge' } ) ;
146+
147+ // Opt-in for comms
148+ answers . set ( 'opt-in' , { text : 'Yes' , selectMatch : 'yes' } ) ;
149+ answers . set ( 'whatsapp' , { text : 'Yes' , selectMatch : 'yes' } ) ;
150+
151+ // Do you live in location? → Yes
152+ answers . set ( 'do you currently live' , { text : 'Yes' , selectMatch : 'yes' } ) ;
153+ answers . set ( 'this role is open to candidates' , { text : 'Yes' , selectMatch : 'yes' } ) ;
41154
42- answers . set ( 'linkedin' , cand . linkedin || '' ) ;
43- answers . set ( 'github' , cand . github || '' ) ;
44- answers . set ( 'portfolio' , cand . portfolio_url || '' ) ;
45- answers . set ( 'website' , cand . portfolio_url || '' ) ;
46- answers . set ( 'phone' , cand . phone || '' ) ;
47- answers . set ( 'location' , cand . location || '' ) ;
48- answers . set ( 'city' , cand . location || '' ) ;
49- answers . set ( 'visa' , autoApplyConfig . visa_status || '' ) ;
50- answers . set ( 'authorized' , autoApplyConfig . visa_status || '' ) ;
51- answers . set ( 'sponsorship' , autoApplyConfig . visa_status || '' ) ;
52- answers . set ( 'start' , autoApplyConfig . start_date || '' ) ;
53- answers . set ( 'available' , autoApplyConfig . start_date || '' ) ;
54- answers . set ( 'hear' , autoApplyConfig . referral_source || 'Company career page' ) ;
55- answers . set ( 'source' , autoApplyConfig . referral_source || 'Company career page' ) ;
56- answers . set ( 'referral' , autoApplyConfig . referral_source || 'Company career page' ) ;
57- answers . set ( 'salary' , autoApplyConfig . salary_expectation || '' ) ;
58- answers . set ( 'compensation' , autoApplyConfig . salary_expectation || '' ) ;
155+ // Remote work
156+ answers . set ( 'plan to work from' , { text : 'No' , selectMatch : 'no' } ) ;
157+ answers . set ( 'plan to work remotely' , { text : 'Yes' , selectMatch : 'yes' } ) ;
158+ answers . set ( 'work remotely' , { text : 'Yes' , selectMatch : 'yes' } ) ;
159+ answers . set ( 'intend to work' , { text : 'Yes' , selectMatch : 'yes' } ) ;
160+
161+ // Commercial experience questions
162+ answers . set ( 'commercial experience using go' , { text : 'Yes' , selectMatch : 'yes' } ) ;
163+ answers . set ( 'commercial experience using kubernetes' , { text : 'Yes' , selectMatch : 'yes' } ) ;
164+ answers . set ( 'hands-on experience' , { text : 'Yes' , selectMatch : 'yes' } ) ;
165+ answers . set ( 'production experience' , { text : 'Yes' , selectMatch : 'yes' } ) ;
166+
167+ // Skill ratings
168+ answers . set ( 'rate your level of skill' , { text : 'Intermediate' , selectMatch : 'intermediate' } ) ;
169+ answers . set ( 'level of skill programming' , { text : 'Intermediate' , selectMatch : 'intermediate' } ) ;
170+
171+ // How did you hear / learn about
172+ answers . set ( 'how did you learn' , { text : 'Company career page' , selectMatch : 'career' } ) ;
173+ answers . set ( 'learn about this job' , { text : 'Company career page' , selectMatch : 'career' } ) ;
174+ answers . set ( 'how did you hear' , { text : 'Company career page' , selectMatch : 'career' } ) ;
175+ answers . set ( 'hear about' , { text : 'Company career page' , selectMatch : 'career' } ) ;
176+
177+ // Conditional follow-ups ("if you answered yes/selected above")
178+ answers . set ( 'if you answered' , { text : 'N/A' , selectMatch : 'none' } ) ;
179+ answers . set ( 'if you selected' , { text : 'N/A' , selectMatch : 'none' } ) ;
180+ answers . set ( 'select all that apply' , { text : 'N/A' , selectMatch : 'none' } ) ;
181+
182+ // AI tools
183+ answers . set ( 'ai tools' , { text : 'GitHub Copilot for daily coding, ChatGPT for research and brainstorming, and custom AI pipelines for job search automation.' } ) ;
59184
60185 return answers ;
61186}
62187
63- function matchAnswer ( label , autoAnswers ) {
188+ /**
189+ * Match a question label to an auto-answer.
190+ * For select-type questions, find the best matching option value.
191+ */
192+ function matchAnswer ( label , autoAnswers , question = null ) {
64193 const lower = label . toLowerCase ( ) ;
65194 for ( const [ pattern , answer ] of autoAnswers ) {
66- if ( answer && lower . includes ( pattern ) ) return answer ;
195+ if ( ! lower . includes ( pattern ) ) continue ;
196+
197+ // For select questions, find the matching option
198+ if ( question && question . type . includes ( 'select' ) && question . values ?. length > 0 ) {
199+ const target = ( answer . selectMatch || answer . text || '' ) . toLowerCase ( ) ;
200+ if ( ! target ) continue ;
201+
202+ // Exact label match first
203+ const exact = question . values . find ( v => v . label . toLowerCase ( ) === target ) ;
204+ if ( exact ) return exact . value ;
205+
206+ // Partial match
207+ const partial = question . values . find ( v => v . label . toLowerCase ( ) . includes ( target ) ) ;
208+ if ( partial ) return partial . value ;
209+
210+ // Try matching "No" / "Yes" for boolean-like selects
211+ if ( [ 'yes' , 'no' ] . includes ( target ) ) {
212+ const boolMatch = question . values . find ( v => v . label . toLowerCase ( ) === target ) ;
213+ if ( boolMatch ) return boolMatch . value ;
214+ }
215+
216+ // For multi_value_multi_select: look for "none of the above" or "N/A"
217+ if ( question . type === 'multi_value_multi_select' ) {
218+ const noneOpt = question . values . find ( v => {
219+ const vl = v . label . toLowerCase ( ) ;
220+ return vl . includes ( 'none of the above' ) || vl . includes ( 'n/a' ) ||
221+ vl . includes ( 'not applicable' ) || vl . includes ( 'none' ) ;
222+ } ) ;
223+ if ( noneOpt ) return noneOpt . value ;
224+
225+ // For acknowledge/confirm multi-selects, pick the first/only option
226+ if ( target . includes ( 'acknowledge' ) || target . includes ( 'confirm' ) ) {
227+ return question . values [ 0 ] ?. value ;
228+ }
229+ }
230+
231+ continue ; // selectMatch didn't find an option
232+ }
233+
234+ // Text answer
235+ if ( answer . text ) return answer . text ;
67236 }
68237 return null ;
69238}
@@ -102,11 +271,14 @@ export async function submitApplication({ boardToken, jobId, profile, pdfPath, c
102271 const unanswered = [ ] ;
103272
104273 for ( const q of job . questions ) {
105- const answer = matchAnswer ( q . label , autoAnswers ) ;
106- if ( answer ) {
107- form . set ( q . id , answer ) ;
274+ // Skip standard fields already handled above
275+ if ( STANDARD_FIELDS . has ( q . id ) ) continue ;
276+
277+ const answer = matchAnswer ( q . label , autoAnswers , q ) ;
278+ if ( answer !== null && answer !== undefined ) {
279+ form . set ( q . id , String ( answer ) ) ;
108280 } else if ( q . required ) {
109- unanswered . push ( { id : q . id , label : q . label , type : q . type } ) ;
281+ unanswered . push ( { id : q . id , label : q . label , type : q . type , values : q . values ?. slice ( 0 , 5 ) } ) ;
110282 }
111283 }
112284
0 commit comments