Towards systematic evidence

This commit is contained in:
2026-04-29 14:41:39 +02:00
parent 93640976b0
commit 6c0a7db5e4
4 changed files with 171 additions and 17 deletions

View File

@@ -537,8 +537,9 @@ def repository_detail(
<tbody>{run_rows or '<tr><td colspan="5" class="muted">No runs yet.</td></tr>'}</tbody>
</table>
</section>
<section class="panel">
<h2>Approved Ability Map</h2>
<section class="stack">
<div class="panel">
<h2>Approved Characteristics</h2>
{render_graph_counts(
asdict(ability_map),
facts_count=None,
@@ -548,13 +549,19 @@ def repository_detail(
),
)}
{render_approved_registry_actions(repository_id, asdict(ability_map))}
</div>
<div class="panel">
<h2>Latest Candidate Graph</h2>
{render_latest_candidate_counts(repository_id, latest_candidate, service)}
</div>
<div class="panel">
<h2>Approved Characteristic Tree</h2>
{render_ability_map(asdict(ability_map), repository_id)}
</div>
</section>
</div>
<section class="panel" style="margin-top:18px">
<h2>Manual Registry Entry</h2>
<h2>Manual Characteristic Tuning</h2>
<div class="grid">
<form class="stack" method="post" action="/ui/repos/{repository_id}/abilities">
<h3>Add Ability</h3>
@@ -583,12 +590,12 @@ def repository_detail(
<button type="submit">Add Feature</button>
</form>
<form class="stack" method="post" action="/ui/repos/{repository_id}/evidence">
<h3>Add Evidence</h3>
<label>Capability ID <input name="capability_id" type="number" min="1" required></label>
<label>Type <input name="type" required></label>
<label>Reference <input name="reference" required></label>
<h3>Add Capability Support</h3>
<label>Supported capability ID <input name="capability_id" type="number" min="1" required></label>
<label>Support type <input name="type" placeholder="fact, documentation, test, example, feature" required></label>
<label>Reference <input name="reference" placeholder="Observed fact, file, or lower-level characteristic" required></label>
<label>Strength <input name="strength" value="medium" required></label>
<button type="submit">Add Evidence</button>
<button type="submit">Add Support</button>
</form>
</div>
</section>
@@ -1850,7 +1857,7 @@ def render_latest_candidate_counts(
analysis_run_id, candidate_graph = latest_candidate
facts_count = len(service.list_observed_facts(repository_id, analysis_run_id))
return render_graph_counts(
asdict(candidate_graph),
active_candidate_graph(asdict(candidate_graph)),
facts_count=facts_count,
label_prefix="candidate",
base_href=(
@@ -1865,6 +1872,39 @@ def render_latest_candidate_counts(
)
def active_candidate_graph(graph: dict) -> dict:
active_abilities = []
for ability in graph.get("abilities", []):
active_capabilities = []
for capability in ability.get("capabilities", []):
active_features = [
feature
for feature in capability.get("features", [])
if feature.get("status", "candidate") != "rejected"
]
active_evidence = [
evidence
for evidence in capability.get("evidence", [])
if evidence.get("status", "candidate") != "rejected"
]
capability_active = (
capability.get("status", "candidate") != "rejected"
and (active_features or active_evidence)
)
if capability_active:
active_capability = {**capability}
active_capability["features"] = active_features
active_capability["evidence"] = active_evidence
active_capabilities.append(active_capability)
if ability.get("status", "candidate") != "rejected" and active_capabilities:
active_ability = {**ability}
active_ability["capabilities"] = active_capabilities
active_abilities.append(active_ability)
active_graph = {**graph}
active_graph["abilities"] = active_abilities
return active_graph
def render_graph_counts(
graph: dict,
facts_count: int | None = None,
@@ -2639,6 +2679,10 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
abilities = ability_map.get("abilities", [])
if not abilities:
return '<p class="muted">No approved entries yet.</p>'
repository = ability_map["repository"]
scope_description = repository.get("description") or (
f"Scope root for the approved characteristics of {repository['name']}."
)
items = []
for ability in abilities:
capabilities = []
@@ -2659,7 +2703,10 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
<span class="pill">{capability['confidence']:.2f} {escape(capability['confidence_label'])}</span>
<p class="muted">{escape(capability['description'])}</p>
{render_approved_capability_forms(capability, repository_id)}
<ul>{features}{evidence}</ul>
<h3>Features</h3>
<ul>{features or '<li class="muted">No approved features.</li>'}</ul>
<h3>Evidence supporting this capability</h3>
<ul>{evidence or '<li class="muted">No support evidence yet.</li>'}</ul>
</li>
"""
)
@@ -2675,7 +2722,18 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
</li>
"""
)
return f'<div class="tree"><ul>{"".join(items)}</ul></div>'
return f"""
<div class="tree">
<ul>
<li id="scope-{repository['id']}">
<strong>{escape(repository['name'])}</strong>
<span class="pill">scope</span>
<p class="muted">{escape(scope_description)}</p>
<ul>{"".join(items)}</ul>
</li>
</ul>
</div>
"""
def render_approved_ability_forms(ability: dict, repository_id: int) -> str:
@@ -2739,18 +2797,18 @@ def render_approved_feature(feature: dict, repository_id: int) -> str:
def render_approved_evidence(evidence: dict, repository_id: int) -> str:
return f"""
<li>
{escape(evidence["type"])}
<strong>{escape(evidence["type"])}</strong>
<span class="pill">{escape(evidence["strength"])}</span>
<span class="source">{escape(evidence["reference"])}</span>
{render_sources(evidence.get("source_refs", []))}
<form class="stack" method="post" action="/ui/repos/{repository_id}/evidence/{evidence['id']}/edit">
<label>Type <input name="type" value="{escape(evidence['type'])}" required></label>
<label>Support type <input name="type" value="{escape(evidence['type'])}" required></label>
<label>Reference <input name="reference" value="{escape(evidence['reference'])}" required></label>
<label>Strength <input name="strength" value="{escape(evidence['strength'])}" required></label>
<button class="secondary" type="submit">Save Evidence</button>
<button class="secondary" type="submit">Save Support</button>
</form>
<form method="post" action="/ui/repos/{repository_id}/evidence/{evidence['id']}/delete">
<button class="secondary" type="submit">Delete Evidence</button>
<button class="secondary" type="submit">Delete Support</button>
</form>
</li>
"""