Proyectos de Subversion LeadersLinked - Services

Rev

Rev 520 | Rev 522 | Ir a la última revisión | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
512 ariadna 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace LeadersLinked\Controller;
6
 
7
use Smalot\PdfParser\Parser;
8
 
9
use Laminas\Mvc\Controller\AbstractActionController;
10
use Laminas\View\Model\ViewModel;
11
use Laminas\View\Model\JsonModel;
12
use Laminas\Db\Adapter\AdapterInterface;
13
use Laminas\Db\Sql\Select;
14
use Laminas\Db\Adapter\Adapter;
15
use Laminas\Db\Sql\Sql;
16
use Laminas\Db\ResultSet\HydratingResultSet;
17
use Laminas\Hydrator\ArraySerializableHydrator;
516 ariadna 18
use LeadersLinked\Mapper\CompetencyMapper;
512 ariadna 19
use LeadersLinked\Mapper\JobDescriptionMapper;
516 ariadna 20
use LeadersLinked\Mapper\JobDescriptionCompetencyMapper;
512 ariadna 21
use LeadersLinked\Mapper\LocationMapper;
22
use LeadersLinked\Mapper\QueryMapper;
23
use ArrayObject;
24
 
25
class RecruitmentCreateJobDescriptionController extends AbstractActionController
26
{
27
    /**
28
     *
29
     * @var \Laminas\Db\Adapter\AdapterInterface
30
     */
31
    private $adapter;
32
 
33
    /**
34
     *
35
     * @var \LeadersLinked\Cache\CacheInterface
36
     */
37
    private $cache;
38
 
39
 
40
    /**
41
     *
42
     * @var \Laminas\Log\LoggerInterface
43
     */
44
    private $logger;
45
 
46
    /**
47
     *
48
     * @var array
49
     */
50
    private $config;
51
 
52
 
53
    /**
54
     *
55
     * @var \Laminas\Mvc\I18n\Translator
56
     */
57
    private $translator;
58
 
59
 
60
    /**
61
     *
62
     * @param \Laminas\Db\Adapter\AdapterInterface $adapter
63
     * @param \LeadersLinked\Cache\CacheInterface $cache
64
     * @param \Laminas\Log\LoggerInterface LoggerInterface $logger
65
     * @param array $config
66
     * @param \Laminas\Mvc\I18n\Translator $translator
67
     */
68
    public function __construct($adapter, $cache, $logger, $config, $translator)
69
    {
70
        $this->adapter      = $adapter;
71
        $this->cache        = $cache;
72
        $this->logger       = $logger;
73
        $this->config       = $config;
74
        $this->translator   = $translator;
75
    }
76
 
77
    public function indexAction()
78
    {
79
        $request = $this->getRequest();
80
 
81
        // 🔹 Obtener el ID desde la ruta (URL) con el formato /endpoint/:id
516 ariadna 82
        $jobDescriptionId = $this->params()->fromRoute('id');
512 ariadna 83
 
84
        // 🔹 Verificar si el ID es válido
516 ariadna 85
        if (!$jobDescriptionId) {
512 ariadna 86
            return new JsonModel([
87
                'success' => false,
88
                'message' => 'Missing vacancy ID'
89
            ]);
90
        }
91
 
92
        // 🔹 Obtener usuario y permisos ACL
93
        $currentUserPlugin = $this->plugin('currentUserPlugin');
94
        $currentUser = $currentUserPlugin->getUser();
95
        $acl = $this->getEvent()->getViewModel()->getVariable('acl');
96
 
97
        // 🔹 Verificar si el usuario tiene permiso para extraer criterios
516 ariadna 98
        if (!$acl->isAllowed($currentUser->usertype_id, 'recruitment-ai/job-description')) {
512 ariadna 99
            return new JsonModel([
100
                'success' => false,
101
                'message' => 'Access denied'
102
            ]);
103
        }
104
 
105
        if ($request->isGet()) {
106
 
107
            // Llamar a la función que obtiene los archivos de la vacante
518 ariadna 108
            $competencies = $this->analyzeJobDescriptionCompetencies($jobDescriptionId);
512 ariadna 109
 
110
            return new JsonModel([
111
                'success' => true,
516 ariadna 112
                'data' => $competencies
512 ariadna 113
            ]);
114
        }
115
 
516 ariadna 116
        // Si el método no es GET ni POST
117
        return new JsonModel([
118
            'success' => false,
119
            'message' => 'Invalid request method'
120
        ]);
121
    }
512 ariadna 122
 
516 ariadna 123
    public function getJobDescriptionCompetencies($jobDescriptionId)
124
    {
125
        // 🔹 Validación básica del ID
126
        if (!is_numeric($jobDescriptionId) || $jobDescriptionId <= 0) {
127
            return null;
128
        }
512 ariadna 129
 
516 ariadna 130
        try {
131
            // 🔹 Crear el QueryMapper
132
            $queryMapper = QueryMapper::getInstance($this->adapter);
133
            $select = $queryMapper->getSql()->select();
512 ariadna 134
 
516 ariadna 135
            // 🔹 FROM JobDescriptionMapper
136
            $select->from(['jd' => JobDescriptionMapper::_TABLE]);
137
            $select->columns([
138
                'name',
139
                'functions',
140
                'objectives'
141
            ]);
142
 
143
            // 🔹 JOIN con JobDescriptionCompetencyMapper
144
            $select->join(
145
                ['jdc' => JobDescriptionCompetencyMapper::_TABLE],
146
                'jd.id = jdc.job_description_id',
147
                [] // No seleccionamos nada directamente de esta tabla
148
            );
149
 
150
            // 🔹 JOIN con CompetencyMapper
151
            $select->join(
152
                ['c' => CompetencyMapper::_TABLE],
153
                'jdc.competency_id = c.id',
154
                [
155
                    'competency_name' => 'name',
156
                    'competency_description' => 'description'
157
                ]
158
            );
159
 
160
            // 🔹 WHERE por ID de descripción de trabajo
161
            $select->where->equalTo('jd.id', $jobDescriptionId);
162
 
163
            // 🔹 Ejecutar la consulta
164
            $statement = $queryMapper->getSql()->prepareStatementForSqlObject($select);
165
            $resultSet = $statement->execute();
166
 
167
            // 🔹 Procesar resultados
168
            $hydrator = new ArraySerializableHydrator();
169
            $hydratingResultSet = new HydratingResultSet($hydrator);
170
            $hydratingResultSet->initialize($resultSet);
171
 
172
            // 🔹 Agrupar resultados por competencias
173
            $jobInfo = null;
174
            $competencies = [];
175
 
176
            foreach ($hydratingResultSet as $row) {
177
                if (!$jobInfo) {
178
                    $jobInfo = [
179
                        'name' => $row['name'],
180
                        'functions' => $row['functions'],
181
                        'objectives' => $row['objectives']
182
                    ];
183
                }
184
 
185
                $competencies[] = [
186
                    'name' => $row['competency_name'],
187
                    'description' => $row['competency_description']
188
                ];
512 ariadna 189
            }
190
 
516 ariadna 191
            if (!$jobInfo) {
192
                return null;
193
            }
512 ariadna 194
 
516 ariadna 195
            // 🔹 Construir el texto formateado
196
            $formattedText =
197
                "### Información del Trabajo\n" .
198
                "Nombre: {$jobInfo['name']}\n" .
199
                "Funciones: {$jobInfo['functions']}\n" .
200
                "Objetivos: {$jobInfo['objectives']}\n\n" .
201
                "### Competencias requeridas:\n";
202
 
203
            foreach ($competencies as $index => $comp) {
204
                $formattedText .= ($index + 1) . ". {$comp['name']} - {$comp['description']}\n";
512 ariadna 205
            }
516 ariadna 206
 
207
            return $formattedText;
208
        } catch (\Exception $e) {
209
            // Aquí podrías loggear el error si es necesario
210
            return null;
512 ariadna 211
        }
212
    }
518 ariadna 213
 
214
    function cleanContent($text)
215
    {
216
        // Eliminar la palabra "json"
217
        $text = str_replace("json", "", $text);
218
 
219
        // Eliminar los saltos de línea \n
220
        $text = str_replace("\n", "", $text);
221
 
222
        // Eliminar los acentos invertidos (```)
223
        $text = str_replace("```", "", $text);
224
 
225
        // Retornar el contenido arreglado
226
        return $text;
227
    }
228
 
229
    public function analyzeJobDescriptionCompetencies($jobDescriptionId)
230
    {
231
        // 🔹 Obtener texto de la descripción de cargo y competencias
232
        $descriptionText = $this->getJobDescriptionCompetencies($jobDescriptionId);
233
 
234
        if (!$descriptionText) {
235
            return [
236
                'success' => false,
237
                'message' => 'No se encontró la descripción del cargo.',
238
                'data' => null
239
            ];
240
        }
241
 
242
        // 🔹 Crear el mensaje para OpenAI
243
        $messages = [
244
            [
245
                'role' => 'system',
246
                'content' => "Eres un experto en talento humano, análisis de perfiles laborales y gestión de competencias para
247
                el mercado laboral actual."
248
            ],
249
            [
250
                'role' => 'user',
251
                'content' => "A continuación te proporciono una descripción de un cargo con sus funciones, objetivos y competencias requeridas.
252
                Analiza si las competencias listadas están actualizadas para los requerimientos actuales de la industria y el trabajo moderno.
253
 
254
                Retorna una respuesta estructurada en formato JSON como este:
255
 
256
                {
257
                \"actualizado\": true o false,
258
                \"competencias\": [
259
                    {
520 ariadna 260
                    \"name\": \"nombre de la competencia\",
261
                    \"description\": \"recomendación o análisis de si está o no actualizada, o si falta complementar\"
518 ariadna 262
                    }
263
                ]
264
                }
265
 
266
                Texto a analizar:
267
                \"\"\"$descriptionText\"\"\"
268
                "
269
            ]
270
        ];
271
 
272
        // 🔹 Consultar OpenAI
273
        $response = $this->analyzeCvWithAi($messages);
274
 
521 ariadna 275
        return $response;
518 ariadna 276
    }
