Compare commits
8 Commits
faaso
...
0e3870a6fa
Author | SHA1 | Date | |
---|---|---|---|
0e3870a6fa | |||
dad3597c1b | |||
ea9e65a20b | |||
19f25017f0 | |||
b05327fd8d | |||
9a84e979ac | |||
539c0839db | |||
cb9a24f4aa |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
.faaso.yml
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
# 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.
|
|
0
busqueda/__init__.py
Normal file
0
busqueda/__init__.py
Normal file
@@ -1,149 +0,0 @@
|
|||||||
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
|
|
||||||
DBHOST = File.read("/secrets/dbhost").strip
|
|
||||||
|
|
||||||
DBURL = "postgres://#{USER}:#{PASS}@#{DBHOST}:5432/nombres"
|
|
||||||
puts "Connnecting to #{DBURL}"
|
|
||||||
|
|
||||||
# Create a connection pool to the database
|
|
||||||
pg = ConnectionPool.new(capacity: 5, timeout: 1.seconds) do
|
|
||||||
PG.connect(DBURL)
|
|
||||||
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)
|
|
||||||
|
|
||||||
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 > 2
|
|
||||||
title = "¿Puede ser ... #{datos[1][0].to_s.titleize}? ¿O capaz que #{datos[2][0].to_s.titleize}? ¡Contame más!"
|
|
||||||
elsif datos.size == 2
|
|
||||||
title = "Me parece que ... #{datos[1][0].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
|
|
@@ -1,11 +0,0 @@
|
|||||||
name: busqueda
|
|
||||||
runtime: kemal
|
|
||||||
options:
|
|
||||||
shard_build_options: "--release -d --error-trace"
|
|
||||||
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 ."
|
|
164
busqueda/handler.py
Normal file
164
busqueda/handler.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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"},
|
||||||
|
)
|
@@ -1,243 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-theme="dark">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="https://www.gstatic.com/charts/loader.js"
|
|
||||||
></script>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
|
|
||||||
/>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Kode+Mono&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<script type="text/javascript">
|
|
||||||
google.charts.load("current", { packages: ["corechart"] });
|
|
||||||
google.charts.setOnLoadCallback(drawChart);
|
|
||||||
|
|
||||||
async function drawChart() {
|
|
||||||
fetch("/", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
p: document.getElementById("p").value,
|
|
||||||
g: document.getElementById("g").value,
|
|
||||||
a: document.getElementById("a").value,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
title = json["title"];
|
|
||||||
data = json["data"];
|
|
||||||
console.log(data);
|
|
||||||
data = google.visualization.arrayToDataTable(data);
|
|
||||||
var options = {
|
|
||||||
title: title,
|
|
||||||
titleTextStyle: {
|
|
||||||
color: "#aaa",
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
startup: true,
|
|
||||||
duration: 1000,
|
|
||||||
easing: "out",
|
|
||||||
},
|
|
||||||
backgroundColor: "#1c212c",
|
|
||||||
vAxis: {
|
|
||||||
minValue: 0,
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
hAxis: {
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
legend: { position: 'none'},
|
|
||||||
};
|
|
||||||
|
|
||||||
var chart = new google.visualization.BarChart(
|
|
||||||
document.getElementById("chart")
|
|
||||||
);
|
|
||||||
|
|
||||||
chart.draw(data, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
html * {
|
|
||||||
font-family: "Quicksand", sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main class="container" style="text-align: center">
|
|
||||||
<header>
|
|
||||||
<h1>Adivinar nombres por edad y género</h1>
|
|
||||||
</header>
|
|
||||||
<div id="chart" style="width: 80vw; height: 50vh; margin: auto"></div>
|
|
||||||
<form
|
|
||||||
onSubmit="return false;"
|
|
||||||
style="margin: auto; margin-top: 2em; width: 80%"
|
|
||||||
>
|
|
||||||
<fieldset class="grid">
|
|
||||||
<input type="text" name="p" id="p" placeholder="Como empieza tu nombre?" />
|
|
||||||
<select name="a" id="a">
|
|
||||||
<option selected value="">Año?</option>
|
|
||||||
<option value="1922">1922</option>
|
|
||||||
<option value="1923">1923</option>
|
|
||||||
<option value="1924">1924</option>
|
|
||||||
<option value="1925">1925</option>
|
|
||||||
<option value="1926">1926</option>
|
|
||||||
<option value="1927">1927</option>
|
|
||||||
<option value="1928">1928</option>
|
|
||||||
<option value="1929">1929</option>
|
|
||||||
<option value="1930">1930</option>
|
|
||||||
<option value="1931">1931</option>
|
|
||||||
<option value="1932">1932</option>
|
|
||||||
<option value="1933">1933</option>
|
|
||||||
<option value="1934">1934</option>
|
|
||||||
<option value="1935">1935</option>
|
|
||||||
<option value="1936">1936</option>
|
|
||||||
<option value="1937">1937</option>
|
|
||||||
<option value="1938">1938</option>
|
|
||||||
<option value="1939">1939</option>
|
|
||||||
<option value="1940">1940</option>
|
|
||||||
<option value="1941">1941</option>
|
|
||||||
<option value="1942">1942</option>
|
|
||||||
<option value="1943">1943</option>
|
|
||||||
<option value="1944">1944</option>
|
|
||||||
<option value="1945">1945</option>
|
|
||||||
<option value="1946">1946</option>
|
|
||||||
<option value="1947">1947</option>
|
|
||||||
<option value="1948">1948</option>
|
|
||||||
<option value="1949">1949</option>
|
|
||||||
<option value="1950">1950</option>
|
|
||||||
<option value="1951">1951</option>
|
|
||||||
<option value="1952">1952</option>
|
|
||||||
<option value="1953">1953</option>
|
|
||||||
<option value="1954">1954</option>
|
|
||||||
<option value="1955">1955</option>
|
|
||||||
<option value="1956">1956</option>
|
|
||||||
<option value="1957">1957</option>
|
|
||||||
<option value="1958">1958</option>
|
|
||||||
<option value="1959">1959</option>
|
|
||||||
<option value="1960">1960</option>
|
|
||||||
<option value="1961">1961</option>
|
|
||||||
<option value="1962">1962</option>
|
|
||||||
<option value="1963">1963</option>
|
|
||||||
<option value="1964">1964</option>
|
|
||||||
<option value="1965">1965</option>
|
|
||||||
<option value="1966">1966</option>
|
|
||||||
<option value="1967">1967</option>
|
|
||||||
<option value="1968">1968</option>
|
|
||||||
<option value="1969">1969</option>
|
|
||||||
<option value="1970">1970</option>
|
|
||||||
<option value="1971">1971</option>
|
|
||||||
<option value="1972">1972</option>
|
|
||||||
<option value="1973">1973</option>
|
|
||||||
<option value="1974">1974</option>
|
|
||||||
<option value="1975">1975</option>
|
|
||||||
<option value="1976">1976</option>
|
|
||||||
<option value="1977">1977</option>
|
|
||||||
<option value="1978">1978</option>
|
|
||||||
<option value="1979">1979</option>
|
|
||||||
<option value="1980">1980</option>
|
|
||||||
<option value="1981">1981</option>
|
|
||||||
<option value="1982">1982</option>
|
|
||||||
<option value="1983">1983</option>
|
|
||||||
<option value="1984">1984</option>
|
|
||||||
<option value="1985">1985</option>
|
|
||||||
<option value="1986">1986</option>
|
|
||||||
<option value="1987">1987</option>
|
|
||||||
<option value="1988">1988</option>
|
|
||||||
<option value="1989">1989</option>
|
|
||||||
<option value="1990">1990</option>
|
|
||||||
<option value="1991">1991</option>
|
|
||||||
<option value="1992">1992</option>
|
|
||||||
<option value="1993">1993</option>
|
|
||||||
<option value="1994">1994</option>
|
|
||||||
<option value="1995">1995</option>
|
|
||||||
<option value="1996">1996</option>
|
|
||||||
<option value="1997">1997</option>
|
|
||||||
<option value="1998">1998</option>
|
|
||||||
<option value="1999">1999</option>
|
|
||||||
<option value="2000">2000</option>
|
|
||||||
<option value="2001">2001</option>
|
|
||||||
<option value="2002">2002</option>
|
|
||||||
<option value="2003">2003</option>
|
|
||||||
<option value="2004">2004</option>
|
|
||||||
<option value="2005">2005</option>
|
|
||||||
<option value="1966">1966</option>
|
|
||||||
<option value="1967">1967</option>
|
|
||||||
<option value="1968">1968</option>
|
|
||||||
<option value="1969">1969</option>
|
|
||||||
<option value="1970">1970</option>
|
|
||||||
<option value="1971">1971</option>
|
|
||||||
<option value="1972">1972</option>
|
|
||||||
<option value="1973">1973</option>
|
|
||||||
<option value="1974">1974</option>
|
|
||||||
<option value="1975">1975</option>
|
|
||||||
<option value="1976">1976</option>
|
|
||||||
<option value="1977">1977</option>
|
|
||||||
<option value="1978">1978</option>
|
|
||||||
<option value="1979">1979</option>
|
|
||||||
<option value="1980">1980</option>
|
|
||||||
<option value="1981">1981</option>
|
|
||||||
<option value="1982">1982</option>
|
|
||||||
<option value="1983">1983</option>
|
|
||||||
<option value="1984">1984</option>
|
|
||||||
<option value="1985">1985</option>
|
|
||||||
<option value="1986">1986</option>
|
|
||||||
<option value="1987">1987</option>
|
|
||||||
<option value="1988">1988</option>
|
|
||||||
<option value="1989">1989</option>
|
|
||||||
<option value="1990">1990</option>
|
|
||||||
<option value="1991">1991</option>
|
|
||||||
<option value="1992">1992</option>
|
|
||||||
<option value="1993">1993</option>
|
|
||||||
<option value="1994">1994</option>
|
|
||||||
<option value="1995">1995</option>
|
|
||||||
<option value="1996">1996</option>
|
|
||||||
<option value="1997">1997</option>
|
|
||||||
<option value="1998">1998</option>
|
|
||||||
<option value="1999">1999</option>
|
|
||||||
<option value="2000">2000</option>
|
|
||||||
<option value="2001">2001</option>
|
|
||||||
<option value="2002">2002</option>
|
|
||||||
<option value="2003">2003</option>
|
|
||||||
<option value="2004">2004</option>
|
|
||||||
<option value="2005">2005</option>
|
|
||||||
</select>
|
|
||||||
<select name="g" id="g" />
|
|
||||||
<option selected value="">Género?</option>
|
|
||||||
<option value="f">Femenino</option>
|
|
||||||
<option value="m">Masculino</option>
|
|
||||||
</select>
|
|
||||||
<input type="submit" value="Buscar" onCLick="drawChart();" />
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
3
busqueda/requirements.txt
Normal file
3
busqueda/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pygal
|
||||||
|
requests
|
||||||
|
pyrqlite
|
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
@@ -12,9 +12,6 @@ PASS = File.read("/var/openfaas/secrets/nombres-pass").strip
|
|||||||
DB_URL = "postgres://#{USER}:#{PASS}@10.61.0.1:5432/nombres"
|
DB_URL = "postgres://#{USER}:#{PASS}@10.61.0.1:5432/nombres"
|
||||||
|
|
||||||
class Handler
|
class Handler
|
||||||
# This class is the entry point for the OpenFaaS function.
|
|
||||||
# run() is the important bit
|
|
||||||
|
|
||||||
def format_buffer(buffer, canvas_name, title = "")
|
def format_buffer(buffer, canvas_name, title = "")
|
||||||
# Process the gnuplot output so it works in the page
|
# Process the gnuplot output so it works in the page
|
||||||
#
|
#
|
||||||
@@ -53,6 +50,50 @@ class Handler
|
|||||||
}.join("").downcase
|
}.join("").downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def feminidad(cursor, nombre)
|
||||||
|
# Yes this database is upper case
|
||||||
|
nombre = nombre.to_s.upcase
|
||||||
|
sql1 = %(
|
||||||
|
SELECT COALESCE(frecuencia,0)
|
||||||
|
FROM mujeres WHERE nombre='#{nombre}'
|
||||||
|
)
|
||||||
|
sql2 = %(
|
||||||
|
SELECT COALESCE(frecuencia,0)
|
||||||
|
FROM hombres WHERE nombre='#{nombre}'
|
||||||
|
)
|
||||||
|
|
||||||
|
hombres = mujeres = 0
|
||||||
|
cursor.query sql1 do |result|
|
||||||
|
mujeres = result.read(Int32)
|
||||||
|
end
|
||||||
|
cursor.query sql2 do |result|
|
||||||
|
hombres = result.read(Int32)
|
||||||
|
end
|
||||||
|
if hombres == mujeres == 0
|
||||||
|
return 0.5
|
||||||
|
end
|
||||||
|
mujeres / (hombres + mujeres)
|
||||||
|
end
|
||||||
|
|
||||||
|
# def split_por_genero(cursor, nombres)
|
||||||
|
# femeninos = Array(Tuple(Int32, String)).new
|
||||||
|
# masculinos = Array(Tuple(Int32, String)).new
|
||||||
|
# nombres.map { |nombre|
|
||||||
|
# fem = feminidad(cursor, nombre[1])
|
||||||
|
# # El overlap en 0.5 es intencional!
|
||||||
|
# if fem >= 0.5
|
||||||
|
# femeninos << nombre
|
||||||
|
# end
|
||||||
|
# if fem <= 0.5
|
||||||
|
# masculinos << nombre
|
||||||
|
# end
|
||||||
|
# }
|
||||||
|
# {
|
||||||
|
# "f": femeninos,
|
||||||
|
# "m": masculinos,
|
||||||
|
# }
|
||||||
|
# end
|
||||||
|
|
||||||
def run(request : HTTP::Request)
|
def run(request : HTTP::Request)
|
||||||
# Try to find most popular names based on a prefix, year and gender.
|
# Try to find most popular names based on a prefix, year and gender.
|
||||||
#
|
#
|
||||||
@@ -88,52 +129,58 @@ class Handler
|
|||||||
|
|
||||||
year = year.to_i?
|
year = year.to_i?
|
||||||
|
|
||||||
datos = [] of Tuple(Int32, String)
|
|
||||||
DB.open(DB_URL) do |cursor|
|
|
||||||
if prefijo.nil? && year.nil?
|
if prefijo.nil? && year.nil?
|
||||||
# Global totals
|
# Global totals
|
||||||
result_set = cursor.query("
|
# FIXME: SLOW
|
||||||
|
sql = %(
|
||||||
SELECT total::integer, nombre
|
SELECT total::integer, nombre
|
||||||
FROM totales
|
FROM totales
|
||||||
ORDER BY total DESC
|
ORDER BY total DESC
|
||||||
LIMIT 50")
|
LIMIT 50
|
||||||
|
)
|
||||||
elsif prefijo.nil? && !year.nil?
|
elsif prefijo.nil? && !year.nil?
|
||||||
# Per-year totals
|
# Per-year totals
|
||||||
result_set = cursor.query("
|
sql = %(
|
||||||
SELECT contador::integer, nombre
|
SELECT contador::integer, nombre
|
||||||
FROM nombres
|
FROM nombres
|
||||||
WHERE
|
WHERE
|
||||||
anio = $1
|
anio = '#{year}'
|
||||||
ORDER BY contador DESC
|
ORDER BY contador DESC
|
||||||
LIMIT 50", year)
|
LIMIT 50
|
||||||
|
)
|
||||||
elsif !prefijo.nil? && year.nil?
|
elsif !prefijo.nil? && year.nil?
|
||||||
# Filter only by prefix
|
# Filter only by prefix
|
||||||
result_set = cursor.query("
|
sql = %(
|
||||||
SELECT total::integer, nombre
|
SELECT total, nombre
|
||||||
FROM totales
|
FROM totales
|
||||||
WHERE
|
WHERE
|
||||||
nombre LIKE $1
|
nombre LIKE '#{prefijo}%'
|
||||||
ORDER BY total DESC
|
ORDER BY total DESC
|
||||||
LIMIT 50", prefijo + "%")
|
LIMIT 50
|
||||||
elsif !prefijo.nil? && !year.nil?
|
)
|
||||||
|
else
|
||||||
# We have both
|
# We have both
|
||||||
result_set = cursor.query("
|
sql = %(
|
||||||
SELECT contador::integer, nombre
|
SELECT contador, nombre
|
||||||
FROM nombres
|
FROM nombres
|
||||||
WHERE
|
WHERE
|
||||||
anio = $1 AND
|
anio = '#{year}' AND
|
||||||
nombre LIKE $2
|
nombre LIKE '#{prefijo}%'
|
||||||
ORDER BY contador DESC
|
ORDER BY contador DESC
|
||||||
LIMIT 50", year, prefijo + "%")
|
LIMIT 50
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if !result_set.nil?
|
puts "QUERY: #{sql}"
|
||||||
|
|
||||||
|
datos = [] of Tuple(Int32, String)
|
||||||
|
DB.open(DB_URL) do |cursor|
|
||||||
|
cursor.query sql do |result_set|
|
||||||
result_set.each do
|
result_set.each do
|
||||||
valor = result_set.read(Int32)
|
valor = result_set.read(Int32)
|
||||||
nombre = result_set.read(String)
|
nombre = result_set.read(String)
|
||||||
datos.push({valor, nombre})
|
datos.push({valor, nombre})
|
||||||
end
|
end
|
||||||
result_set.close
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -155,36 +202,33 @@ class Handler
|
|||||||
|
|
||||||
if genero
|
if genero
|
||||||
DB.open(DB_URL) do |cursor|
|
DB.open(DB_URL) do |cursor|
|
||||||
datos.reject! { |row|
|
datos = datos.find do |item|
|
||||||
# How feminine is this name?
|
# How feminine is this name?
|
||||||
# Yes this database is upper case
|
# Yes this database is upper case
|
||||||
puts "Checking #{row[0]} #{row[1]}"
|
nombre = item[1].upcase
|
||||||
|
puts "Checking #{nombre}"
|
||||||
feminidad = 0
|
feminidad = 0
|
||||||
sql = %(
|
sql = %(
|
||||||
SELECT COALESCE((SELECT frecuencia FROM mujeres WHERE nombre='#{row[1]?.to_s.upcase}'), 0) AS mujeres,
|
SELECT COALESCE((SELECT frecuencia FROM mujeres WHERE nombre='#{nombre}'), 0) AS mujeres,
|
||||||
COALESCE((SELECT frecuencia FROM hombres WHERE nombre='#{row[1]?.to_s.upcase}'), 0) AS hombres
|
COALESCE((SELECT frecuencia FROM hombres WHERE nombre='#{nombre}'), 0) AS hombres
|
||||||
)
|
)
|
||||||
puts "SQL: #{sql}"
|
cursor.query sql do |result|
|
||||||
cursor.query sql do |result_set|
|
mujeres = result.read(Int32)
|
||||||
result_set.each do
|
hombres = result.read(Int32)
|
||||||
mujeres = result_set.read(Int32)
|
|
||||||
hombres = result_set.read(Int32)
|
|
||||||
puts "frecuencias: #{mujeres} #{hombres}"
|
|
||||||
if hombres == mujeres == 0
|
if hombres == mujeres == 0
|
||||||
feminidad = 0.5
|
feminidad = 0.5
|
||||||
else
|
else
|
||||||
feminidad = mujeres / (hombres + mujeres)
|
feminidad = mujeres / (hombres + mujeres)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
# El overlap en 0.5 es intencional!
|
# El overlap en 0.5 es intencional!
|
||||||
if (feminidad >= 0.5 && genero == "f") ||
|
if (feminidad >= 0.5 && genero == "f") ||
|
||||||
(feminidad <= 0.5 && genero == "m")
|
(feminidad <= 0.5 && genero == "m")
|
||||||
false
|
|
||||||
else
|
|
||||||
true
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
}
|
|
||||||
puts "Data split by gender"
|
puts "Data split by gender"
|
||||||
end
|
end
|
||||||
end
|
end
|
127
c-historico/handler.cr
Normal file
127
c-historico/handler.cr
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
require "http/client"
|
||||||
|
require "http/headers"
|
||||||
|
require "http/request"
|
||||||
|
require "ishi/html"
|
||||||
|
require "json"
|
||||||
|
require "uuid"
|
||||||
|
require "db"
|
||||||
|
require "pg"
|
||||||
|
|
||||||
|
USER = File.read("/var/openfaas/secrets/nombres-user").strip
|
||||||
|
PASS = File.read("/var/openfaas/secrets/nombres-pass").strip
|
||||||
|
|
||||||
|
class Handler
|
||||||
|
def format_buffer(buffer, canvas_name, title = "")
|
||||||
|
# Process the gnuplot output so it works in the page
|
||||||
|
#
|
||||||
|
# buffer is the Ishi output
|
||||||
|
# name is a string to replace for gnuplot_canvas so
|
||||||
|
# we can have multiple charts in a page
|
||||||
|
# title is added on top of the chart
|
||||||
|
|
||||||
|
html = buffer.to_s.split("\n")
|
||||||
|
html = html[html.index("<script type=\"text/javascript\">")..html.index("</script>")]
|
||||||
|
html = "<b>#{title}</b>" + html.join("\n") + %(
|
||||||
|
<div class="gnuplot">
|
||||||
|
<canvas id="Tile" width="32" height="32" hidden></canvas>
|
||||||
|
<table class="plot">
|
||||||
|
<tr><td>
|
||||||
|
<canvas id="gnuplot_canvas" width="800" height="300" tabindex="0">
|
||||||
|
Sorry, your browser seems not to support the HTML 5 canvas element
|
||||||
|
</canvas>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
<script type="text/javascript" defer>
|
||||||
|
gnuplot.init(); gnuplot_canvas();
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
# This ID needs to be unique in case
|
||||||
|
# we have 2 charts in the same page
|
||||||
|
html.gsub("gnuplot_canvas", canvas_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(sql)
|
||||||
|
# Runs a SQL query against the database.
|
||||||
|
#
|
||||||
|
# Returns an array of values [[Year,Count]...]
|
||||||
|
# Or nil if there are no results
|
||||||
|
|
||||||
|
DB.open("postgres://#{USER}:#{PASS}@10.61.0.1:5432/nombres") do |cursor|
|
||||||
|
cursor.query sql do |result_set|
|
||||||
|
result = [] of Tuple(Int32, Int32)
|
||||||
|
result_set.each do
|
||||||
|
year = result_set.read(Int32)
|
||||||
|
contador = result_set.read(Int32)
|
||||||
|
result.push({year, contador})
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# No result, return nil
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
nombres = [] of String
|
||||||
|
|
||||||
|
def normalize_name(s)
|
||||||
|
# Remove diacritics, turn lowercase
|
||||||
|
normalized = s.unicode_normalize(:nfkd).chars
|
||||||
|
normalized.reject! { |character|
|
||||||
|
!character.ascii_letter?
|
||||||
|
}.join("").downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(request : HTTP::Request)
|
||||||
|
unless (body = request.body).nil?
|
||||||
|
query = JSON.parse(body)
|
||||||
|
nombres = query["i"].as_s.split(",").map(&.strip)
|
||||||
|
nombres.reject! { |nombre| nombre.size == 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
if nombres.nil? || nombres.empty?
|
||||||
|
nombres = ["maria", "juan"]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove all diacritics and whatnot
|
||||||
|
nombres = nombres.map { |nombre|
|
||||||
|
normalize_name nombre
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "Processing #{nombres}"
|
||||||
|
|
||||||
|
buffer = IO::Memory.new
|
||||||
|
Ishi.new(buffer) do
|
||||||
|
canvas_size(800, 300)
|
||||||
|
show_key(true)
|
||||||
|
xrange(1922..2015)
|
||||||
|
nombres.map { |nombre|
|
||||||
|
sql = "SELECT anio::integer, contador::integer FROM nombres WHERE nombre = '#{nombre}' ORDER BY anio"
|
||||||
|
x = Array(Int32).new
|
||||||
|
y = Array(Int32).new
|
||||||
|
results = query(sql)
|
||||||
|
if results.nil? # No results, all 0s
|
||||||
|
x = (1922..2015).to_a
|
||||||
|
y = x.map { |_| 0 }
|
||||||
|
else # We got results
|
||||||
|
values = Hash(Int32, Int32).new(default_value: 0)
|
||||||
|
results.map { |row|
|
||||||
|
values[row[0]] = row[1]
|
||||||
|
}
|
||||||
|
(1922..2015).map { |year|
|
||||||
|
x << year
|
||||||
|
y << values[year] # Defaults to 0, so we have the whole set
|
||||||
|
}
|
||||||
|
end
|
||||||
|
plot(x, y, title: nombre.titleize, style: :lines, linewidth: 3)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
puts "After Ishi"
|
||||||
|
|
||||||
|
{
|
||||||
|
body: format_buffer(buffer, "historico"),
|
||||||
|
status_code: 200,
|
||||||
|
headers: HTTP::Headers{"Content-Type" => "text/html"},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
8
c-historico/shard.yml
Normal file
8
c-historico/shard.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name: historico
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
ishi:
|
||||||
|
github: toddsundsted/ishi
|
||||||
|
pg:
|
||||||
|
github: will/crystal-pg
|
29
deploy.sh
29
deploy.sh
@@ -1,28 +1,5 @@
|
|||||||
#!/bin/sh -x
|
#!/bin/sh -x
|
||||||
set -e
|
set -e
|
||||||
#export OPENFAAS_URL=http://pinky:8082
|
export OPENFAAS_URL=http://pinky:8082
|
||||||
#pass faas.ralsina.me | faas-cli login -u admin --password-stdin
|
pass faas.ralsina.me | faas-cli login -u admin --password-stdin
|
||||||
|
faas-cli deploy -f functions.yml $*
|
||||||
#pass iol-pass | faas-cli secret create iol-pass
|
|
||||||
#pass iol-user | faas-cli secret create iol-user
|
|
||||||
#pass iol-api-secret | faas-cli secret create iol-api-secret
|
|
||||||
#pass nombres-user | faas-cli secret create nombres-user
|
|
||||||
#pass nombres-pass | faas-cli secret create nombres-pass
|
|
||||||
#faas-cli deploy -f functions.yml $*
|
|
||||||
|
|
||||||
export FAASO_SERVER=http://rocky:8888/admin
|
|
||||||
pass faaso-rocky | faaso login
|
|
||||||
pass nombres-user | faaso secret -a historico user
|
|
||||||
pass nombres-pass | faaso secret -a historico pass
|
|
||||||
echo "192.168.0.98" | faaso secret -a historico dbhost
|
|
||||||
faaso build busqueda
|
|
||||||
faaso scale busqueda 0
|
|
||||||
faaso scale busqueda 1
|
|
||||||
pass nombres-user | faaso secret -a busqueda user
|
|
||||||
pass nombres-pass | faaso secret -a busqueda pass
|
|
||||||
echo "192.168.0.98" | faaso secret -a busqueda dbhost
|
|
||||||
faaso build historico
|
|
||||||
faaso scale historico 0
|
|
||||||
faaso scale historico 1
|
|
||||||
|
|
||||||
rsync -rav nombres.ralsina.me/* ralsina@pinky:/data/websites/nombres.ralsina.me/
|
|
||||||
|
@@ -3,10 +3,36 @@ provider:
|
|||||||
name: openfaas
|
name: openfaas
|
||||||
gateway: http://pinky:8082
|
gateway: http://pinky:8082
|
||||||
functions:
|
functions:
|
||||||
|
# busqueda:
|
||||||
|
# lang: python3-flask
|
||||||
|
# handler: ./busqueda
|
||||||
|
# image: ralsina/nombres_busqueda:latest
|
||||||
|
# historico:
|
||||||
|
# lang: python3-flask
|
||||||
|
# handler: ./historico
|
||||||
|
# image: ralsina/nombres_historico:latest
|
||||||
tapas:
|
tapas:
|
||||||
lang: python3-flask
|
lang: python3-flask
|
||||||
handler: ./tapas
|
handler: ./tapas
|
||||||
image: ralsina/tapas:latest
|
image: ralsina/tapas:latest
|
||||||
|
c-historico:
|
||||||
|
lang: crystal-http
|
||||||
|
handler: ./c-historico
|
||||||
|
image: ralsina/c-historico:latest
|
||||||
|
build_args:
|
||||||
|
ADDITIONAL_PACKAGE: gnuplot
|
||||||
|
secrets:
|
||||||
|
- nombres-pass
|
||||||
|
- nombres-user
|
||||||
|
c-busqueda:
|
||||||
|
lang: crystal-http
|
||||||
|
handler: ./c-busqueda
|
||||||
|
image: ralsina/c-busqueda:latest
|
||||||
|
build_args:
|
||||||
|
ADDITIONAL_PACKAGE: gnuplot
|
||||||
|
secrets:
|
||||||
|
- nombres-pass
|
||||||
|
- nombres-user
|
||||||
iol:
|
iol:
|
||||||
lang: python3-fastapi
|
lang: python3-fastapi
|
||||||
handler: ./iol
|
handler: ./iol
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
# Readme for Historico
|
|
||||||
|
|
||||||
This is a funko using the Crystal runtime for [FaaSO](https://git.ralsina.me/ralsina/faaso)
|
|
||||||
|
|
||||||
## What is Historico
|
|
||||||
|
|
||||||
Write here what it is
|
|
||||||
|
|
||||||
## How to use Historico
|
|
||||||
|
|
||||||
And so on.
|
|
0
historico/__init__.py
Normal file
0
historico/__init__.py
Normal file
@@ -1,66 +0,0 @@
|
|||||||
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
|
|
||||||
DBHOST = File.read("/secrets/dbhost").strip
|
|
||||||
|
|
||||||
DBURL = "postgres://#{USER}:#{PASS}@#{DBHOST}:5432/nombres"
|
|
||||||
puts "Connnecting to #{DBURL}"
|
|
||||||
|
|
||||||
# Create a connection pool to the database
|
|
||||||
pg = ConnectionPool.new(capacity: 5, timeout: 1.seconds) do
|
|
||||||
PG.connect(DBURL)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Connect to the database and get information about
|
|
||||||
# the requested names
|
|
||||||
get "/" do |env|
|
|
||||||
# Names are query parameters
|
|
||||||
# Split by commas, capitalize and take the first 5
|
|
||||||
names = env.params.query["names"]
|
|
||||||
.split(",").map(&.strip.capitalize)[..4]
|
|
||||||
|
|
||||||
# Prepare results table
|
|
||||||
results = [] of Array(String)
|
|
||||||
results << ["Año"] + names
|
|
||||||
(1922..2015).each do |anio|
|
|
||||||
results << [anio.to_s]
|
|
||||||
end
|
|
||||||
# Connect using credentials provided
|
|
||||||
pg.connection do |cursor|
|
|
||||||
# Get the information for each name
|
|
||||||
names.map do |name|
|
|
||||||
# Normalize: remove diacritics etc.
|
|
||||||
name = name.unicode_normalize(:nfkd)
|
|
||||||
.chars.reject! { |character|
|
|
||||||
!character.ascii_letter? && (character != ' ')
|
|
||||||
}.join("").downcase
|
|
||||||
|
|
||||||
counter_per_year = {} of Int32 => Int32
|
|
||||||
cursor.query("
|
|
||||||
SELECT anio::integer, contador::integer
|
|
||||||
FROM nombres WHERE nombre = $1", name) do |result_set|
|
|
||||||
result_set.each do
|
|
||||||
counter_per_year[result_set.read(Int32)] = result_set.read(Int32)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
(1922..2015).each do |anio|
|
|
||||||
results[anio - 1921] << counter_per_year.fetch(anio, 0).to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
results.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
# The `/ping/` endpoint is configured in the container as a healthcheck
|
|
||||||
# You can make it better by checking that your database is responding
|
|
||||||
# or whatever checks you think are important
|
|
||||||
#
|
|
||||||
get "/ping/" do
|
|
||||||
pg.connection.exec("SELECT 42")
|
|
||||||
"OK"
|
|
||||||
end
|
|
@@ -1,11 +0,0 @@
|
|||||||
name: historico
|
|
||||||
runtime: kemal
|
|
||||||
options:
|
|
||||||
shard_build_options: "--release -d --error-trace"
|
|
||||||
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 ."
|
|
66
historico/handler.py
Normal file
66
historico/handler.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import unicodedata
|
||||||
|
import urllib
|
||||||
|
from collections import defaultdict as ddict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from json import loads
|
||||||
|
|
||||||
|
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 handle(req):
|
||||||
|
"""handle a request to the function
|
||||||
|
Args:
|
||||||
|
req (str): request body
|
||||||
|
|
||||||
|
{"i": ["nombre1, nombre2"]}
|
||||||
|
|
||||||
|
"""
|
||||||
|
nombres = []
|
||||||
|
try:
|
||||||
|
nombres = loads(req)
|
||||||
|
nombres = nombres["i"].split(",")
|
||||||
|
nombres = [remove_accents(x.strip().lower()) for x in nombres]
|
||||||
|
nombres = [n for n in nombres if n]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not nombres:
|
||||||
|
nombres = ["maria", "juan"]
|
||||||
|
|
||||||
|
chart = pygal.Line(
|
||||||
|
height=200, fill=True, human_readable=True, show_minor_x_labels=False
|
||||||
|
)
|
||||||
|
chart.x_labels = [str(x) for x in range(1922, 2015)]
|
||||||
|
chart.x_labels_major = [str(x) if x % 10 == 0 else "" for x in range(1922, 2015)]
|
||||||
|
for nombre in nombres:
|
||||||
|
datos = ddict(int)
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
sql = """
|
||||||
|
SELECT anio, contador, nombre
|
||||||
|
FROM nombres
|
||||||
|
WHERE nombre = :nombre
|
||||||
|
ORDER BY anio
|
||||||
|
"""
|
||||||
|
cursor.execute(sql, {"nombre": nombre})
|
||||||
|
datos.update({r["anio"]: r["contador"] for r in cursor.fetchall()})
|
||||||
|
chart.add(nombre.title(), [datos[x] for x in range(1922, 2015)])
|
||||||
|
|
||||||
|
chart.x_labels = [str(n) for n in range(1922, 2015)]
|
||||||
|
chart.x_labels_major = [str(n) for n in range(1920, 2020, 10)]
|
||||||
|
|
||||||
|
# return Response(chart.render(is_unicode=True), mimetype="image/svg+xml")
|
||||||
|
return chart.render(is_unicode=True), 200, {"Content-Type": "image/svg+xml"}
|
@@ -1,109 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-theme="dark">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="https://www.gstatic.com/charts/loader.js"
|
|
||||||
></script>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap"
|
|
||||||
rel="stylesheet">
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
|
|
||||||
/>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Kode+Mono&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<script type="text/javascript">
|
|
||||||
google.charts.load("current", { packages: ["corechart"] });
|
|
||||||
google.charts.setOnLoadCallback(drawChart);
|
|
||||||
|
|
||||||
async function drawChart() {
|
|
||||||
fetch(
|
|
||||||
"/faaso/historico/?" +
|
|
||||||
new URLSearchParams({
|
|
||||||
names: document.getElementById("nombres").value,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
var data = [json[0]];
|
|
||||||
data.push(
|
|
||||||
...json
|
|
||||||
.slice(1)
|
|
||||||
.map((item) => item.map((value) => parseInt(value)))
|
|
||||||
);
|
|
||||||
data = google.visualization.arrayToDataTable(data);
|
|
||||||
var options = {
|
|
||||||
title: "",
|
|
||||||
animation: {
|
|
||||||
startup: true,
|
|
||||||
duration: 1000,
|
|
||||||
easing: "out",
|
|
||||||
},
|
|
||||||
backgroundColor: "#1c212c",
|
|
||||||
vAxis: {
|
|
||||||
minValue: 0,
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
hAxis: {
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
legend: { position: "bottom", textStyle: { color: "#aaa" } },
|
|
||||||
};
|
|
||||||
|
|
||||||
var chart = new google.visualization.LineChart(
|
|
||||||
document.getElementById("chart")
|
|
||||||
);
|
|
||||||
|
|
||||||
chart.draw(data, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
html * {
|
|
||||||
font-family: 'Quicksand', sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main class="container" style="text-align: center">
|
|
||||||
<header>
|
|
||||||
<h1>Popularidad de Nombres en Argentina</h1>
|
|
||||||
</header>
|
|
||||||
<div id="chart" style="width: 80vw; height: 50vh; margin: auto"></div>
|
|
||||||
<form
|
|
||||||
role="search"
|
|
||||||
onSubmit="return false;"
|
|
||||||
style="margin: auto; margin-top: 2em; width: 80%"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
name="nombres"
|
|
||||||
id="nombres"
|
|
||||||
placeholder="Nombres separados con comas"
|
|
||||||
aria-label="Search"
|
|
||||||
/>
|
|
||||||
<input type="submit" value="Buscar" onCLick="drawChart();" />
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
3
historico/requirements.txt
Normal file
3
historico/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pygal
|
||||||
|
requests
|
||||||
|
pyrqlite
|
@@ -1,14 +0,0 @@
|
|||||||
name: historico
|
|
||||||
version: 0.1.0
|
|
||||||
|
|
||||||
targets:
|
|
||||||
funko:
|
|
||||||
main: main.cr
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
kemal:
|
|
||||||
github: kemalcr/kemal
|
|
||||||
pg:
|
|
||||||
github: will/crystal-pg
|
|
||||||
pool:
|
|
||||||
github: ysbaddaden/pool
|
|
@@ -1,339 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-theme="dark">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="https://www.gstatic.com/charts/loader.js"
|
|
||||||
></script>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
|
|
||||||
/>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Kode+Mono&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<script type="text/javascript">
|
|
||||||
google.charts.load("current", { packages: ["corechart"] });
|
|
||||||
google.charts.setOnLoadCallback(drawChart);
|
|
||||||
|
|
||||||
async function drawChart() {
|
|
||||||
drawChart1();
|
|
||||||
drawChart2();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function drawChart1() {
|
|
||||||
fetch("https://faaso-prod.ralsina.me/faaso/busqueda/", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
p: document.getElementById("p").value,
|
|
||||||
g: document.getElementById("g").value,
|
|
||||||
a: document.getElementById("a").value,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
title = json["title"];
|
|
||||||
data = json["data"];
|
|
||||||
console.log(data);
|
|
||||||
data = google.visualization.arrayToDataTable(data);
|
|
||||||
var options = {
|
|
||||||
title: title,
|
|
||||||
titleTextStyle: {
|
|
||||||
color: "#aaa",
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
startup: true,
|
|
||||||
duration: 1000,
|
|
||||||
easing: "out",
|
|
||||||
},
|
|
||||||
backgroundColor: "#1c212c",
|
|
||||||
vAxis: {
|
|
||||||
minValue: 0,
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
hAxis: {
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
legend: { position: 'none'},
|
|
||||||
};
|
|
||||||
|
|
||||||
var chart1 = new google.visualization.BarChart(
|
|
||||||
document.getElementById("chart1")
|
|
||||||
);
|
|
||||||
|
|
||||||
chart1.draw(data, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async function drawChart2() {
|
|
||||||
fetch(
|
|
||||||
"https://faaso-prod.ralsina.me/faaso/historico/?" +
|
|
||||||
new URLSearchParams({
|
|
||||||
names: document.getElementById("nombres").value,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
var data = [json[0]];
|
|
||||||
data.push(
|
|
||||||
...json
|
|
||||||
.slice(1)
|
|
||||||
.map((item) => item.map((value) => parseInt(value)))
|
|
||||||
);
|
|
||||||
data = google.visualization.arrayToDataTable(data);
|
|
||||||
var options = {
|
|
||||||
title: "",
|
|
||||||
animation: {
|
|
||||||
startup: true,
|
|
||||||
duration: 1000,
|
|
||||||
easing: "out",
|
|
||||||
},
|
|
||||||
backgroundColor: "#1c212c",
|
|
||||||
vAxis: {
|
|
||||||
minValue: 0,
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
hAxis: {
|
|
||||||
gridlines: { color: "#666" },
|
|
||||||
minorGridlines: { color: "#1c212c" },
|
|
||||||
textStyle: { color: "#aaa" }
|
|
||||||
},
|
|
||||||
legend: { position: "bottom", textStyle: { color: "#aaa" } },
|
|
||||||
};
|
|
||||||
|
|
||||||
var chart2 = new google.visualization.LineChart(
|
|
||||||
document.getElementById("chart2")
|
|
||||||
);
|
|
||||||
|
|
||||||
chart2.draw(data, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
html * {
|
|
||||||
font-family: "Quicksand", sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main class="container" style="text-align: center">
|
|
||||||
<header>
|
|
||||||
<h1>Cosas con Datos de Nombres de Argentina</h1>
|
|
||||||
<h2>Adivinar nombres por edad y género</h2>
|
|
||||||
</header>
|
|
||||||
<div id="chart1" style="width: 80vw; height: 50vh; margin: auto"></div>
|
|
||||||
<form
|
|
||||||
onSubmit="return false;"
|
|
||||||
style="margin: auto; margin-top: 2em; width: 80%"
|
|
||||||
>
|
|
||||||
<fieldset class="grid">
|
|
||||||
<input type="text" name="p" id="p" placeholder="Como empieza tu nombre?" />
|
|
||||||
<select name="a" id="a">
|
|
||||||
<option selected value="">Año?</option>
|
|
||||||
<option value="1922">1922</option>
|
|
||||||
<option value="1923">1923</option>
|
|
||||||
<option value="1924">1924</option>
|
|
||||||
<option value="1925">1925</option>
|
|
||||||
<option value="1926">1926</option>
|
|
||||||
<option value="1927">1927</option>
|
|
||||||
<option value="1928">1928</option>
|
|
||||||
<option value="1929">1929</option>
|
|
||||||
<option value="1930">1930</option>
|
|
||||||
<option value="1931">1931</option>
|
|
||||||
<option value="1932">1932</option>
|
|
||||||
<option value="1933">1933</option>
|
|
||||||
<option value="1934">1934</option>
|
|
||||||
<option value="1935">1935</option>
|
|
||||||
<option value="1936">1936</option>
|
|
||||||
<option value="1937">1937</option>
|
|
||||||
<option value="1938">1938</option>
|
|
||||||
<option value="1939">1939</option>
|
|
||||||
<option value="1940">1940</option>
|
|
||||||
<option value="1941">1941</option>
|
|
||||||
<option value="1942">1942</option>
|
|
||||||
<option value="1943">1943</option>
|
|
||||||
<option value="1944">1944</option>
|
|
||||||
<option value="1945">1945</option>
|
|
||||||
<option value="1946">1946</option>
|
|
||||||
<option value="1947">1947</option>
|
|
||||||
<option value="1948">1948</option>
|
|
||||||
<option value="1949">1949</option>
|
|
||||||
<option value="1950">1950</option>
|
|
||||||
<option value="1951">1951</option>
|
|
||||||
<option value="1952">1952</option>
|
|
||||||
<option value="1953">1953</option>
|
|
||||||
<option value="1954">1954</option>
|
|
||||||
<option value="1955">1955</option>
|
|
||||||
<option value="1956">1956</option>
|
|
||||||
<option value="1957">1957</option>
|
|
||||||
<option value="1958">1958</option>
|
|
||||||
<option value="1959">1959</option>
|
|
||||||
<option value="1960">1960</option>
|
|
||||||
<option value="1961">1961</option>
|
|
||||||
<option value="1962">1962</option>
|
|
||||||
<option value="1963">1963</option>
|
|
||||||
<option value="1964">1964</option>
|
|
||||||
<option value="1965">1965</option>
|
|
||||||
<option value="1966">1966</option>
|
|
||||||
<option value="1967">1967</option>
|
|
||||||
<option value="1968">1968</option>
|
|
||||||
<option value="1969">1969</option>
|
|
||||||
<option value="1970">1970</option>
|
|
||||||
<option value="1971">1971</option>
|
|
||||||
<option value="1972">1972</option>
|
|
||||||
<option value="1973">1973</option>
|
|
||||||
<option value="1974">1974</option>
|
|
||||||
<option value="1975">1975</option>
|
|
||||||
<option value="1976">1976</option>
|
|
||||||
<option value="1977">1977</option>
|
|
||||||
<option value="1978">1978</option>
|
|
||||||
<option value="1979">1979</option>
|
|
||||||
<option value="1980">1980</option>
|
|
||||||
<option value="1981">1981</option>
|
|
||||||
<option value="1982">1982</option>
|
|
||||||
<option value="1983">1983</option>
|
|
||||||
<option value="1984">1984</option>
|
|
||||||
<option value="1985">1985</option>
|
|
||||||
<option value="1986">1986</option>
|
|
||||||
<option value="1987">1987</option>
|
|
||||||
<option value="1988">1988</option>
|
|
||||||
<option value="1989">1989</option>
|
|
||||||
<option value="1990">1990</option>
|
|
||||||
<option value="1991">1991</option>
|
|
||||||
<option value="1992">1992</option>
|
|
||||||
<option value="1993">1993</option>
|
|
||||||
<option value="1994">1994</option>
|
|
||||||
<option value="1995">1995</option>
|
|
||||||
<option value="1996">1996</option>
|
|
||||||
<option value="1997">1997</option>
|
|
||||||
<option value="1998">1998</option>
|
|
||||||
<option value="1999">1999</option>
|
|
||||||
<option value="2000">2000</option>
|
|
||||||
<option value="2001">2001</option>
|
|
||||||
<option value="2002">2002</option>
|
|
||||||
<option value="2003">2003</option>
|
|
||||||
<option value="2004">2004</option>
|
|
||||||
<option value="2005">2005</option>
|
|
||||||
<option value="1966">1966</option>
|
|
||||||
<option value="1967">1967</option>
|
|
||||||
<option value="1968">1968</option>
|
|
||||||
<option value="1969">1969</option>
|
|
||||||
<option value="1970">1970</option>
|
|
||||||
<option value="1971">1971</option>
|
|
||||||
<option value="1972">1972</option>
|
|
||||||
<option value="1973">1973</option>
|
|
||||||
<option value="1974">1974</option>
|
|
||||||
<option value="1975">1975</option>
|
|
||||||
<option value="1976">1976</option>
|
|
||||||
<option value="1977">1977</option>
|
|
||||||
<option value="1978">1978</option>
|
|
||||||
<option value="1979">1979</option>
|
|
||||||
<option value="1980">1980</option>
|
|
||||||
<option value="1981">1981</option>
|
|
||||||
<option value="1982">1982</option>
|
|
||||||
<option value="1983">1983</option>
|
|
||||||
<option value="1984">1984</option>
|
|
||||||
<option value="1985">1985</option>
|
|
||||||
<option value="1986">1986</option>
|
|
||||||
<option value="1987">1987</option>
|
|
||||||
<option value="1988">1988</option>
|
|
||||||
<option value="1989">1989</option>
|
|
||||||
<option value="1990">1990</option>
|
|
||||||
<option value="1991">1991</option>
|
|
||||||
<option value="1992">1992</option>
|
|
||||||
<option value="1993">1993</option>
|
|
||||||
<option value="1994">1994</option>
|
|
||||||
<option value="1995">1995</option>
|
|
||||||
<option value="1996">1996</option>
|
|
||||||
<option value="1997">1997</option>
|
|
||||||
<option value="1998">1998</option>
|
|
||||||
<option value="1999">1999</option>
|
|
||||||
<option value="2000">2000</option>
|
|
||||||
<option value="2001">2001</option>
|
|
||||||
<option value="2002">2002</option>
|
|
||||||
<option value="2003">2003</option>
|
|
||||||
<option value="2004">2004</option>
|
|
||||||
<option value="2005">2005</option>
|
|
||||||
</select>
|
|
||||||
<select name="g" id="g" />
|
|
||||||
<option selected value="">Género?</option>
|
|
||||||
<option value="f">Femenino</option>
|
|
||||||
<option value="m">Masculino</option>
|
|
||||||
</select>
|
|
||||||
<input type="submit" value="Buscar" onCLick="drawChart1();" />
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
<main class="container" style="text-align: center">
|
|
||||||
<header>
|
|
||||||
<h2>Popularidad de Nombres en Argentina</h2>
|
|
||||||
</header>
|
|
||||||
<div id="chart2" style="width: 80vw; height: 50vh; margin: auto"></div>
|
|
||||||
<form
|
|
||||||
role="search"
|
|
||||||
onSubmit="return false;"
|
|
||||||
style="margin: auto; margin-top: 2em; width: 80%"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
name="nombres"
|
|
||||||
id="nombres"
|
|
||||||
placeholder="Nombres separados con comas"
|
|
||||||
aria-label="Search"
|
|
||||||
/>
|
|
||||||
<input type="submit" value="Buscar" onCLick="drawChart2();" />
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<main class="container">
|
|
||||||
<header>
|
|
||||||
<h2>Cosas Nerds</h2>
|
|
||||||
</header>
|
|
||||||
<ul>
|
|
||||||
<li>Los datos utilizados son provistos por el RENAPER (Registro Nacional de las Personas), y sólo son útiles para Argentina: <a href="http://www.datos.gob.ar/dataset/otros-nombres-personas-fisicas">Los Datos</a>
|
|
||||||
<li>Le saqué todos los acentos a la data. Después de todo, si te llamás María y te digo Maria no te vas a ofender.
|
|
||||||
<li>Había una página del RENAPER que hacía algo parecido pero era una porquería. Ahora hay otra que ni miré.
|
|
||||||
<li>La data no es perfecta. En el caso de algunos nombres muy peculiares están duplicados. Por ejemplo, mi cuñado se llama "Pedro Fuat", y hay dos registros en el mismo año. Sospecho que es un duplicado de él mismo. Nota de color: los tres hijos de mi suegra tienen nombres que nadie más tiene en la historia de la Argentina.
|
|
||||||
<li>Los datos están publicados de manera conveniente <a href="https://www.dolthub.com/repositories/ralsina/nombres_argentina_1922_2005/doc/main">en dolthub.</a>
|
|
||||||
<li>La detección de género está hecha en base a datos del INE de España (lista de los nombres de personas nacidas en España con más de 20 ocurrencias)
|
|
||||||
<li>Si alguien tiene data similar de otros países me encantaría hacer páginas similares.
|
|
||||||
<li>Si les interesa es muy fácil hacer un Jupyter Notebook para jugar con estos datos.
|
|
||||||
<li>Código y cosas: https://github.com/ralsina/nombres
|
|
||||||
</ul>
|
|
||||||
La aplicación consiste de:
|
|
||||||
<ul>
|
|
||||||
<li>Una página estática (ésta!)
|
|
||||||
<li>Los charts SVG están hechos con Google Charts
|
|
||||||
<li>El código de backend está hecho en Crystal
|
|
||||||
<li>Todo está ejecutándose en una computadora categoría RaspBerry Pi usando <a href="faaso.ralsina.me">faaso</a> con una base de datos Postgres
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user