Add Data Quality Contract cockpit
This commit is contained in:
parent
94fa0d7026
commit
cc951b713d
@ -397,3 +397,238 @@ def orders_html(request: Request, limit: int = Query(50, ge=1, le=200)):
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/data-quality-contract")
|
||||
def api_data_quality_contract(limit: int = Query(100, ge=1, le=500)):
|
||||
summary = fetch_one(
|
||||
"""
|
||||
SELECT
|
||||
count(*) AS total_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'CONTRATO_CUMPLIDO') AS contract_ok_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'VALIDO_CON_OBSERVACIONES') AS contract_warn_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'BLOQUEADO_CRITICO') AS blocked_critical_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'BLOQUEADO_ALTO') AS blocked_high_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'BLOQUEADO_MEDIO') AS blocked_medium_orders,
|
||||
round(avg(contract_score), 2) AS avg_contract_score,
|
||||
sum(critical_failures) AS total_critical_failures,
|
||||
sum(high_failures) AS total_high_failures,
|
||||
sum(medium_failures) AS total_medium_failures,
|
||||
sum(total_issue_score) AS total_issue_score
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_order_summary_v1
|
||||
""",
|
||||
(),
|
||||
)
|
||||
|
||||
orders = fetch_all(
|
||||
"""
|
||||
SELECT
|
||||
odoo_order_ref,
|
||||
odoo_state,
|
||||
sale_order_ref,
|
||||
customer_name,
|
||||
product_default_code,
|
||||
product_name,
|
||||
machine_id,
|
||||
contract_score,
|
||||
contract_status,
|
||||
failed_rules,
|
||||
warn_rules,
|
||||
critical_failures,
|
||||
high_failures,
|
||||
medium_failures,
|
||||
main_blocker_rule_code,
|
||||
main_blocker_rule_name,
|
||||
main_blocker_domain,
|
||||
main_blocker_severity,
|
||||
owner_area_to_act,
|
||||
main_blocker_finding,
|
||||
main_recommended_action,
|
||||
contract_summary
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_order_summary_v1
|
||||
ORDER BY contract_score ASC, total_issue_score DESC, odoo_order_ref DESC
|
||||
LIMIT %s
|
||||
""",
|
||||
(limit,),
|
||||
)
|
||||
|
||||
owners = fetch_all(
|
||||
"""
|
||||
SELECT
|
||||
owner_area,
|
||||
failed_checks,
|
||||
warn_checks,
|
||||
critical_failed_checks,
|
||||
high_failed_checks,
|
||||
affected_orders,
|
||||
total_issue_score,
|
||||
affected_rules,
|
||||
affected_order_refs
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_owner_summary_v1
|
||||
WHERE failed_checks > 0
|
||||
OR warn_checks > 0
|
||||
ORDER BY total_issue_score DESC NULLS LAST, owner_area
|
||||
""",
|
||||
(),
|
||||
)
|
||||
|
||||
rules = fetch_all(
|
||||
"""
|
||||
SELECT
|
||||
rule_code,
|
||||
rule_name,
|
||||
domain,
|
||||
severity,
|
||||
owner_area,
|
||||
check_status,
|
||||
count(*) AS order_count,
|
||||
sum(issue_score) AS total_issue_score,
|
||||
string_agg(
|
||||
DISTINCT odoo_order_ref,
|
||||
', '
|
||||
ORDER BY odoo_order_ref
|
||||
) AS affected_orders
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_order_checks_v1
|
||||
WHERE check_status IN ('FAIL', 'WARN')
|
||||
GROUP BY
|
||||
rule_code,
|
||||
rule_name,
|
||||
domain,
|
||||
severity,
|
||||
owner_area,
|
||||
check_status
|
||||
ORDER BY
|
||||
total_issue_score DESC NULLS LAST,
|
||||
rule_code,
|
||||
check_status
|
||||
""",
|
||||
(),
|
||||
)
|
||||
|
||||
return jsonable_encoder(
|
||||
{
|
||||
"summary": summary or {},
|
||||
"orders": orders,
|
||||
"owners": owners,
|
||||
"rules": rules,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.get("/data-quality-contract.html", response_class=HTMLResponse)
|
||||
def data_quality_contract_html(request: Request, limit: int = Query(100, ge=1, le=500)):
|
||||
summary = fetch_one(
|
||||
"""
|
||||
SELECT
|
||||
count(*) AS total_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'CONTRATO_CUMPLIDO') AS contract_ok_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'VALIDO_CON_OBSERVACIONES') AS contract_warn_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'BLOQUEADO_CRITICO') AS blocked_critical_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'BLOQUEADO_ALTO') AS blocked_high_orders,
|
||||
count(*) FILTER (WHERE contract_status = 'BLOQUEADO_MEDIO') AS blocked_medium_orders,
|
||||
round(avg(contract_score), 2) AS avg_contract_score,
|
||||
sum(critical_failures) AS total_critical_failures,
|
||||
sum(high_failures) AS total_high_failures,
|
||||
sum(medium_failures) AS total_medium_failures,
|
||||
sum(total_issue_score) AS total_issue_score
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_order_summary_v1
|
||||
""",
|
||||
(),
|
||||
)
|
||||
|
||||
orders = fetch_all(
|
||||
"""
|
||||
SELECT
|
||||
odoo_order_ref,
|
||||
odoo_state,
|
||||
sale_order_ref,
|
||||
customer_name,
|
||||
product_default_code,
|
||||
product_name,
|
||||
machine_id,
|
||||
contract_score,
|
||||
contract_status,
|
||||
failed_rules,
|
||||
warn_rules,
|
||||
critical_failures,
|
||||
high_failures,
|
||||
medium_failures,
|
||||
main_blocker_rule_code,
|
||||
main_blocker_rule_name,
|
||||
main_blocker_domain,
|
||||
main_blocker_severity,
|
||||
owner_area_to_act,
|
||||
main_blocker_finding,
|
||||
main_recommended_action,
|
||||
contract_summary
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_order_summary_v1
|
||||
ORDER BY contract_score ASC, total_issue_score DESC, odoo_order_ref DESC
|
||||
LIMIT %s
|
||||
""",
|
||||
(limit,),
|
||||
)
|
||||
|
||||
owners = fetch_all(
|
||||
"""
|
||||
SELECT
|
||||
owner_area,
|
||||
failed_checks,
|
||||
warn_checks,
|
||||
critical_failed_checks,
|
||||
high_failed_checks,
|
||||
affected_orders,
|
||||
total_issue_score,
|
||||
affected_rules,
|
||||
affected_order_refs
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_owner_summary_v1
|
||||
WHERE failed_checks > 0
|
||||
OR warn_checks > 0
|
||||
ORDER BY total_issue_score DESC NULLS LAST, owner_area
|
||||
""",
|
||||
(),
|
||||
)
|
||||
|
||||
rules = fetch_all(
|
||||
"""
|
||||
SELECT
|
||||
rule_code,
|
||||
rule_name,
|
||||
domain,
|
||||
severity,
|
||||
owner_area,
|
||||
check_status,
|
||||
count(*) AS order_count,
|
||||
sum(issue_score) AS total_issue_score,
|
||||
string_agg(
|
||||
DISTINCT odoo_order_ref,
|
||||
', '
|
||||
ORDER BY odoo_order_ref
|
||||
) AS affected_orders
|
||||
FROM mv_reports_ucepsa_demo.data_quality_contract_order_checks_v1
|
||||
WHERE check_status IN ('FAIL', 'WARN')
|
||||
GROUP BY
|
||||
rule_code,
|
||||
rule_name,
|
||||
domain,
|
||||
severity,
|
||||
owner_area,
|
||||
check_status
|
||||
ORDER BY
|
||||
total_issue_score DESC NULLS LAST,
|
||||
rule_code,
|
||||
check_status
|
||||
""",
|
||||
(),
|
||||
)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"data_quality_contract.html",
|
||||
{
|
||||
"request": request,
|
||||
"title": REPORT_TITLE,
|
||||
"summary": summary or {},
|
||||
"orders": orders,
|
||||
"owners": owners,
|
||||
"rules": rules,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -111,6 +111,9 @@
|
||||
|
||||
<h1>Cost Readiness Cockpit</h1>
|
||||
<p class="muted">{{ title }}</p>
|
||||
<p>
|
||||
<a href="/data-quality-contract.html" target="_blank">Abrir Data Quality Contract</a>
|
||||
</p>
|
||||
<p>
|
||||
Esta pantalla muestra qué órdenes están preparadas para cálculo de margen y qué datos lo bloquean.
|
||||
No calcula margen real si falta coste de materia prima.
|
||||
|
||||
@ -0,0 +1,296 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ title }} - Data Quality Contract</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 28px;
|
||||
color: #1f2933;
|
||||
background: #f7f9fb;
|
||||
font-size: 13px;
|
||||
}
|
||||
.page {
|
||||
background: white;
|
||||
padding: 28px;
|
||||
border-radius: 12px;
|
||||
max-width: 1700px;
|
||||
margin: auto;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 4px 0;
|
||||
color: #0f172a;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 28px;
|
||||
color: #0f172a;
|
||||
border-bottom: 1px solid #cbd5e1;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
.muted {
|
||||
color: #64748b;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
background: #ffffff;
|
||||
}
|
||||
.label {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.value {
|
||||
color: #0f172a;
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
th {
|
||||
background: #f1f5f9;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #cbd5e1;
|
||||
}
|
||||
td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.ok {
|
||||
color: #166534;
|
||||
font-weight: bold;
|
||||
}
|
||||
.warn {
|
||||
color: #92400e;
|
||||
font-weight: bold;
|
||||
}
|
||||
.bad {
|
||||
color: #991b1b;
|
||||
font-weight: bold;
|
||||
}
|
||||
.small {
|
||||
font-size: 11px;
|
||||
}
|
||||
a {
|
||||
color: #0f766e;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 3px 7px;
|
||||
border-radius: 999px;
|
||||
background: #e2e8f0;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<h1>Data Quality Contract Odoo–MESAVAULT</h1>
|
||||
<p class="muted">{{ title }}</p>
|
||||
<p>
|
||||
Esta pantalla resume si las órdenes cumplen el contrato mínimo de datos para informes técnicos,
|
||||
margen industrial, pérdidas y readiness económico.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/orders.html" target="_blank">Órdenes</a>
|
||||
|
|
||||
<a href="/cost-readiness.html" target="_blank">Cost Readiness</a>
|
||||
</p>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="label">Órdenes</div>
|
||||
<div class="value">{{ summary.total_orders or 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Contrato cumplido</div>
|
||||
<div class="value">{{ summary.contract_ok_orders or 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Con observaciones</div>
|
||||
<div class="value">{{ summary.contract_warn_orders or 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Bloqueo crítico</div>
|
||||
<div class="value">{{ summary.blocked_critical_orders or 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Fallos críticos</div>
|
||||
<div class="value">{{ summary.total_critical_failures or 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Score medio</div>
|
||||
<div class="value">{{ summary.avg_contract_score|num(2) if summary.avg_contract_score is not none else "0.00" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>1. Estado del contrato por orden</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Orden</th>
|
||||
<th>Pedido venta</th>
|
||||
<th>Cliente</th>
|
||||
<th>Producto</th>
|
||||
<th>Máquina</th>
|
||||
<th>Score</th>
|
||||
<th>Estado contrato</th>
|
||||
<th>Fallos</th>
|
||||
<th>Bloqueo principal</th>
|
||||
<th>Área responsable</th>
|
||||
<th>Acción recomendada</th>
|
||||
<th>Informe</th>
|
||||
</tr>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td>{{ order.odoo_order_ref }}</td>
|
||||
<td>{{ order.sale_order_ref or "" }}</td>
|
||||
<td>{{ order.customer_name or "" }}</td>
|
||||
<td>
|
||||
<strong>{{ order.product_default_code or "" }}</strong><br>
|
||||
<span class="small">{{ order.product_name or "" }}</span>
|
||||
</td>
|
||||
<td>{{ order.machine_id or "" }}</td>
|
||||
<td>
|
||||
{% if order.contract_score >= 80 %}
|
||||
<span class="ok">{{ order.contract_score }}</span>
|
||||
{% elif order.contract_score >= 50 %}
|
||||
<span class="warn">{{ order.contract_score }}</span>
|
||||
{% else %}
|
||||
<span class="bad">{{ order.contract_score }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if order.contract_status == 'CONTRATO_CUMPLIDO' %}
|
||||
<span class="ok">Contrato cumplido</span>
|
||||
{% elif order.contract_status == 'VALIDO_CON_OBSERVACIONES' %}
|
||||
<span class="warn">Válido con observaciones</span>
|
||||
{% elif order.contract_status == 'BLOQUEADO_CRITICO' %}
|
||||
<span class="bad">Bloqueado crítico</span>
|
||||
{% elif order.contract_status == 'BLOQUEADO_ALTO' %}
|
||||
<span class="bad">Bloqueado alto</span>
|
||||
{% elif order.contract_status == 'BLOQUEADO_MEDIO' %}
|
||||
<span class="warn">Bloqueado medio</span>
|
||||
{% else %}
|
||||
<span class="pill">{{ order.contract_status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
Críticos: {{ order.critical_failures }}<br>
|
||||
Altos: {{ order.high_failures }}<br>
|
||||
Medios: {{ order.medium_failures }}<br>
|
||||
Avisos: {{ order.warn_rules }}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ order.main_blocker_rule_code or "" }}</strong><br>
|
||||
<span class="small">{{ order.main_blocker_rule_name or "" }}</span><br>
|
||||
<span class="small">{{ order.main_blocker_finding or "" }}</span>
|
||||
</td>
|
||||
<td>{{ order.owner_area_to_act or "" }}</td>
|
||||
<td class="small">{{ order.main_recommended_action or "" }}</td>
|
||||
<td>
|
||||
<a href="/report.html?order_ref={{ order.odoo_order_ref | urlencode }}" target="_blank">HTML</a>
|
||||
|
|
||||
<a href="/report.pdf?order_ref={{ order.odoo_order_ref | urlencode }}" target="_blank">PDF</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="12">No hay órdenes evaluadas.</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<h2>2. Resumen por área responsable</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Área</th>
|
||||
<th>Fallos</th>
|
||||
<th>Avisos</th>
|
||||
<th>Fallos críticos</th>
|
||||
<th>Fallos altos</th>
|
||||
<th>Órdenes afectadas</th>
|
||||
<th>Score incidencia</th>
|
||||
<th>Reglas afectadas</th>
|
||||
<th>Órdenes</th>
|
||||
</tr>
|
||||
{% for owner in owners %}
|
||||
<tr>
|
||||
<td>{{ owner.owner_area }}</td>
|
||||
<td>{{ owner.failed_checks }}</td>
|
||||
<td>{{ owner.warn_checks }}</td>
|
||||
<td>{{ owner.critical_failed_checks }}</td>
|
||||
<td>{{ owner.high_failed_checks }}</td>
|
||||
<td>{{ owner.affected_orders }}</td>
|
||||
<td>{{ owner.total_issue_score }}</td>
|
||||
<td>{{ owner.affected_rules }}</td>
|
||||
<td class="small">{{ owner.affected_order_refs }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="9">Sin incidencias por área.</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<h2>3. Reglas con incidencias</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Regla</th>
|
||||
<th>Nombre</th>
|
||||
<th>Dominio</th>
|
||||
<th>Severidad</th>
|
||||
<th>Estado</th>
|
||||
<th>Área</th>
|
||||
<th>Órdenes afectadas</th>
|
||||
<th>Score total</th>
|
||||
<th>Órdenes</th>
|
||||
</tr>
|
||||
{% for rule in rules %}
|
||||
<tr>
|
||||
<td>{{ rule.rule_code }}</td>
|
||||
<td>{{ rule.rule_name }}</td>
|
||||
<td>{{ rule.domain }}</td>
|
||||
<td>{{ rule.severity }}</td>
|
||||
<td>
|
||||
{% if rule.check_status == 'FAIL' %}
|
||||
<span class="bad">FAIL</span>
|
||||
{% elif rule.check_status == 'WARN' %}
|
||||
<span class="warn">WARN</span>
|
||||
{% else %}
|
||||
{{ rule.check_status }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ rule.owner_area }}</td>
|
||||
<td>{{ rule.order_count }}</td>
|
||||
<td>{{ rule.total_issue_score }}</td>
|
||||
<td class="small">{{ rule.affected_orders }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="9">Sin reglas con incidencias.</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -64,6 +64,9 @@
|
||||
<div class="page">
|
||||
<h1>Informes de ejecución de orden</h1>
|
||||
<p class="muted">{{ title }}</p>
|
||||
<p>
|
||||
<a href="/data-quality-contract.html" target="_blank">Abrir Data Quality Contract</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/cost-readiness.html" target="_blank">Abrir Cost Readiness Cockpit</a>
|
||||
|
||||
1166
ucepsa/edge-oee-demo/sql/reports/090_data_quality_contract.sql
Normal file
1166
ucepsa/edge-oee-demo/sql/reports/090_data_quality_contract.sql
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user