277
 
278
    function callExternalApi($url, $payload, $headers)
279
    {
280
        $ch = curl_init($url);
281
        curl_setopt($ch, CURLOPT_CAINFO, '/etc/apache2/ssl/cacert.pem');
282
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
283
        curl_setopt($ch, CURLOPT_POST, true);
284
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
285
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
286
 
287
        $response = curl_exec($ch);
288
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
289
        $curlError = curl_error($ch);
290
 
291
        curl_close($ch);
292
 
293
        // Verificar si hubo un error en la petición
294
        if ($curlError) {
295
            error_log("cURL Error: " . $curlError);
296
            return [false, "cURL Error: " . $curlError];
297
        }
298
 
299
        // Si la API devuelve un código de error (4xx o 5xx), registrarlo
300
        if ($httpCode >= 400) {
301
            error_log("Error HTTP {$httpCode}: " . $response);
302
            return [false, "Error HTTP {$httpCode}: " . $response];
303
        }
304
 
305
        // Intentar decodificar la respuesta JSON
306
        $decodedResponse = json_decode($response, true);
307
 
308
        // Verificar si la respuesta es válida
309
        if (json_last_error() !== JSON_ERROR_NONE) {
310
            error_log("Error al decodificar JSON: " . json_last_error_msg());
311
            return [false, "Error al decodificar JSON: " . json_last_error_msg()];
312
        }
313
 
314
        return $decodedResponse;
315
    }
316
 
317
    function analyzeCvWithAi(array $messages)
318
    {
319
        $apiKey = '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';
320
        $url = "https://api.openai.com/v1/chat/completions";
321
 
322
        $payload = json_encode([
323
            'model' => 'gpt-4o-mini',
324
            'messages' => $messages,
325
            'temperature' => 0.7
326
        ]);
327
 
328
        $headers = [
329
            'Content-Type: application/json',
330
            "Authorization: Bearer $apiKey"
331
        ];
332
 
333
        return $this->callExternalApi($url, $payload, $headers);
334
    }
512 ariadna 335
}