[REFACTORED] Cambiar obtención de tarifas por tipo de programador

- Reorganizar la lógica para crear un archivo por tipo de programador
- Simplificar la estructura de archivos usando [tipo].rate
- Crear tarifas solo para tipos de programadores que no tengan archivo existente
- Mejorar prompts para obtener específicamente tarifas por hora
- Añadir función get_programmer_types para centralizar la lista de profesionales
- Optimizar la función get_fallback_rate para soportar nueva estructura
- Aplicar consistentemente el límite de 200 USD/hora para todas las tarifas

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mauro Rosero P. 2025-03-12 11:46:11 -05:00
parent 935831aa5e
commit fb00f958dc
Signed by: mrosero
GPG key ID: 83BD2A5F674B7E26

View file

@ -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