{% extends "base.html" %} {% from "components/status_badge.html" import status_badge %} {% block title %}{{ project.name }} — Project{% endblock %} {% block app_title %}{{ project.name }}{% endblock %} {% block content %} {% set pid = project.project_id %} {# ---------------- 1. Project header (hero) ---------------- #}

{{ project.name }}

{{ project.path }}
{{ jobs_total }} Jobs
{{ runs_total }} Runs
{% if latest_job %} {{ status_badge(latest_job.status.value) }} {% else %} — {% endif %} Latest job
{# ---------------- 2. Latest training summary ---------------- #}

Latest training

{% if latest_training %} View run → {% endif %}
{% if latest_training and latest_training_eval %} {% set is_multi = latest_training_eval.get("is_multi") %} {% if not is_multi %} {% set m = latest_training_eval.get("metrics") or {} %}
AUC {{ "%.3f"|format(m.get("auc")) if m.get("auc") is not none else "—" }}
F1 {{ "%.3f"|format(m.get("f1")) if m.get("f1") is not none else "—" }}
Precision {{ "%.3f"|format(m.get("precision")) if m.get("precision") is not none else "—" }}
Recall {{ "%.3f"|format(m.get("recall")) if m.get("recall") is not none else "—" }}
{% else %} {# Ensemble: per-model metrics live in ranking, not a single metrics dict. Point users to Compare rather than showing four empty placeholders. #}
Ensemble run — per-model metrics are available in Compare. {% if latest_training_eval.get("best_model") %} Best base model: {{ latest_training_eval.get("best_model") }}. {% endif %}
{% endif %}
Model: {{ latest_training.model_types | join(", ") if latest_training.model_types else "—" }} {% if is_multi %}Ensemble{% endif %} {% if latest_training.status %}· {{ status_badge(latest_training.status) }}{% endif %} {% if latest_training.timestamp %}· {{ latest_training.timestamp[:19] }} UTC{% endif %}
{% elif latest_training %} {# Training run exists but produced no evaluation report (e.g. failed/partial). #}
Training run {{ latest_training.run_id }} has no evaluation summary (status: {{ latest_training.status }}).
{% else %}
No training runs yet for this project.
{% endif %}
{# ---------------- 3. Jobs & Runs by Run type ---------------- #}

Jobs & Runs by type

{% set type_meta = { "etl": {"icon": "bi-database", "label": "ETL"}, "eda": {"icon": "bi-bar-chart", "label": "EDA"}, "inference": {"icon": "bi-lightning", "label": "Inference"}, "training": {"icon": "bi-cpu", "label": "Training"} } %} {% for bucket in run_type_buckets %} {% set tjobs = job_groups[bucket] %} {% set truns = run_groups[bucket] %} {% set meta = type_meta[bucket] %}

{{ meta.label }}

{{ tjobs|length }} job{{ '' if tjobs|length == 1 else 's' }} · {{ truns|length }} run{{ '' if truns|length == 1 else 's' }}
{% if not tjobs and not truns %}
No {{ meta.label|lower }} activity yet.
{% else %}

Jobs

{% if tjobs %} {% for job in tjobs[:10] %} {% endfor %}
JobStatusRunEnqueued
{{ job.job_id[:12] }} {{ status_badge(job.status.value) }} {% if job.run_id %}{{ job.run_id[:16] }}{% else %}—{% endif %} {{ job.enqueued_at[:19] if job.enqueued_at else "—" }}
{% else %}
No jobs.
{% endif %}

Runs

{% if truns %} {% if bucket == "training" %}{% endif %} {% for run in truns[:10] %} {% if bucket == "training" %} {% endif %} {% endfor %}
RunStatusAUCF1
{{ run.run_id[:20] }} {{ status_badge(run.status) }}{{ "%.3f"|format(run.val_auc) if run.val_auc is not none else "—" }} {{ "%.3f"|format(run.val_f1) if run.val_f1 is not none else "—" }}
{% else %}
No runs.
{% endif %}
{% endif %}
{% endfor %}
{# ---------------- 4. Lineage snippet ---------------- #} {# ADR-0003: derived_from (Run→Run retrain chain). lineage_chain is ordered root→leaf; lineage_available is True only when at least one run has a real derived_from link. The muted placeholder is the empty-state. #}

Lineage

{% if lineage_available %} retrain chain · {{ lineage_chain|length }} run{{ '' if lineage_chain|length == 1 else 's' }} {% endif %}
{% if lineage_available %}
{% for run in lineage_chain %} {% if not loop.first %}→{% endif %} {{ run.run_id[:24] }} {% endfor %}
Each run was retrained from the one before it (derived_from).
{% else %}
No retrain lineage yet — runs Retrained from another Run will appear here as a derived_from chain (ADR-0003).
{% endif %}
{# ---------------- 5. YAML editor (preserved action) ---------------- #}

Submit a job

{% set submit_url = '/projects/' ~ pid ~ '/jobs' %} {% include "components/editor.html" %}
Submit a configuration to see validation results. Jobs run in {{ project.path }}.
{% endblock %}