Removed obsolete TODO.md

This commit is contained in:
2026-04-30 14:04:22 +02:00
parent 3620e2c7fc
commit 39612bde53
4 changed files with 337 additions and 97 deletions

View File

@@ -765,6 +765,16 @@ def repository_detail(
<h2>Review Decisions</h2>
{render_review_decisions(decisions)}
</section>
<section class="panel" style="margin-top:18px">
<h2>Classification Quality Feedback</h2>
{render_expectation_gap_form(
action=f"/ui/repos/{repository_id}/expectation-gaps",
default_type="classification",
default_name="Wrong or missing characteristic classification",
default_notes="",
)}
{render_expectation_gaps(service.list_expectation_gaps(repository_id))}
</section>
<section class="panel" style="margin-top:18px">
<h2>Delete Repository</h2>
<form class="stack" method="post" action="/ui/repos/{repository_id}/delete">
@@ -1192,15 +1202,12 @@ def analysis_run_detail(
</section>
<section class="panel" style="margin-top:18px">
<h2>Expectation Gaps</h2>
<form class="stack" method="post" action="/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}/expectation-gaps">
<div class="grid">
<label>Expected type <input name="expected_type" placeholder="capability, feature, fact, classification" required></label>
<label>Expected name <input name="expected_name" placeholder="Use OpenRouter Models" required></label>
<label>Source <input name="source" value="human" required></label>
<label>Notes <input name="notes" placeholder="What made you expect this?"></label>
</div>
<button type="submit">Record Gap</button>
</form>
{render_expectation_gap_form(
action=f"/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}/expectation-gaps",
default_type="classification",
default_name="Missing or wrong classification",
default_notes="",
)}
{render_expectation_gaps(expectation_gaps)}
</section>
"""
@@ -1231,6 +1238,25 @@ def create_expectation_gap_from_form(
)
@router.post("/ui/repos/{repository_id}/expectation-gaps")
def create_repository_expectation_gap_from_form(
repository_id: int,
expected_type: str = Form(...),
expected_name: str = Form(...),
source: str = Form("human"),
notes: str = Form(""),
service: RegistryService = Depends(get_service),
) -> RedirectResponse:
service.record_expectation_gap(
repository_id,
expected_type=expected_type,
expected_name=expected_name,
source=source,
notes=notes,
)
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
@router.get("/ui/repos/{repository_id}/elements")
def repository_element_listing(
repository_id: int,
@@ -1308,6 +1334,13 @@ def repository_element_listing(
<h1 style="margin-right:auto">{escape(title)}</h1>
<a class="button secondary" href="/ui/repos/{repository_id}">Repository</a>
</div>
{render_element_breadcrumbs(
repository_id=repository_id,
scope=scope,
item_type=type,
entry_filter=entry_filter,
analysis_run_id=analysis_run_id,
)}
<section class="panel" style="margin-bottom:18px">
<form class="stack" method="get" action="{filter_action}">
<input type="hidden" name="scope" value="{escape(listing_scope)}">
@@ -1331,6 +1364,13 @@ def repository_element_listing(
</form>
</section>
<section class="panel">
{render_element_type_nav(
repository_id=repository_id,
scope=listing_scope,
item_type=type,
entry_filter=entry_filter,
analysis_run_id=analysis_run_id,
)}
<table>
<thead><tr><th>Entry</th><th>Class</th><th>Name</th><th>Parent</th><th>Source</th><th>Actions</th></tr></thead>
<tbody>{rows}</tbody>
@@ -2318,14 +2358,25 @@ def graph_element_rows(
ability["name"],
"",
ability.get("source_refs", []),
item_id=ability.get("id"),
item_kind="abilities",
description=ability.get("description", ""),
confidence=ability.get("confidence", 1.0),
attributes=ability.get("attributes", []),
status=ability.get("status", ""),
entry_state=entry_state,
)
item_id=ability.get("id"),
item_kind="abilities",
description=ability.get("description", ""),
confidence=ability.get("confidence", 1.0),
attributes=ability.get("attributes", []),
child_counts={
"capabilities": len(ability.get("capabilities", [])),
"features": sum(
len(capability.get("features", []))
for capability in ability.get("capabilities", [])
),
"supports": sum(
len(capability.get("evidence", []))
for capability in ability.get("capabilities", [])
),
},
status=ability.get("status", ""),
entry_state=entry_state,
)
)
for capability in ability.get("capabilities", []):
if item_type == "capabilities":
@@ -2340,6 +2391,10 @@ def graph_element_rows(
description=capability.get("description", ""),
confidence=capability.get("confidence", 1.0),
attributes=capability.get("attributes", []),
child_counts={
"features": len(capability.get("features", [])),
"supports": len(capability.get("evidence", [])),
},
inputs=capability.get("inputs", []),
outputs=capability.get("outputs", []),
status=capability.get("status", ""),
@@ -2360,6 +2415,7 @@ def graph_element_rows(
attributes=feature.get("attributes", []),
confidence=feature.get("confidence", 1.0),
location=feature.get("location", ""),
child_counts={"facts": len(feature.get("source_refs", []))},
status=feature.get("status", ""),
entry_state=entry_state,
)
@@ -2533,7 +2589,7 @@ def render_element_row(
<tr>
<td>{render_entry_badge(row)}</td>
<td><span class="pill">{escape(str(row["primary_class"]))}</span>{render_attribute_pills(row)}</td>
<td>{escape(str(row["name"]))}</td>
<td>{escape(str(row["name"]))}{render_drilldown_links(row, repository_id, analysis_run_id)}</td>
<td>{escape(str(row["parent"]))}</td>
<td>{render_element_source_detail(row)}</td>
<td>{render_element_actions(row, repository_id, analysis_run_id)}</td>
@@ -2541,6 +2597,109 @@ def render_element_row(
"""
def render_element_breadcrumbs(
*,
repository_id: int,
scope: str,
item_type: str,
entry_filter: str,
analysis_run_id: int | None,
) -> str:
scope_label = "Facts" if scope == "facts" else (entry_filter or "all")
href = (
f"/ui/repos/{repository_id}/elements?scope={escape(scope)}"
f"&type={escape(item_type)}{render_analysis_run_query_suffix(analysis_run_id)}"
)
return f"""
<p class="actions">
<a class="pill" href="/ui/repos/{repository_id}">Repository</a>
<span class="pill">{escape(scope_label)}</span>
<a class="pill" href="{href}">{escape(item_type)}</a>
</p>
"""
def render_element_type_nav(
*,
repository_id: int,
scope: str,
item_type: str,
entry_filter: str,
analysis_run_id: int | None,
) -> str:
items = [
("scopes", "Scope"),
("abilities", "Abilities"),
("capabilities", "Capabilities"),
("features", "Features"),
("supports", "Supports"),
("facts", "Facts"),
]
links = []
for value, label in items:
link_scope = "facts" if value == "facts" else scope
params = (
f"scope={quote_plus(link_scope)}&type={quote_plus(value)}"
f"{render_analysis_run_query_suffix(analysis_run_id)}"
)
if value != "facts" and entry_filter:
params += f"&entry_filter={quote_plus(entry_filter)}"
active = " active" if value == item_type else ""
links.append(
f'<a class="pill{active}" href="/ui/repos/{repository_id}/elements?{params}">{escape(label)}</a>'
)
return f'<p class="actions">{"".join(links)}</p>'
def render_drilldown_links(
row: dict,
repository_id: int,
analysis_run_id: int | None,
) -> str:
item_kind = row.get("item_kind")
entry_state = str(row.get("entry_state") or "approved")
counts = row.get("child_counts", {})
links: list[tuple[str, str, str]] = []
if item_kind == "scope":
links.append(("abilities", "abilities", ""))
elif item_kind == "abilities":
links.extend(
[
("capabilities", "capabilities", str(row["name"])),
("features", "features", str(row["name"])),
("supports", "supports", str(row["name"])),
]
)
elif item_kind == "capabilities":
links.extend(
[
("features", "features", str(row["name"])),
("supports", "supports", str(row["name"])),
]
)
elif item_kind == "features":
fact_query = str(row.get("location") or row.get("name") or "")
links.append(("facts", "facts", fact_query))
if not links:
return ""
rendered = []
for item_type, label, query in links:
count = counts.get(item_type)
label_text = f"{count} {label}" if count is not None else label
scope = "facts" if item_type == "facts" else "all"
params = f"scope={scope}&type={item_type}"
if item_type != "facts":
params += f"&entry_filter={quote_plus(entry_state)}"
if query:
params += f"&q={quote_plus(query)}"
if analysis_run_id is not None:
params += f"&analysis_run_id={analysis_run_id}"
rendered.append(
f'<a class="pill" href="/ui/repos/{repository_id}/elements?{params}">{escape(label_text)}</a>'
)
return f'<p class="actions">{ "".join(rendered) }</p>'
def render_attribute_pills(row: dict) -> str:
attributes = [
str(attribute)
@@ -3028,6 +3187,54 @@ def render_review_decisions(decisions: list) -> str:
"""
def render_expectation_gap_form(
*,
action: str,
default_type: str,
default_name: str,
default_notes: str,
) -> str:
return f"""
<form class="stack" method="post" action="{action}">
<div class="grid">
<label>Expected type
<select name="expected_type">
{render_gap_type_options(default_type)}
</select>
</label>
<label>Expected name <input name="expected_name" value="{escape(default_name)}" required></label>
<label>Source <input name="source" value="human" required></label>
<label>Notes <input name="notes" value="{escape(default_notes)}" placeholder="Expected class, attribute, or organization smell"></label>
</div>
<div class="actions">
<button type="submit">Record Gap</button>
<button class="secondary" type="submit" name="expected_type" value="classification-primary">Missing Primary Class</button>
<button class="secondary" type="submit" name="expected_type" value="classification-attribute">Wrong Attribute</button>
<button class="secondary" type="submit" name="expected_type" value="classification-granularity">Granularity Smell</button>
<button class="secondary" type="submit" name="expected_type" value="classification-support">Support Organization Smell</button>
</div>
</form>
"""
def render_gap_type_options(selected: str) -> str:
options = [
"ability",
"capability",
"feature",
"fact",
"classification",
"classification-primary",
"classification-attribute",
"classification-granularity",
"classification-support",
]
return "".join(
f'<option value="{escape(option)}"{" selected" if option == selected else ""}>{escape(option)}</option>'
for option in options
)
def render_expectation_gaps(gaps: list) -> str:
if not gaps:
return '<p class="muted">No expectation gaps recorded for this run.</p>'
@@ -3279,7 +3486,10 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
<li id="capability-{capability['id']}">
<strong>{escape(capability['name'])}</strong>
<span class="pill">ID {capability['id']}</span>
<span class="pill">{escape(capability.get('primary_class', 'capability'))}</span>
{render_characteristic_attributes(capability)}
<span class="pill">{capability['confidence']:.2f} {escape(capability['confidence_label'])}</span>
{render_approved_tree_drilldown(repository_id, "capabilities", capability)}
<p class="muted">{escape(capability['description'])}</p>
{render_approved_capability_forms(capability, repository_id)}
<h3>Features</h3>
@@ -3294,7 +3504,10 @@ def render_ability_map(ability_map: dict, repository_id: int) -> str:
<li id="ability-{ability['id']}">
<strong>{escape(ability['name'])}</strong>
<span class="pill">ID {ability['id']}</span>
<span class="pill">{escape(ability.get('primary_class', 'ability'))}</span>
{render_characteristic_attributes(ability)}
<span class="pill">{ability['confidence']:.2f} {escape(ability['confidence_label'])}</span>
{render_approved_tree_drilldown(repository_id, "abilities", ability)}
<p class="muted">{escape(ability['description'])}</p>
{render_approved_ability_forms(ability, repository_id)}
<ul>{''.join(capabilities)}</ul>
@@ -3371,8 +3584,11 @@ def render_approved_feature(feature: dict, repository_id: int) -> str:
<li>
{escape(feature["name"])}
<span class="pill">{escape(feature["type"])}</span>
<span class="pill">{escape(feature.get("primary_class", feature["type"]))}</span>
{render_characteristic_attributes(feature)}
<span class="pill">{feature["confidence"]:.2f} {escape(feature["confidence_label"])}</span>
<span class="source">{escape(feature["location"])}</span>
{render_approved_tree_drilldown(repository_id, "features", feature)}
{render_sources(feature.get("source_refs", []))}
<form class="stack" method="post" action="/ui/repos/{repository_id}/features/{feature['id']}/edit">
<label>Name <input name="name" value="{escape(feature['name'])}" required></label>
@@ -3388,6 +3604,70 @@ def render_approved_feature(feature: dict, repository_id: int) -> str:
"""
def render_characteristic_attributes(item: dict) -> str:
primary = str(item.get("primary_class", ""))
attributes = [
str(attribute)
for attribute in item.get("attributes", [])
if str(attribute) and str(attribute) != primary
]
return "".join(
f' <span class="pill">{escape(attribute)}</span>'
for attribute in attributes
)
def render_approved_tree_drilldown(
repository_id: int,
item_kind: str,
item: dict,
) -> str:
name = str(item.get("name", ""))
if item_kind == "abilities":
links = [
("capabilities", len(item.get("capabilities", [])), name),
(
"features",
sum(
len(capability.get("features", []))
for capability in item.get("capabilities", [])
),
name,
),
(
"supports",
sum(
len(capability.get("evidence", []))
for capability in item.get("capabilities", [])
),
name,
),
]
elif item_kind == "capabilities":
links = [
("features", len(item.get("features", [])), name),
("supports", len(item.get("evidence", [])), name),
]
elif item_kind == "features":
links = [("facts", len(item.get("source_refs", [])), item.get("location", name))]
else:
links = []
if not links:
return ""
rendered = []
for target_type, count, query in links:
scope = "facts" if target_type == "facts" else "all"
params = f"scope={scope}&type={target_type}"
if target_type != "facts":
params += "&entry_filter=approved"
if query:
params += f"&q={quote_plus(str(query))}"
rendered.append(
f'<a class="pill" href="/ui/repos/{repository_id}/elements?{params}">{count} {escape(target_type)}</a>'
)
return f'<p class="actions">{"".join(rendered)}</p>'
def render_approved_evidence(evidence: dict, repository_id: int) -> str:
target_kind = escape(str(evidence.get("target_kind") or "capability"))
target_id = evidence.get("target_id")