[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:
parent
935831aa5e
commit
fb00f958dc
1 changed files with 99 additions and 72 deletions
|
@ -487,7 +487,7 @@ Responde solo con el valor numérico con dos decimales."""
|
||||||
|
|
||||||
return prompt
|
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.
|
Proporciona una tarifa de respaldo cuando la API falla o no está disponible.
|
||||||
Usa estimaciones basadas en datos de mercado generales.
|
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
|
# Valor por defecto si el tipo no está en la lista
|
||||||
base_rate = base_rates.get(programmer_type, 35.00)
|
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
|
# Factores de ajuste por región
|
||||||
region_factors = {
|
region_factors = {
|
||||||
'ww': 1.0, # Promedio mundial
|
'ww': 1.0, # Promedio mundial
|
||||||
|
@ -534,10 +539,6 @@ def get_fallback_rate(programmer_type, region_code):
|
||||||
'es': 1.3, # España
|
'es': 1.3, # España
|
||||||
'it': 1.3 # Italia
|
'it': 1.3 # Italia
|
||||||
}
|
}
|
||||||
|
|
||||||
# Si no hay región, devolver la tarifa base
|
|
||||||
if region_code is None:
|
|
||||||
return base_rate
|
|
||||||
|
|
||||||
# Aplicar factor regional
|
# Aplicar factor regional
|
||||||
factor = region_factors.get(region_code, 1.0)
|
factor = region_factors.get(region_code, 1.0)
|
||||||
|
@ -553,8 +554,31 @@ def show_result(message):
|
||||||
if SHOW_RESULTS:
|
if SHOW_RESULTS:
|
||||||
print(message)
|
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():
|
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
|
# Obtener modelo configurado
|
||||||
model = get_ai_model()
|
model = get_ai_model()
|
||||||
logger.info(f"Usando modelo de IA: {model}")
|
logger.info(f"Usando modelo de IA: {model}")
|
||||||
|
@ -571,43 +595,39 @@ def update_rate_files():
|
||||||
# Crear la carpeta rates si no existe
|
# Crear la carpeta rates si no existe
|
||||||
os.makedirs(RATES_DIR, exist_ok=True)
|
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
|
# Control de errores para limitar los intentos de API
|
||||||
consecutive_errors = 0
|
consecutive_errors = 0
|
||||||
max_consecutive_errors = 3
|
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
|
# Si hemos tenido demasiados errores consecutivos, cambiar a modo de respaldo
|
||||||
if consecutive_errors >= max_consecutive_errors:
|
if consecutive_errors >= max_consecutive_errors:
|
||||||
logger.error(f"Se detectaron {consecutive_errors} errores consecutivos. Cambiando a modo de respaldo.")
|
logger.error(f"Se detectaron {consecutive_errors} errores consecutivos. Cambiando a modo de respaldo.")
|
||||||
api_available = False
|
api_available = False
|
||||||
|
|
||||||
programmer_type, region_code = parse_rate_filename(rate_file)
|
|
||||||
|
|
||||||
if programmer_type is None:
|
# Generar el prompt para la consulta
|
||||||
logger.warning(f"No se pudo analizar el nombre del archivo: {rate_file}, saltando.")
|
prompt = generate_prompt_base(programmer_type)
|
||||||
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)
|
|
||||||
|
|
||||||
# Variable para almacenar la tarifa
|
# Variable para almacenar la tarifa
|
||||||
rate = None
|
rate = None
|
||||||
|
|
||||||
# Si la API está disponible, intentar consultarla
|
# Si la API está disponible, intentar consultarla
|
||||||
if api_available:
|
if api_available:
|
||||||
logger.info(f"Consultando tarifa para {programmer_type}" +
|
logger.info(f"Consultando tarifa para {programmer_type}")
|
||||||
(f" en región {region_code}" if region_code else ""))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Consultar a Perplexity
|
# Consultar a Perplexity
|
||||||
|
@ -616,68 +636,75 @@ def update_rate_files():
|
||||||
if rate is not None:
|
if rate is not None:
|
||||||
consecutive_errors = 0 # Reiniciar contador de errores tras un éxito
|
consecutive_errors = 0 # Reiniciar contador de errores tras un éxito
|
||||||
else:
|
else:
|
||||||
logger.error(f"No se pudo obtener la tarifa para {programmer_type}" +
|
logger.error(f"No se pudo obtener la tarifa para {programmer_type}")
|
||||||
(f" en región {region_code}" if region_code else ""))
|
|
||||||
consecutive_errors += 1
|
consecutive_errors += 1
|
||||||
except Exception as e:
|
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
|
consecutive_errors += 1
|
||||||
# Pequeña pausa tras un error para evitar sobrecargar la API
|
# Pequeña pausa tras un error para evitar sobrecargar la API
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
# Si la API falló o no está disponible, usar valor de respaldo
|
# Si la API falló o no está disponible, usar valor de respaldo
|
||||||
if rate is None:
|
if rate is None:
|
||||||
logger.warning(f"Usando valor de respaldo para {programmer_type}" +
|
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, None)
|
||||||
rate = get_fallback_rate(programmer_type, region_code)
|
|
||||||
logger.info(f"Valor de respaldo generado: {rate:.2f}")
|
logger.info(f"Valor de respaldo generado: {rate:.2f}")
|
||||||
|
|
||||||
# Comprobar si la tarifa es superior a 200.00 USD/hora
|
# Comprobar si la tarifa es superior al umbral establecido
|
||||||
rate_threshold = 200.00
|
|
||||||
if rate > rate_threshold:
|
if rate > rate_threshold:
|
||||||
# Buscar todas las tarifas del mismo tipo de programador para calcular el promedio
|
logger.warning(f"Tarifa {rate:.2f} supera el umbral de {rate_threshold}. Ajustando...")
|
||||||
same_type_rates = []
|
rate = min(rate, rate_threshold)
|
||||||
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)
|
|
||||||
|
|
||||||
# Guardar el resultado en el archivo - solo el valor numérico con dos decimales, sin salto de línea
|
# 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:
|
with open(rate_file, 'w', encoding='utf-8') as f:
|
||||||
f.write(f"{rate:.2f}")
|
f.write(f"{rate:.2f}")
|
||||||
|
|
||||||
# Registrar en el log y mostrar el resultado en la consola
|
# 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"
|
result_message = f"Tarifa para {programmer_type}: {rate:.2f} USD/hora"
|
||||||
logger.info(f"Actualizado {os.path.basename(rate_file)} con valor: {rate:.2f}")
|
logger.info(f"Creado archivo {rate_file} con valor: {rate:.2f}")
|
||||||
show_result(result_message)
|
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__":
|
if __name__ == "__main__":
|
||||||
# Parámetros de línea de comandos para controlar el comportamiento
|
# Parámetros de línea de comandos para controlar el comportamiento
|
||||||
|
|
Loading…
Reference in a new issue