ruslanmv's picture
updates
02ddb79
from __future__ import annotations
import os
import logging
import pandas as pd
from flask import Flask, jsonify, request, render_template_string
from unidecode import unidecode
from typing import Dict, List, Optional
# --- Data layer ---
CSV_PATH = "equiparazioni.csv"
if os.path.exists(CSV_PATH):
MAP = pd.read_csv(CSV_PATH)
else:
MAP = pd.DataFrame([
{"col_1_dl": "Ingegneria informatica", "col_2_ls": "35/S – Ingegneria informatica", "col_3_lm": "LM-32 – Ingegneria informatica"},
{"col_1_dl": "Scienze dell’economia", "col_2_ls": "64/S – Scienze dell’economia", "col_3_lm": "LM-56 – Scienze dell’economia"},
])
# --- Logic layer ---
def _norm(txt: str | None) -> str:
if txt is None or pd.isna(txt):
return ""
return "".join(c for c in unidecode(str(txt)).lower() if c.isalnum())
def find_triplet(title: str) -> Optional[Dict[str, str]]:
key = _norm(title)
if not key:
return None
for _, row in MAP.iterrows():
if key in {_norm(row.col_1_dl), _norm(row.col_2_ls), _norm(row.col_3_lm)}:
return {k: v if pd.notna(v) else "" for k, v in row.to_dict().items()}
return None
def satisfies_once(candidate_title: str, required_title: str) -> bool:
cand_triplet = find_triplet(candidate_title)
req_triplet = find_triplet(required_title)
if cand_triplet is None or req_triplet is None:
return False
return cand_triplet == req_triplet
def satisfies(candidate_title: str, required_titles: List[str]) -> bool:
if not required_titles:
return True
return any(satisfies_once(candidate_title, r) for r in required_titles)
# --- Web layer ---
app = Flask(__name__)
@app.route("/titles")
def all_titles():
titles = (
MAP["col_1_dl"].dropna().tolist() +
MAP["col_2_ls"].dropna().tolist() +
MAP["col_3_lm"].dropna().tolist()
)
return jsonify(sorted(set(titles)))
@app.route("/check", methods=["POST"])
def check():
data = request.get_json(force=True)
candidate = data.get("candidate", "")
required = data.get("required", [])
approved = satisfies(candidate, required)
mapping = find_triplet(candidate) or {}
return jsonify({"approved": approved, "mapping": mapping})
HTML_PAGE = """
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Validatore Titoli – HR</title>
<style>
:root { --main-bg: #fff; --text-color: #333; --border-color: #ccc; --badge-bg: #eef; --green: #4CAF50; --red: #F44336; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; margin:2em; background:var(--main-bg); color:var(--text-color); line-height:1.6; }
h1,h3 { color:#0056b3; }
.grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(250px,1fr)); gap:24px; }
select,button { width:100%; padding:10px; border-radius:5px; border:1px solid var(--border-color); font-size:1rem; }
select { height:250px; }
button { background:#007bff; color:#fff; cursor:pointer; border:none; margin-top:8px; transition:background .2s; }
button:hover { background:#0056b3; }
#requiredList { min-height:100px; border:1px solid var(--border-color); padding:8px; overflow-y:auto; border-radius:5px; background:#f9f9f9; }
.badge { display:inline-flex; align-items:center; padding:5px 10px; margin:4px; background:var(--badge-bg); border-radius:15px; font-size:.9em; cursor:pointer; }
.badge:hover::after { content:' ❌'; color:var(--red); }
.result { padding:15px; text-align:center; font-weight:bold; color:#fff; border-radius:5px; transition:background .3s; }
.approved { background:var(--green); }
.rejected { background:var(--red); }
#mapping { font-size:.9em; border:1px solid #ddd; padding:12px; border-radius:5px; background:#f9f9f9; }
#mapping strong { color:#0056b3; }
</style>
</head>
<body>
<h1>Validatore Titoli di Studio</h1>
<div class="grid">
<div>
<h3>1. Titolo del Candidato</h3>
<select id="candidateSelect" size="15"></select>
</div>
<div>
<h3>2. Requisiti del Bando</h3>
<select id="requiredSelect" size="10"></select>
<button id="addBtn">Aggiungi Requisito ➜</button>
</div>
<div>
<h3>Titoli Richiesti (OR)</h3>
<div id="requiredList"></div>
<h3 style="margin-top:20px;">Mapping del Candidato</h3>
<div id="mapping">(selezionare un candidato)</div>
</div>
<div>
<h3>4. Esito</h3>
<div id="resultBox" class="result">...</div>
</div>
</div>
<script>
let allTitles = [], requiredTitles = new Set();
const candidateSelect = document.getElementById('candidateSelect'),
requiredSelect = document.getElementById('requiredSelect'),
addBtn = document.getElementById('addBtn'),
requiredListDiv = document.getElementById('requiredList'),
resultBox = document.getElementById('resultBox'),
mappingDiv = document.getElementById('mapping');
async function populateTitles() {
const r = await fetch('/titles');
allTitles = await r.json();
const opts = allTitles.map(t => `<option value="${t}">${t}</option>`).join('');
candidateSelect.innerHTML = opts;
requiredSelect.innerHTML = opts;
candidateSelect.onchange = runCheck;
addBtn.onclick = () => {
const val = requiredSelect.value;
if (val && !requiredTitles.has(val)) {
requiredTitles.add(val);
renderRequiredList();
runCheck();
}
};
}
function renderRequiredList() {
requiredListDiv.innerHTML = '';
requiredTitles.forEach(title => {
const span = document.createElement('span');
span.textContent = title;
span.className = 'badge';
span.onclick = () => {
requiredTitles.delete(title);
renderRequiredList();
runCheck();
};
requiredListDiv.appendChild(span);
});
}
async function runCheck() {
const cand = candidateSelect.value;
if (!cand) return;
const res = await (await fetch('/check', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ candidate: cand, required: Array.from(requiredTitles) })
})).json();
if (res.mapping && Object.keys(res.mapping).length) {
mappingDiv.innerHTML =
`<strong>Vecchio Ord. (DL):</strong> ${res.mapping.col_1_dl || 'N/A'}<br>` +
`<strong>Specialistica (LS):</strong> ${res.mapping.col_2_ls || 'N/A'}<br>` +
`<strong>Magistrale (LM):</strong> ${res.mapping.col_3_lm || 'N/A'}`;
} else {
mappingDiv.textContent = 'Titolo non riconosciuto.';
}
if (!requiredTitles.size) {
resultBox.className = 'result';
resultBox.textContent = 'Aggiungere requisiti';
} else {
resultBox.className = res.approved ? 'result approved' : 'result rejected';
resultBox.textContent = res.approved ? 'APPROVATO' : 'NON APPROVATO';
}
}
populateTitles();
</script>
</body>
</html>
"""
@app.route("/")
def index():
return render_template_string(HTML_PAGE)
# --- App Runner ---
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)