-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchatbot.py
More file actions
465 lines (375 loc) · 16.7 KB
/
chatbot.py
File metadata and controls
465 lines (375 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
import pandas as pd
import spacy
import es_core_news_sm
from spacy.matcher import Matcher
from scipy import spatial
import re
import math
import unicodedata
import unidecode
from nltk.corpus import stopwords
from nltk.tokenize import RegexpTokenizer
from operator import itemgetter
PREGUNTAS = {
#IDIOMAS 4
'¿Qué idiomas habla?':4, '¿Qué idiomas conoce el candidato?': 4,
'¿Cuáles son los idiomas que conoce el candidato?': 4, '¿Qué lenguas habla el candidato?': 4,
'¿Qué lenguas son las que conoce el candidato?': 4, '¿Que idiomas habla?': 4,
#Nombre del candidato 0
'¿Cómo se llama el candidato?': 0, '¿Cuál es el nombre del candidato?:': 0,
'¿Cómo se llama el candidato?': 0, '¿Con qué nombre responde el candidato?': 0,
'¿Quién es el candidato?': 0, 'Nombre del candidato': 0,
#Teléfono del candidato 2
'¿Cuál es el número de teléfono del candidato?': 2, '¿Qué número de teléfono tiene el candidato?': 2,
'¿A qué número hay que llamar para contactar con el candidato?': 2, '¿Qué número de teléfono figura como el número del candidato?': 2,
'¿Teléfono de contacto?': 2, '¿Cual es teléfono móvil?':2,
#Experiencia laboral 7
'¿Qué experiencia laboral tiene?': 7, '¿Qué experiencia tiene el candidato?': 7,
'¿Cuál es la experiencia que tiene el candidato?': 7, '¿Qué tipo de experiencia tiene el candidato?':7,
'¿Dónde ha trabajado previamente el candidato?': 7, '¿Qué profesiones ha llevado a cabo previamente el candidato?': 7,
'¿Qué experiencia experiencia tiene?': 7, 'Donde ha trabajado?:':7,
#Lengiajes de programación 3
'¿Qué lenguajes de programación conoce?': 3, '¿Qué lenguajes de programación controla el candidato?':3,
'¿Cuáles son los lenguajes de programación que controla el candidato?': 3, '¿Cómo se llaman los lenguajes de programación que controla el candidato?': 3,
'¿De qué lenguajes de programación tiene conocimientos el candidato?':3, '¿Lenguajes de programación?': 3, '¿Sabe programar en Python?': 3, 'Conoce Java': 3,
#Formación académica 6
'¿Qué educación tiene el candidato?': 6, '¿Qué títulos tiene el candidato?': 6,
'¿Cuáles son los títulos del candidato?':6, '¿Con qué titulaciones cuenta el candidato?': 6,
'¿Qué ha estudiado?': 6, '¿Qué estudios tiene?': 6,
#Correo de contacto 1
'¿Cuál es su correo de contacto?':1, '¿Cuál es el correo del candidato?': 1,
'¿Qué correo tiene el candidato?': 1, '¿A qué dirección de correo debemos escribir para contactar con el candidato?': 1,
'¿Qué dirección de correo figura como la dirección del candidato?': 1, '¿Correo?': 1, 'email?':1,'¿email del candidato?': 1, '¿e-mail?':1,
#Referencias 5
'¿Cuáles son sus referencias?': 5, '¿Cuáles son las referencias del candidato?': 5,
'¿Qué referencias tiene el candidato?': 5, '¿Qué personas pueden servir como referencia del candidato?': 5,
'¿Quién figura como referencia del candidato?': 5, '¿Referencias?' : 5
}
secciones = ['EXPERIENCIA', 'EDUCACIÓN', 'CONTACTO', 'HABILIDADES', 'IDIOMAS', 'REFERENCIAS']
# Extracción de secciones:
def encontrar_seccion(seccion, df):
fila_i = None
fila_f = None
for f in range(len(df['text'])):
if seccion in df['text'][f]:
fila_i = f
v = [l.isupper() for l in df['text'][f].strip('\n')]
if fila_i != None and all(v) and f!= fila_i and len(v) != 0:
fila_f = f
break
if f == len(df['text']) -1:
fila_f = f + 1
break
return (fila_i, fila_f)
def saca_texto(lineas, df):
i, f = lineas
texto = ''
for j in range(i+1, f):
texto += df['text'][j]
return texto
def sub_frase_a_partir_de_palabra(frase, palabra):
separadores = ['.',':','|',';',',','/']
try:
i = frase.index(palabra)
sol = frase[i:]
min = len(sol)
for s in separadores:
try:
j = sol.index(s)
#print(sol[0:j]+'\n')
if j < min:
min = j
except Exception as e:
pass
return (i,i+min)
except Exception as e:
return (0,0)
def limpiar_frase_titulos(cadena):
cadena_procesada = unidecode.unidecode(cadena) #Quitar acentos
cadena_procesada = cadena_procesada.lower() # A minúsculas
return cadena_procesada
def encontrar_titulos(df):
claves = ['Doble Grado', 'Doble Titulación', 'Doble Graduado', 'Doble Graduada',
'Grado','Máster','Licenciado','Licenciada','Graduado','Graduada','Licenciatura','PhD',
'Doctorado','Doctorada','Doctor','Doctora','Titulo','Titulado','Titulada','titulación',
'Diplomado','Diplomada','Diplomatura']
titulos = '\n\n'
sec_educ = encontrar_seccion(secciones[1],df)
for f in range(sec_educ[0],(sec_educ[1]+1)):
frase = df['text'][f]
frase = limpiar_frase_titulos(frase)
for clave in claves:
indices = sub_frase_a_partir_de_palabra(frase,limpiar_frase_titulos(clave))
if (indices[1]-indices[0] > 2) and (limpiar_frase_titulos(df['text'][f][indices[0]:indices[1]]) not in limpiar_frase_titulos(clave)):
titulos += df['text'][f][indices[0]:indices[1]]+'\n'
break
return titulos
def encontrar_idiomas(df):
tokenizer = RegexpTokenizer(r'[0-9A-Za-z_À-ÿñ]{2,}')
palabras_vacias = set(stopwords.words('spanish'))
relevantes = ['Español','A1','A2','B1','B2','C1','C2','Alemán','Francés', 'Inglés', 'English'
'Italiano', 'Chino', 'Ruso', 'Japonés', 'Portugués', 'Catlán', 'Català', 'Vasco', 'Euskera', 'Euskara',
'Gallego', 'Galego']
limpio_relevantes = []
for x in relevantes:
limpio_relevantes.append(limpiar_pregunta(x))
idiomas = '\n'
for f in range(len(df['text'])):
frase = df['text'][f]
#print(frase)
frase = limpiar_pregunta(frase)
frase = tokenizer.tokenize(frase)
#print(frase)
frase = [w for w in frase if not w.lower() in palabras_vacias]
if len(set(limpio_relevantes).intersection(set(frase))) > 0 :
idiomas += df['text'][f] + '\n'
if len(idiomas) < 3:
return "No hemos encontrado información de idiomas"
return idiomas
# print(saca_texto(encontrar_seccion(secciones[0])))
# Extracción del nombre del candidato:
def encontrar_nombre(x, df):
nlp = es_core_news_sm.load()
doc = nlp(x)
matcher = Matcher(nlp.vocab)
matcher.add("matching_father", pattern_father)
matches = matcher(doc)
sub_text = ''
if(len(matches) > 0):
span = doc[matches[0][1]:matches[0][2]]
sub_text = span.text
tokens = sub_text.split(' ')
if len(tokens) == 3:
name, surname1, surname2 = tokens[0], tokens[1], tokens[2]
else:
name, surname1, surname2 = None, None, None
return name, surname1, surname2
def devolver_nombre(df):
nombre = None
for f in range(len(df['text'])):
if df['Nombre'][f] != None and df['Apellido1'][f] != None and df['Apellido2'][f] != None:
if f == 0:
nombre = df['Nombre'][f] + ' ' + df['Apellido1'][f] + ' ' + df['Apellido2'][f]
return nombre
last_token = ['\n']
pattern_father = [[{'POS':'PROPN', 'OP' : '+'},
{'POS':'PROPN', 'OP' : '+'},
{'POS':'PROPN', 'OP' : '+'},
{'LOWER': {'IN' : last_token}}]]
# Extracción de lenguajes de programación:
def encontrar_lenguajes(df):
lenguajes = 'Python, R, Java, C, JavaScript, Matlab, SQL, SPARQL, C++'
punto = None
fila = None
for f in range(len(df['text'])):
if 'lenguajes de programación' in df['text'][f].lower():
fila = f
punto = df['text'][f].lower().index('lenguajes')
break
cadena = df['text'][fila][punto:].replace(',','').replace('.','').split()
l = [i for i in cadena if i in lenguajes.replace(',','').split()]
return ', '.join(l)
def tf_idf(frase, idf, union, k):
tokenizer = RegexpTokenizer(r'[A-Za-z_À-ÿñ]{3,}')
palabras_vacias = set(stopwords.words('spanish'))
tf = dict.fromkeys(union,0)
tf_idf = dict.fromkeys(union,0)
#TF
frase_tokens = tokenizer.tokenize(frase)
frase_tokens = [w for w in frase_tokens if not w.lower() in palabras_vacias]
for palabra in frase_tokens:
tf[palabra] += 1
for palabra in frase_tokens:
tf_idf[palabra] = idf[palabra] * tf[palabra]
l = sorted(tf_idf.items(), key=itemgetter(1), reverse=True)
l = l[:k]
#Compute array
arr = []
for palabra in union:
match = False
for par in l:
if palabra == par[0]:
match = True
break
if match:
arr.append(1)
else:
arr.append(0)
return arr
def limpiar_pregunta(cadena):
cadena_procesada = unidecode.unidecode(cadena) #Quitar acentos
cadena_procesada = cadena_procesada.lower() # A minúsculas
cadena_procesada = cadena_procesada.replace('(','').replace(')','').replace('[','').replace(']','')
cadena_procesada = re.sub(r"[^a-z0-9 ñ]", "",cadena_procesada) #Quitar carácteres especiales
return cadena_procesada
#Tomar una pregunta y buscar la más similar
def preocesar_pregunta(pregunta):
tokenizer = RegexpTokenizer(r'[A-Za-z_À-ÿñ]{3,}')
palabras_vacias = set(stopwords.words('spanish'))
#Preprocesar pregunta:
pregunta_procesada = limpiar_pregunta(pregunta)
texto = ''
#Si la pregunta esta vacía tras procesar:
if len(pregunta_procesada)<4:
return ['', '',0]
#Corpus de preguntas
for q in PREGUNTAS:
#Si está en la lista, terminamos aquí
if ( pregunta_procesada in limpiar_pregunta(q) ) or ( limpiar_pregunta(q) in pregunta_procesada ):
return [q, PREGUNTAS[q], 1]
q_procesada = limpiar_pregunta(q) + ' '
texto += q_procesada
#Miramos si la pregunta está relacionada con la base de datos de preguntas
texto = tokenizer.tokenize(texto)
texto = [w for w in texto if not w.lower() in palabras_vacias]
palabras_pregunta = tokenizer.tokenize(pregunta_procesada)
palabras_pregunta = [w for w in palabras_pregunta if not w.lower() in palabras_vacias]
if(len( set(texto).intersection(set(palabras_pregunta)) )<1):
return ['','',0]
for palabra in palabras_pregunta:
texto.append(palabra)
#IDF
N = len(texto)
union = set(texto)
#print('Union '+str(union))
IDF = dict.fromkeys(union,0)
for palabra in texto:
IDF[palabra] += 1
for palabra in texto:
IDF[palabra] = math.log10((N/(1+IDF[palabra])))
#Buscamos la frase más cercana
v1 = tf_idf(pregunta_procesada, IDF, union, 15)
max = 0
distancias = [0,0,0,0,0,0,0,0]
divisores = [0,0,0,0,0,0,0,0]
cercana = []
for q in PREGUNTAS:
q_procesada = limpiar_pregunta(q)
v2 = tf_idf(q_procesada, IDF, union, 15)
d = 1 - spatial.distance.cosine(v1, v2)
distancias[PREGUNTAS[q]] += d
divisores[PREGUNTAS[q]] += 1
if d > max:
max = d
cercana = q
max = 0
j = 0
for i in range(8):
distancias[i] = distancias[i]/divisores[i]
if max < distancias[i]:
j = i
max = distancias[i]
return [cercana,j,max]
def preguntar_pregunta(pregunta):
#Cotas de similitud:
cota1 = 0.95
cota2 = 0.6
vector = preocesar_pregunta(pregunta)
#print("\nEstamos seguros con score = "+str(vector[2])+" de que la pregunta es:\n"+str(vector[0])+"\n")
if vector[2] >= cota1:
selector = vector[1]
elif vector[2] >= cota2:
selector = vector[1]
print("\n¿Es esta su pregunta: (s/n)?\n")
print("\n"+vector[0]+"\n")
confirm = input()
if not confirm.lower() == 's' and not confirm.lower() == 'si' and not confirm.lower() == 'sí':
selector = -1
else:
print("\nLo siento, no le entiendo\n")
selector = -1
return selector
def main():
parada = False
while (not parada):
# Elección del CV a analizar
rutas = ["cv_txt/cv_jorge.txt", "cv_txt/cv_luis.txt"]
indice = input(("Escriba 0 para elegir el primer currículum, "
"1 para elegir el segundo currículum o "
"cualquier otra cosa para salir del sistema de preguntas:\n"))
if indice.isdigit():
if int(indice) not in range(2):
print("GRACIAS por hacer uso de este servicio. Le esperamos cuando tenga alguna duda.")
parada = True
break
else:
indice = int(indice)
else:
print("Gracias por hacer uso de este servicio. Le esperamos cuando tenga alguna duda.")
parada = True
break
fichero = rutas[indice]
#Cambiamos la variable pregunta para elegir qué dato tomamos
# Las preguntas está hechas de esta forma hasta que sepamos qué preguntas base y qué parafraseo poner.
# Los números llegan hasta el 7, que son las dimensiones que tenemos actualmente.
# Si queremos añadir más solo habría que aumentar el range
pregunta = input(("\n\n¿Qué quieres saber sobre el candidato? \n\n"))
selector = preguntar_pregunta(pregunta)
print(selector)
while selector not in range(8):
pregunta = input(("\n\n¿Qué quieres saber sobre el candidato? \n\n"))
selector = preguntar_pregunta(pregunta)
print(selector)
# Procesamiento del txt
with open(fichero, 'r') as f:
text = [line for line in f.readlines()]
df = pd.DataFrame(text,columns=['text'])
df.head()
#print("\n df = \n"+str(df))
text = df['text'][0]
nlp = es_core_news_sm.load()
doc = nlp(text)
features = []
for token in doc:
features.append({'token' : token.text, 'pos' : token.pos_})
fdf = pd.DataFrame(features)
fdf.head(len(fdf))
# Llamada para sacar el nombre del candidato
if selector == 0:
new_columns = ['Nombre','Apellido1', 'Apellido2']
for n,col in enumerate(new_columns):
df[col] = df['text'].apply(lambda x: encontrar_nombre(x, df)).apply(lambda x: x[n])
print("El nombre completo del candidato es:", devolver_nombre(df))
# Llamada para sacar el correo del candidato
if selector == 1:
data = open(fichero,'r')
texto = data.read()
r = re.compile(r'(\b[\w.]+@+[\w.]+.+[\w.]\b)')
results = r.findall(texto)
try:
email_del_candidato = results[0]
print("El email del candidato es:", email_del_candidato)
except Exception as e:
print('No hemos encontrado email')
# Llamada para sacar el número de teléfono del candidato
# Te dejo la estructura del email porque podría sacarse de forma similar
if selector == 2:
data = open(fichero,'r')
texto = data.read()
r = re.compile(r'[ (\[]{,1}[+]{,1}[ (\[]{,1}\d{0,4}[ )\]]{,1}[- ]{,3}\d{2,4}[- ]{,3}\d{2,4}[- ]{,3}\d{2,4}[- ]{,3}\d{0,4}')
results = r.findall(texto)
try:
telefono_del_candidato = results[0]
i = 0
while(len(telefono_del_candidato.replace(" ", "").replace("-", "")) < 9):
i += 1
telefono_del_candidato = results[i]
print("El teléfono del candidato es:", telefono_del_candidato)
except Exception as e:
print('No hemos encontrado teléfono de contacto')
# Llamada a sacar los lenguajes de programación que conoce el candidato
if selector == 3:
print('Los lenguajes de programación que maneja el candidato son: \n' + encontrar_lenguajes(df) + '.')
# Llamada para sacar los idiomas que sabe el candidato
if selector == 4:
print("Los idiomas que maneja el candidato son: \n" + encontrar_idiomas(df))
# Llamada para sacar las referencias del candidato
if selector == 5:
print("Las referencias del candidato son: \n" + saca_texto(encontrar_seccion(secciones[5],df), df))
# Llamada para sacar la información académica del candidato
if selector == 6:
print("La formación académica del candidato es:", encontrar_titulos(df))
# Llamada para sacar la experiencia laboral del candidato
if selector == 7:
print("La experiencia laboral del candidato es: \n" + saca_texto(encontrar_seccion(secciones[0],df),df))
main()