caudal/programa/pruebas/test_interprete.py
2026-10-13 08:01:06 -05:00

189 lines
10 KiB
Python

"""
Pruebas del asistente (frases normales → operaciones propuestas). Sin red.
python3 -m unittest discover -s pruebas -v
"""
from __future__ import annotations
import datetime as dt
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from cartera.datos import Datos # noqa: E402
from cartera.interprete import _numero, interpretar, trocear # noqa: E402
HOY = dt.date(2026, 9, 17) # jueves
def _a(i, nombre, tipo, codigo="", unidad="participaciones", **extra):
return {"id": i, "nombre": nombre, "tipo": tipo, "codigo": codigo, "unidad": unidad,
"estado": "abierta", **extra}
DATOS = Datos(
[_a("F1", "Fondo Valor FI Clase C", "Fondo", "ES0000000011", estado="cerrada", fecha_cierre="2024-11-20"),
_a("F2", "Fondo Valor FI Clase B", "Fondo", "ES0000000029"),
_a("P1", "Ejemplo Pensiones PP", "PlanPensiones", "N0001"),
_a("C1", "Bitcoin", "Cripto", "bitcoin", "BTC"),
_a("M1", "Oro físico (caja fuerte)", "Metal", "XAU", "oz"),
_a("E1", "ETF Mundial", "ETF", "IWDA.AS"),
_a("PR2", "Préstamo P2P Ejemplo", "Prestamo", "", "€", fecha_inicio="2026-01-01",
prestamo={"importe_final": 5300.0, "vencimiento": "2026-12-01"})],
[{"id": "a", "fecha": "2025-01-01", "activo": "F2", "tipo": "Compra", "cantidad": 100, "precio": 200},
{"id": "b", "fecha": "2025-01-01", "activo": "C1", "tipo": "Compra", "cantidad": 0.05, "precio": 60000},
{"id": "c", "fecha": "2025-03-01", "activo": "C1", "tipo": "Gasto", "importe": 70},
{"id": "d", "fecha": "2026-01-01", "activo": "PR2", "tipo": "Compra", "cantidad": 1, "precio": 5000, "importe": 5000},
{"id": "e", "fecha": "2025-01-01", "activo": "E1", "tipo": "Compra", "cantidad": 40, "precio": 100}],
[{"fecha": "2026-09-14", "activo": "P1", "precio": 12.5}, {"fecha": "2026-09-15", "activo": "C1", "precio": 98000.0}],
[])
def una(texto, cambio=lambda f, m: 0.85):
r = interpretar(texto, DATOS, HOY, cambio)
assert len(r) == 1, [x["frase"] for x in r]
return r[0]
class TestNumerosYFechas(unittest.TestCase):
def test_numeros(self):
for texto, valor in (("1.234,56", 1234.56), ("70.500", 70500), ("0.0012", 0.0012), ("0,0012", 0.0012),
("12.345", 12345), ("1.5", 1.5), ("125", 125)):
self.assertAlmostEqual(_numero(texto), valor, msg=texto)
def test_fechas(self):
for texto, fecha in (("ayer aporté 100 € al plan", "2026-09-16"), ("el 14 aporté 100 € al plan", "2026-09-14"),
("el 20 aporté 100 € al plan", "2026-08-20"), ("el lunes aporté 100 € al plan", "2026-09-14"),
("el 3 de octubre compré 0,001 btc por 80 €", "2025-10-03"),
("compré 0,001 btc por 80 € el 3/9", "2026-09-03"),
("compré 0,001 btc por 80 € el 21/03/2025", "2025-03-21")):
self.assertEqual(una(texto)["movimiento"]["fecha"], fecha, texto)
self.assertIn("No dices la fecha: pongo hoy.", una("compré 0,001 btc por 80 €")["avisos"])
def test_varias_operaciones(self):
self.assertEqual(trocear("aporté 100 € al plan y compré 0,001 btc por 90 €. he minado 100 sats"),
["aporté 100 € al plan", "compré 0,001 btc por 90 €", "he minado 100 sats"])
self.assertEqual(trocear("compré oro y plata por 100 €"), ["compré oro y plata por 100 €"])
class TestPropuestas(unittest.TestCase):
def test_aportacion_al_plan_estimada(self):
r = una("ayer aporté 100 € al plan")
m = r["movimiento"]
self.assertTrue(r["completa"])
self.assertEqual((m["activo"], m["tipo"], m["importe"], m["precio"]), ("P1", "Compra", 100, 12.5))
self.assertAlmostEqual(m["cantidad"], 8)
self.assertTrue(m["estimado"])
def test_mineria_en_sats(self):
m = una("he minado 12.345 sats")["movimiento"]
self.assertEqual((m["activo"], m["tipo"]), ("C1", "Mineria"))
self.assertAlmostEqual(m["cantidad"], 0.00012345)
self.assertIsNone(m["importe"])
def test_hosting_en_dolares(self):
r = una("pagué 50 $ del hosting el día 1")
m = r["movimiento"]
self.assertEqual((m["activo"], m["tipo"], m["fecha"], m["moneda"], m["importe_original"]),
("C1", "Gasto", "2026-09-01", "USD", 50))
self.assertAlmostEqual(m["importe"], 42.5)
sin_red = una("pagué 50 $ del hosting", cambio=lambda f, mon: None)
self.assertFalse(sin_red["completa"])
def test_compra_con_precio_y_miles(self):
m = una("compré 0.0012 BTC a 70.500 € el 3/9")["movimiento"]
self.assertAlmostEqual(m["cantidad"], 0.0012)
self.assertEqual(m["precio"], 70500)
self.assertAlmostEqual(m["importe"], 84.6)
def test_venta_con_comision(self):
m = una("vendí 0,01 btc por 1000€ con 2 € de comisión")["movimiento"]
self.assertEqual((m["tipo"], m["importe"], m["comisiones"]), ("Venta", 1000, 2))
def test_vender_todo_propone_cerrar(self):
r = una("vendí todo el etf por 5000 €")
self.assertEqual(r["accion"], "cierre")
self.assertEqual(r["cierre"], {"id": "E1", "fecha": "2026-09-17", "importe": 5000})
def test_cobro_de_prestamo(self):
r = una("cobré 5.300 € de p2p ejemplo el 20/06/2026")
self.assertEqual(r["accion"], "cierre")
self.assertEqual(r["cierre"]["importe"], 5300)
def test_el_fondo_va_a_la_clase_abierta(self):
self.assertEqual(una("vendí 10 participaciones del valor a 250 €")["movimiento"]["activo"], "F2")
self.assertEqual(una("vendí 10 participaciones del valor clase b a 250 €")["movimiento"]["activo"], "F2")
def test_no_vende_lo_que_no_tiene(self):
r = una("vendí 1 btc a 100000 €")
self.assertFalse(r["completa"])
def test_pide_lo_que_falta(self):
r = una("compré 100 € de algo raro")
self.assertFalse(r["completa"])
self.assertTrue(r["opciones"])
self.assertFalse(una("compré bitcoin")["completa"])
self.assertFalse(una("traspasé el valorium al plan")["completa"])
def test_activo_nuevo_busqueda_y_alta(self):
from cartera.interprete import termino_busqueda
for frase, termino in (("compré 10 acciones de Iberdrola a 12,50 €", "iberdrola"),
("ayer compré 20 ACME por 1.800 $", "ACME"),
("compré 1.000 € del IE00B4L5Y983", "IE00B4L5Y983"),
("he comprado 5 acciones de banco santander", "banco santander")):
self.assertEqual(termino_busqueda(frase), termino, frase)
r = una("compré 10 acciones de Iberdrola a 12,50 €")
self.assertEqual(r["busqueda"], "iberdrola")
self.assertFalse(r["completa"])
nuevo = {"nombre": "Iberdrola, S.A.", "codigo": "IBE.MC", "tipo": "Accion", "unidad": "acciones",
"precio": 13.0, "fecha_precio": "2026-09-16"}
r = interpretar("compré 10 acciones de Iberdrola a 12,50 €", DATOS, HOY, None, activo_nuevo=nuevo)[0]
self.assertTrue(r["completa"])
self.assertEqual((r["accion"], r["movimiento"]["cantidad"], r["movimiento"]["importe"]), ("alta", 10, 125))
r = interpretar("ayer compré 260 € de iberdrola", DATOS, HOY, None, activo_nuevo=nuevo)[0]
self.assertTrue(r["completa"])
self.assertAlmostEqual(r["movimiento"]["cantidad"], 20) # estimada con su último precio
self.assertTrue(r["movimiento"]["estimado"])
def test_dividendo(self):
datos = Datos(DATOS.activos + [_a("A1", "ACCION EJEMPLO SA", "Accion", "ES0000000037", "acciones", alias="ejemplo, ejm")],
DATOS.movimientos, DATOS.precios, [])
r = interpretar("me han ingresado 12,40 € de dividendo de ejemplo con 3,10 € de retención", datos, HOY)[0]
m = r["movimiento"]
self.assertTrue(r["completa"])
self.assertEqual((m["activo"], m["tipo"], m["importe"], m["retencion"], m["cantidad"]), ("A1", "Dividendo", 12.4, 3.1, None))
self.assertEqual(interpretar("cobré 15 de dividendos del ejm el día 1", datos, HOY)[0]["movimiento"]["importe"], 15)
def test_recompensa_y_redondeo(self):
datos = Datos(DATOS.activos + [_a("A1", "ACCION EJEMPLO SA", "Accion", "ES0000000037", "acciones", alias="ejm")],
DATOS.movimientos, DATOS.precios + [{"fecha": "2026-09-15", "activo": "A1", "precio": 80.0}], [])
r = interpretar("save back de ejm: 0,1234 acciones por 10,20 €", datos, HOY)[0]
m = r["movimiento"]
self.assertTrue(r["completa"])
self.assertEqual((m["tipo"], m["cantidad"], m["valor_recibido"], m["importe"]), ("Recompensa", 0.1234, 10.2, None))
r = interpretar("ayer el save back me dio 8 € de ejm", datos, HOY)[0]
self.assertAlmostEqual(r["movimiento"]["cantidad"], 0.1) # estimada con el precio del día
m = interpretar("roundup de ejm 0,35 acciones por 28 €", datos, HOY)[0]["movimiento"]
self.assertEqual((m["tipo"], m["cantidad"], m["importe"]), ("Compra", 0.35, 28))
def test_saldo_de_una_cuenta(self):
datos = Datos(DATOS.activos + [_a("CC1", "Bankinter", "Cuenta", "", "€"), _a("CC2", "Revolut", "Cuenta", "", "€")],
DATOS.movimientos, DATOS.precios, [])
r = interpretar("saldo de bankinter 1.234,56 €", datos, HOY)[0]
self.assertEqual((r["accion"], r["completa"]), ("saldo", True))
self.assertEqual(r["saldo"], {"activo": "CC1", "fecha": "2026-09-17", "valor": 1234.56})
r = interpretar("ayer en revolut tenía 300 €", datos, HOY)[0]
self.assertEqual((r["accion"], r["saldo"]["activo"], r["saldo"]["fecha"]), ("saldo", "CC2", "2026-09-16"))
self.assertFalse(interpretar("saldo de bankinter", datos, HOY)[0]["completa"])
def test_activo_elegido_por_el_usuario(self):
r = interpretar("compré 2 participaciones por 400 €", DATOS, HOY, None, activo_forzado="F2")[0]
self.assertTrue(r["completa"])
self.assertEqual(r["movimiento"]["precio"], 200)
if __name__ == "__main__":
unittest.main()