diff --git a/busqueda/.faaso.yml b/busqueda/.faaso.yml new file mode 100644 index 0000000..398df70 --- /dev/null +++ b/busqueda/.faaso.yml @@ -0,0 +1,5 @@ +--- +hosts: + https://faaso-prod.ralsina.me/admin/: + - admin + - Neih4ziefioshihaezu3Rienga7baeng diff --git a/busqueda/README.md b/busqueda/README.md new file mode 100644 index 0000000..2518e6a --- /dev/null +++ b/busqueda/README.md @@ -0,0 +1,11 @@ +# Readme for Busqueda + +This is a funko using the Crystal runtime for [FaaSO](https://git.ralsina.me/ralsina/faaso) + +## What is Busqueda + +Write here what it is + +## How to use Busqueda + +And so on. \ No newline at end of file diff --git a/busqueda/__init__.py b/busqueda/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/busqueda/funko.cr b/busqueda/funko.cr new file mode 100644 index 0000000..a8330d6 --- /dev/null +++ b/busqueda/funko.cr @@ -0,0 +1,148 @@ +require "json" +require "kemal" +require "pg" +require "pool/connection" + +# get credentials from secrets + +USER = File.read("secrets/user").strip +PASS = File.read("secrets/pass").strip + +# Create a connection pool to the database +pg = ConnectionPool.new(capacity: 5, timeout: 0.01.seconds) do + PG.connect("postgres://#{USER}:#{PASS}@pinky:5432/nombres") +end + +def normalize(s : String) : String + s.unicode_normalize(:nfkd) + .chars.reject! { |character| + !character.ascii_letter? && (character != ' ') + }.join("").downcase +end + +# A basic hello world get endpoint +post "/" do |env| + prefijo = env.params.json["p"].as(String) + genero = env.params.json["g"].as(String) + year = env.params.json["a"].as(String) + + p! prefijo, genero, year + + prefijo = normalize(prefijo) + if !["f", "m"].includes?(genero) + genero = nil + end + datos = [] of Tuple(String, Int32 | String) + # Connect using credentials provided + pg.connection do |cursor| + if prefijo.empty? && year.empty? + result_set = cursor.query(" + SELECT nombre, total::integer + FROM totales + ORDER BY total DESC + LIMIT 50") + elsif prefijo.empty? && !year.empty? + # Per-year totals + result_set = cursor.query(" + SELECT nombre, contador::integer + FROM nombres + WHERE + anio = $1 + ORDER BY contador DESC + LIMIT 50", year) + elsif !prefijo.empty? && year.empty? + # Filter only by prefix + result_set = cursor.query(" + SELECT nombre, total::integer + FROM totales + WHERE + nombre LIKE $1 + ORDER BY total DESC + LIMIT 50", prefijo + "%") + elsif !prefijo.empty? && !year.empty? + # We have both + result_set = cursor.query(" + SELECT nombre, contador::integer + FROM nombres + WHERE + anio = $1 AND + nombre LIKE $2 + ORDER BY contador DESC + LIMIT 50", year, prefijo + "%") + end + + if !result_set.nil? + result_set.each do + nombre = result_set.read(String) + valor = result_set.read(Int32) + datos.push({nombre, valor}) + end + result_set.close + end + + if datos.empty? + raise "No data found" + end + end + # In this context, remove all composite names + datos.reject! { |row| + row[0].to_s.includes? " " + } + datos.insert(0, {"Nombre", "Cuantos?"}) + + if genero + pg.connection do |cursor| + datos.reject! { |row| + # How feminine is this name? + # Yes this database is upper case + puts "Checking #{row[1]} #{row[0]}" + feminidad = 0 + sql = %( + SELECT COALESCE((SELECT frecuencia FROM mujeres WHERE nombre='#{row[0]?.to_s.upcase}'), 0) AS mujeres, + COALESCE((SELECT frecuencia FROM hombres WHERE nombre='#{row[0]?.to_s.upcase}'), 0) AS hombres + ) + puts "SQL: #{sql}" + cursor.query sql do |result_set| + result_set.each do + mujeres = result_set.read(Int32) + hombres = result_set.read(Int32) + puts "frecuencias: #{mujeres} #{hombres}" + if hombres == mujeres == 0 + feminidad = 0.5 + else + feminidad = mujeres / (hombres + mujeres) + end + end + end + # El overlap en 0.5 es intencional! + if (feminidad >= 0.5 && genero == "f") || + (feminidad <= 0.5 && genero == "m") + false + else + true + end + } + puts "Data split by gender" + end + end + datos = datos[..10].map { |row| + [row[0].capitalize, row[1]] + } + + if datos.size > 1 + title = "¿Puede ser ... #{datos[0][1].to_s.titleize}? ¿O capaz que #{datos[1][1].to_s.titleize}? ¡Contame más!" + elsif datos.size == 1 + title = "Me parece que ... #{datos[0][1].to_s.titleize}!" + else + title = "No tengo idea!" + end + { + "title" => title, + "data" => datos, + }.to_json +end + +get "/ping/" do + pg.connection.exec("SELECT 42") + "OK" +end diff --git a/busqueda/funko.yml b/busqueda/funko.yml new file mode 100644 index 0000000..1e3e507 --- /dev/null +++ b/busqueda/funko.yml @@ -0,0 +1,11 @@ +name: busqueda +runtime: kemal +options: + shard_build_options: "--release" + ship_packages: [] + devel_packages: [] + healthcheck_options: "--interval=1m --timeout=2s --start-period=2s --retries=3" + healthcheck_command: "curl --fail http://localhost:3000/ping || exit 1" + copy_from_build: + - "public public" + - "bin/funko ." \ No newline at end of file diff --git a/busqueda/handler.py b/busqueda/handler.py deleted file mode 100644 index 2f4fe3f..0000000 --- a/busqueda/handler.py +++ /dev/null @@ -1,164 +0,0 @@ -import unicodedata -import urllib -from collections import namedtuple as nt -from dataclasses import dataclass -from json import loads -import logging - -import pygal -import pyrqlite.dbapi2 as dbapi2 -import requests - -connection = dbapi2.connect( - host="10.61.0.1", - user="root", - port=4001, - password="", -) - - -def remove_accents(input_str): - nfkd_form = unicodedata.normalize("NFKD", input_str) - return "".join([c for c in nfkd_form if not unicodedata.combining(c)]) - - -def femininidad(nombre): - sql1 = """ - SELECT COALESCE(frecuencia,0) - FROM mujeres WHERE nombre=:nombre - """ - sql2 = """ - SELECT COALESCE(frecuencia,0) - FROM hombres WHERE nombre=:nombre - """ - with connection.cursor() as cursor: - nombre = nombre.strip().split()[0].strip().upper() - mujeres = cursor.execute(sql1, {"nombre": nombre}).fetchone() - mujeres = 0 if mujeres is None else mujeres[0] - hombres = cursor.execute(sql2, {"nombre": nombre}).fetchone() - hombres = 0 if hombres is None else hombres[0] - if hombres == mujeres == 0: - return 0.5 - return mujeres / (hombres + mujeres) - - -def split_por_genero(nombres): - femeninos = [] - masculinos = [] - for n in nombres: - fem = femininidad(n[1]) - if fem is None: - femeninos.append(n) - masculinos.append(n) - elif fem >= 0.5: - femeninos.append(n) - else: - masculinos.append(n) - return {"f": femeninos, "m": masculinos} - - -def handle(req): - """handle a request to the function - Args: - req (str): request body - - { - p: prefijo del nombre, - g: genero del nombre, - y: año de nacimiento - } - - """ - - try: - data = loads(req) - except Exception as e: - data = {} - - try: - prefijo = data.get("p") or None - genero = data.get("g") or None - try: - año = int(data.get("a")) - except Exception: - año = None - except Exception as e: - prefijo = genero = año = None - return f"{req} -- {e}", 400 - - if prefijo is not None: - prefijo = prefijo.strip().lower() - - if genero not in ("f", "m"): - genero = None - - if prefijo is None and año is None: # Totales globales - with connection.cursor() as cursor: - sql = """ - SELECT total, nombre - FROM totales - ORDER BY total DESC - LIMIT 50 - """ - cursor.execute(sql) - datos = [(r["total"], r["nombre"]) for r in cursor.fetchall()] - - elif prefijo is None and año is not None: # Totales por año - with connection.cursor() as cursor: - sql = """ - SELECT contador, nombre - FROM nombres - WHERE - anio = :anio - ORDER BY contador DESC - LIMIT 50 - """ - cursor.execute(sql, {"anio": año}) - datos = [(r["contador"], r["nombre"]) for r in cursor.fetchall()] - - elif prefijo is not None and año is None: - with connection.cursor() as cursor: - sql = """ - SELECT total, nombre - FROM totales - WHERE - nombre LIKE :nombre - ORDER BY total DESC - LIMIT 50 - """ - cursor.execute(sql, {"nombre": f"{prefijo}%"}) - datos = [(r["total"], r["nombre"]) for r in cursor.fetchall()] - else: - with connection.cursor() as cursor: - sql = """ - SELECT contador, nombre - FROM nombres - WHERE - anio = :anio AND - nombre LIKE :nombre - ORDER BY contador DESC - LIMIT 50 - """ - cursor.execute(sql, {"anio": año, "nombre": f"{prefijo}%"}) - datos = [(r["contador"], r["nombre"]) for r in cursor.fetchall()] - - if genero: - datos = split_por_genero(datos)[genero] - - datos = datos[:10] - - chart = pygal.HorizontalBar(height=400, show_legend=False, show_y_labels=True) - chart.x_labels = [nombre.title() for _, nombre in datos[::-1]] - if len(datos) > 1: - chart.title = f"¿Puede ser ... {datos[0][1].title()}? ¿O capaz que {datos[1][1].title()}? ¡Contáme más!" - elif len(datos) == 1: - chart.title = f"¡Hola {datos[0][1].title()}!" - elif len(datos) < 1: - chart.title = "¡No esssistís!" - chart.add("", [contador for contador, _ in datos[::-1]]) - - return ( - chart.render(is_unicode=True), - 200, - {"Content-Type": "image/svg+xml"}, - ) diff --git a/busqueda/public/index.html b/busqueda/public/index.html new file mode 100644 index 0000000..f93807b --- /dev/null +++ b/busqueda/public/index.html @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+

Adivinar nombres por edad y género

+
+
+
+
+ + + + +
+
+
+ + diff --git a/busqueda/requirements.txt b/busqueda/requirements.txt deleted file mode 100644 index cbc289f..0000000 --- a/busqueda/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pygal -requests -pyrqlite diff --git a/busqueda/shard.yml b/busqueda/shard.yml new file mode 100644 index 0000000..f610413 --- /dev/null +++ b/busqueda/shard.yml @@ -0,0 +1,18 @@ +name: busqueda +version: 0.1.0 + +targets: + funko: + main: main.cr + +dependencies: + kemal: + github: kemalcr/kemal + pg: + github: will/crystal-pg + pool: + github: ysbaddaden/pool + +# development_dependencies: +# webmock: +# github: manastech/webmock.cr diff --git a/c-busqueda/handler.cr b/old-busqueda/handler.cr similarity index 100% rename from c-busqueda/handler.cr rename to old-busqueda/handler.cr diff --git a/c-busqueda/shard.yml b/old-busqueda/shard.yml similarity index 100% rename from c-busqueda/shard.yml rename to old-busqueda/shard.yml