Pipeline engine rewritten with combo-first loop: each combination is processed through all requested passes before moving to the next, with incremental DB saves after every step (crash-safe). Blocked combos now get result rows so they appear in the results page with constraint violation reasons. New pipeline_runs table tracks run lifecycle (pending/running/completed/failed/ cancelled). Web route launches pipeline in a background thread with its own DB connection. HTMX polling partial shows live progress with per-pass breakdown. Also: status guard prevents reviewed->scored downgrade, save_combination loads existing status on dedup for correct resume, per-metric scores show domain bounds + units + position bars, ensure_metric backfills units on existing rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
119 lines
3.8 KiB
HTML
119 lines
3.8 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Combination #{{ combo.id }} — PhysCom{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1>Combination #{{ combo.id }}</h1>
|
|
<a href="{{ url_for('results.results_domain', domain_name=domain.name) }}" class="btn">Back to list</a>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<dl>
|
|
<dt>Domain</dt><dd>{{ domain.name }}</dd>
|
|
<dt>Status</dt><dd><span class="badge badge-{{ combo.status }}">{{ combo.status }}</span></dd>
|
|
{% if combo.block_reason %}
|
|
<dt>Block Reason</dt><dd>{{ combo.block_reason }}</dd>
|
|
{% endif %}
|
|
{% if result %}
|
|
<dt>Composite Score</dt><dd class="score-cell">{{ "%.4f"|format(result.composite_score) }}</dd>
|
|
<dt>Pass Reached</dt><dd>{{ result.pass_reached }}</dd>
|
|
{% if result.novelty_flag %}
|
|
<dt>Novelty</dt><dd>{{ result.novelty_flag }}</dd>
|
|
{% endif %}
|
|
{% if result.llm_review %}
|
|
<dt>LLM Review</dt><dd>{{ result.llm_review }}</dd>
|
|
{% endif %}
|
|
{% if result.human_notes %}
|
|
<dt>Human Notes</dt><dd>{{ result.human_notes }}</dd>
|
|
{% endif %}
|
|
{% endif %}
|
|
</dl>
|
|
</div>
|
|
|
|
<h2>Entities</h2>
|
|
<div class="card-grid">
|
|
{% for e in combo.entities %}
|
|
<div class="card">
|
|
<h3><a href="{{ url_for('entities.entity_detail', entity_id=e.id) }}">{{ e.name }}</a></h3>
|
|
<p class="subtitle">{{ e.dimension }}</p>
|
|
<p>{{ e.description }}</p>
|
|
<table class="compact">
|
|
<thead><tr><th>Key</th><th>Value</th><th>Type</th></tr></thead>
|
|
<tbody>
|
|
{% for dep in e.dependencies %}
|
|
<tr>
|
|
<td>{{ dep.key }}</td>
|
|
<td>{{ dep.value }}{{ ' ' + dep.unit if dep.unit else '' }}</td>
|
|
<td><span class="badge badge-{{ dep.constraint_type }}">{{ dep.constraint_type }}</span></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% if scores %}
|
|
<h2>Per-Metric Scores</h2>
|
|
{% set bounds = {} %}
|
|
{% for mb in domain.metric_bounds %}
|
|
{% set _ = bounds.update({mb.metric_name: mb}) %}
|
|
{% endfor %}
|
|
<div class="card">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Metric</th>
|
|
<th>Raw Value</th>
|
|
<th>Domain Range</th>
|
|
<th>Position</th>
|
|
<th>Normalized</th>
|
|
<th>Weight</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for s in scores %}
|
|
{% set mb = bounds.get(s.metric_name) %}
|
|
<tr>
|
|
<td>{{ s.metric_name }}</td>
|
|
{% set unit = s.metric_unit or '' %}
|
|
<td class="score-cell">{{ "%.2f"|format(s.raw_value) if s.raw_value is not none else '—' }}{{ ' ' + unit if unit and s.raw_value is not none else '' }}</td>
|
|
<td>
|
|
{%- if mb -%}
|
|
{{ "%.2f"|format(mb.norm_min) }} — {{ "%.2f"|format(mb.norm_max) }}{{ ' ' + unit if unit else '' }}
|
|
{%- else -%}
|
|
—
|
|
{%- endif -%}
|
|
</td>
|
|
<td>
|
|
{%- if mb and s.raw_value is not none -%}
|
|
{%- if s.raw_value <= mb.norm_min -%}
|
|
<span class="badge badge-blocked">at/below min</span>
|
|
{%- elif s.raw_value >= mb.norm_max -%}
|
|
<span class="badge badge-valid">at/above max</span>
|
|
{%- else -%}
|
|
{% set pct = ((s.raw_value - mb.norm_min) / (mb.norm_max - mb.norm_min) * 100) | int %}
|
|
<div class="metric-bar-container">
|
|
<div class="metric-bar" style="width: {{ pct }}%"></div>
|
|
</div>
|
|
<span class="metric-bar-label">~{{ pct }}%</span>
|
|
{%- endif -%}
|
|
{%- else -%}
|
|
—
|
|
{%- endif -%}
|
|
</td>
|
|
<td class="score-cell">{{ "%.4f"|format(s.normalized_score) if s.normalized_score is not none else '—' }}</td>
|
|
<td>{{ "%.0f%%"|format(mb.weight * 100) if mb else '—' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<h2>Human Review</h2>
|
|
<div id="review-section">
|
|
{% include "results/_review_form.html" %}
|
|
</div>
|
|
{% endblock %}
|