diff --git a/bin/rate_update.py b/bin/rate_update.py index e5865ef..d8cc18e 100755 --- a/bin/rate_update.py +++ b/bin/rate_update.py @@ -487,7 +487,7 @@ Responde solo con el valor numérico con dos decimales.""" return prompt -def get_fallback_rate(programmer_type, region_code): +def get_fallback_rate(programmer_type, region_code=None): """ Proporciona una tarifa de respaldo cuando la API falla o no está disponible. Usa estimaciones basadas en datos de mercado generales. @@ -514,6 +514,11 @@ def get_fallback_rate(programmer_type, region_code): # Valor por defecto si el tipo no está en la lista base_rate = base_rates.get(programmer_type, 35.00) + # Si no se proporciona región o es None, devolver la tarifa base global (promedio mundial) + if region_code is None: + return base_rate + + # Si se proporciona región, aplicar factor regional # Factores de ajuste por región region_factors = { 'ww': 1.0, # Promedio mundial @@ -534,10 +539,6 @@ def get_fallback_rate(programmer_type, region_code): 'es': 1.3, # España 'it': 1.3 # Italia } - - # Si no hay región, devolver la tarifa base - if region_code is None: - return base_rate # Aplicar factor regional factor = region_factors.get(region_code, 1.0) @@ -553,8 +554,31 @@ def show_result(message): if SHOW_RESULTS: print(message) +def get_programmer_types(): + """Retorna una lista de todos los tipos de programadores soportados.""" + return [ + 'bash', + 'python', + 'fullstack', + 'frontend', + 'backend', + 'devops', + 'mobile', + 'java', + 'php', + 'ruby', + 'dotnet', + 'data', + 'ml', + 'cloud', + 'odoo' + ] + def update_rate_files(): - """Actualiza los archivos de tarifas con datos de Perplexity.""" + """ + Actualiza los archivos de tarifas con datos de Perplexity. + Crea un archivo por tipo de programador con la tarifa por hora general. + """ # Obtener modelo configurado model = get_ai_model() logger.info(f"Usando modelo de IA: {model}") @@ -571,43 +595,39 @@ def update_rate_files(): # Crear la carpeta rates si no existe os.makedirs(RATES_DIR, exist_ok=True) - # Buscar todos los archivos .rate en la nueva ubicación - rate_files = glob.glob(str(RATES_DIR / '*.rate')) - logger.info(f"Encontrados {len(rate_files)} archivos de tarifas para actualizar.") - # Control de errores para limitar los intentos de API consecutive_errors = 0 max_consecutive_errors = 3 + rate_threshold = 200.00 # Umbral para considerar una tarifa como excesiva - for rate_file in rate_files: + # Obtener la lista de tipos de programadores + programmer_types = get_programmer_types() + logger.info(f"Procesando {len(programmer_types)} tipos de programadores.") + + # Procesar cada tipo de programador + for programmer_type in programmer_types: + # Comprobar si ya existe el archivo para este tipo de programador + rate_file = RATES_DIR / f"{programmer_type}.rate" + + # Si el archivo ya existe, saltamos este tipo + if rate_file.exists(): + logger.info(f"El archivo {rate_file} ya existe. Saltando.") + continue + # Si hemos tenido demasiados errores consecutivos, cambiar a modo de respaldo if consecutive_errors >= max_consecutive_errors: logger.error(f"Se detectaron {consecutive_errors} errores consecutivos. Cambiando a modo de respaldo.") api_available = False - - programmer_type, region_code = parse_rate_filename(rate_file) - if programmer_type is None: - logger.warning(f"No se pudo analizar el nombre del archivo: {rate_file}, saltando.") - continue - - # Si es un archivo especial como kdevs.rate - if region_code is None: - prompt = generate_prompt(programmer_type, None) - # Saltamos los archivos especiales que no necesitan actualización - if prompt is None: - logger.info(f"Saltando archivo especial: {os.path.basename(rate_file)}") - continue - else: - prompt = generate_prompt(programmer_type, region_code) + # Generar el prompt para la consulta + prompt = generate_prompt_base(programmer_type) # Variable para almacenar la tarifa rate = None # Si la API está disponible, intentar consultarla if api_available: - logger.info(f"Consultando tarifa para {programmer_type}" + - (f" en región {region_code}" if region_code else "")) + logger.info(f"Consultando tarifa para {programmer_type}") try: # Consultar a Perplexity @@ -616,68 +636,75 @@ def update_rate_files(): if rate is not None: consecutive_errors = 0 # Reiniciar contador de errores tras un éxito else: - logger.error(f"No se pudo obtener la tarifa para {programmer_type}" + - (f" en región {region_code}" if region_code else "")) + logger.error(f"No se pudo obtener la tarifa para {programmer_type}") consecutive_errors += 1 except Exception as e: - logger.error(f"Error al actualizar {os.path.basename(rate_file)}: {e}") + logger.error(f"Error al consultar tarifa para {programmer_type}: {e}") consecutive_errors += 1 # Pequeña pausa tras un error para evitar sobrecargar la API time.sleep(2) # Si la API falló o no está disponible, usar valor de respaldo if rate is None: - logger.warning(f"Usando valor de respaldo para {programmer_type}" + - (f" en región {region_code}" if region_code else "")) - rate = get_fallback_rate(programmer_type, region_code) + logger.warning(f"Usando valor de respaldo para {programmer_type}") + rate = get_fallback_rate(programmer_type, None) logger.info(f"Valor de respaldo generado: {rate:.2f}") - # Comprobar si la tarifa es superior a 200.00 USD/hora - rate_threshold = 200.00 + # Comprobar si la tarifa es superior al umbral establecido if rate > rate_threshold: - # Buscar todas las tarifas del mismo tipo de programador para calcular el promedio - same_type_rates = [] - for other_file in rate_files: - other_type, other_region = parse_rate_filename(other_file) - - # Solo considerar archivos del mismo tipo con regiones válidas - if other_type == programmer_type and other_region is not None and other_region != region_code: - try: - # Leer el archivo para obtener la tarifa actual - with open(other_file, 'r', encoding='utf-8') as f: - content = f.read().strip() - if content: - other_rate = float(content) - # Solo incluir tarifas que no excedan el umbral - if other_rate <= rate_threshold: - same_type_rates.append(other_rate) - logger.info(f"Considerando tarifa válida para promedio: {other_rate:.2f} de {other_region}") - else: - logger.info(f"Ignorando tarifa para promedio (excede umbral): {other_rate:.2f} de {other_region}") - except (ValueError, FileNotFoundError): - continue - - # Si encontramos tarifas del mismo tipo, calcular el promedio - if same_type_rates: - avg_rate = sum(same_type_rates) / len(same_type_rates) - # El promedio nunca excederá el umbral ya que solo incluimos tarifas válidas - logger.warning(f"Tarifa {rate:.2f} supera el umbral de {rate_threshold}. Usando promedio de {len(same_type_rates)} tarifas válidas: {avg_rate:.2f}") - rate = round(avg_rate, 2) - else: - # Si no hay otras tarifas para calcular el promedio, usar un valor de fallback - # Asegurarnos que el valor nunca exceda el umbral - fallback_rate = min(rate_threshold, get_fallback_rate(programmer_type, region_code) * 1.5) - logger.warning(f"Tarifa {rate:.2f} supera el umbral de {rate_threshold}. No hay datos para promedio. Usando valor ajustado: {fallback_rate:.2f}") - rate = round(fallback_rate, 2) + logger.warning(f"Tarifa {rate:.2f} supera el umbral de {rate_threshold}. Ajustando...") + rate = min(rate, rate_threshold) # Guardar el resultado en el archivo - solo el valor numérico con dos decimales, sin salto de línea with open(rate_file, 'w', encoding='utf-8') as f: f.write(f"{rate:.2f}") # Registrar en el log y mostrar el resultado en la consola - result_message = f"Tarifa para {programmer_type}" + (f" en región {region_code}" if region_code else "") + f": {rate:.2f} USD" - logger.info(f"Actualizado {os.path.basename(rate_file)} con valor: {rate:.2f}") + result_message = f"Tarifa para {programmer_type}: {rate:.2f} USD/hora" + logger.info(f"Creado archivo {rate_file} con valor: {rate:.2f}") show_result(result_message) + + # Pequeña pausa para no sobrecargar la API + time.sleep(1) + +def generate_prompt_base(programmer_type): + """ + Genera un prompt para consultar la tarifa base por hora para un tipo de programador. + """ + programmer_map = { + 'bash': 'programador de scripts Bash/Shell', + 'python': 'desarrollador Python', + 'fullstack': 'desarrollador Full Stack', + 'frontend': 'desarrollador Frontend', + 'backend': 'desarrollador Backend', + 'devops': 'ingeniero DevOps', + 'mobile': 'desarrollador de aplicaciones móviles', + 'java': 'desarrollador Java', + 'php': 'desarrollador PHP', + 'ruby': 'desarrollador Ruby', + 'dotnet': 'desarrollador .NET', + 'data': 'científico de datos', + 'ml': 'ingeniero de Machine Learning', + 'cloud': 'arquitecto Cloud', + 'odoo': 'desarrollador Odoo' + } + + programmer_description = programmer_map.get(programmer_type, f'desarrollador {programmer_type}') + + # Usamos el año actual para la consulta principal + current_year = time.strftime("%Y") + prompt = f"""¿Cuál es la tarifa POR HORA promedio mundial en dólares estadounidenses (USD) para un {programmer_description} en {current_year}? + +Si no tienes datos del {current_year}, usa la información más reciente y haz una estimación aproximada. + +IMPORTANTE: +1. Necesito ESPECÍFICAMENTE el valor POR HORA, no mensual ni anual. +2. Si encuentras información en otros períodos de tiempo (mensual, anual, etc.), conviértela a tarifa POR HORA. +3. Busca un promedio mundial, con conocimiento de valores actuales de mercado. + +Responde ÚNICAMENTE con el valor numérico con dos decimales.""" + + return prompt if __name__ == "__main__": # Parámetros de línea de comandos para controlar el comportamiento