generated from coulomb/repo-seed
Removed obsolete TODO.md
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user