generated from coulomb/repo-seed
Improved datamodel and deterministic generation
This commit is contained in:
@@ -719,6 +719,8 @@ def repository_detail(
|
||||
<h3>Add Ability</h3>
|
||||
<label>Name <input name="name" required></label>
|
||||
<label>Description <textarea name="description" rows="2"></textarea></label>
|
||||
<label>Primary class <input name="primary_class" value="ability" required></label>
|
||||
<label>Attributes <input name="attributes" placeholder="Comma-separated"></label>
|
||||
<label>Confidence <input name="confidence" type="number" min="0" max="1" step="0.01" value="1.0" required></label>
|
||||
<button type="submit">Add Ability</button>
|
||||
</form>
|
||||
@@ -729,6 +731,8 @@ def repository_detail(
|
||||
<label>Description <textarea name="description" rows="2"></textarea></label>
|
||||
<label>Inputs <input name="inputs" placeholder="Comma-separated"></label>
|
||||
<label>Outputs <input name="outputs" placeholder="Comma-separated"></label>
|
||||
<label>Primary class <input name="primary_class" value="capability" required></label>
|
||||
<label>Attributes <input name="attributes" placeholder="Comma-separated"></label>
|
||||
<label>Confidence <input name="confidence" type="number" min="0" max="1" step="0.01" value="1.0" required></label>
|
||||
<button type="submit">Add Capability</button>
|
||||
</form>
|
||||
@@ -737,6 +741,8 @@ def repository_detail(
|
||||
<label>Capability ID <input name="capability_id" type="number" min="1" required></label>
|
||||
<label>Name <input name="name" required></label>
|
||||
<label>Type <input name="type" required></label>
|
||||
<label>Primary class <input name="primary_class" placeholder="Defaults to type"></label>
|
||||
<label>Attributes <input name="attributes" placeholder="Comma-separated; defaults to type"></label>
|
||||
<label>Location <input name="location"></label>
|
||||
<label>Confidence <input name="confidence" type="number" min="0" max="1" step="0.01" value="1.0" required></label>
|
||||
<button type="submit">Add Feature</button>
|
||||
@@ -835,6 +841,8 @@ def create_ability_from_form(
|
||||
name: str = Form(...),
|
||||
description: str = Form(""),
|
||||
confidence: float = Form(1.0),
|
||||
primary_class: str = Form("ability"),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.add_ability(
|
||||
@@ -842,6 +850,8 @@ def create_ability_from_form(
|
||||
name=name,
|
||||
description=description,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or "ability",
|
||||
attributes=split_csv(attributes),
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -855,6 +865,8 @@ def create_capability_from_form(
|
||||
inputs: str = Form(""),
|
||||
outputs: str = Form(""),
|
||||
confidence: float = Form(1.0),
|
||||
primary_class: str = Form("capability"),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.add_capability(
|
||||
@@ -865,6 +877,8 @@ def create_capability_from_form(
|
||||
inputs=split_csv(inputs),
|
||||
outputs=split_csv(outputs),
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or "capability",
|
||||
attributes=split_csv(attributes),
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -877,6 +891,8 @@ def create_feature_from_form(
|
||||
type: str = Form(...),
|
||||
location: str = Form(""),
|
||||
confidence: float = Form(1.0),
|
||||
primary_class: str = Form(""),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.add_feature(
|
||||
@@ -886,6 +902,8 @@ def create_feature_from_form(
|
||||
type=type,
|
||||
location=location,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or type,
|
||||
attributes=split_csv(attributes) or [type],
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -924,6 +942,8 @@ def edit_ability_from_form(
|
||||
name: str = Form(...),
|
||||
description: str = Form(""),
|
||||
confidence: float = Form(1.0),
|
||||
primary_class: str = Form("ability"),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.update_ability(
|
||||
@@ -932,6 +952,8 @@ def edit_ability_from_form(
|
||||
name=name,
|
||||
description=description,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or "ability",
|
||||
attributes=split_csv(attributes),
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -955,6 +977,8 @@ def edit_capability_from_form(
|
||||
inputs: str = Form(""),
|
||||
outputs: str = Form(""),
|
||||
confidence: float = Form(1.0),
|
||||
primary_class: str = Form("capability"),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.update_capability(
|
||||
@@ -965,6 +989,8 @@ def edit_capability_from_form(
|
||||
inputs=split_csv(inputs),
|
||||
outputs=split_csv(outputs),
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or "capability",
|
||||
attributes=split_csv(attributes),
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -987,6 +1013,8 @@ def edit_feature_from_form(
|
||||
type: str = Form(...),
|
||||
location: str = Form(""),
|
||||
confidence: float = Form(1.0),
|
||||
primary_class: str = Form(""),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.update_feature(
|
||||
@@ -996,6 +1024,8 @@ def edit_feature_from_form(
|
||||
type=type,
|
||||
location=location,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or type,
|
||||
attributes=split_csv(attributes) or [type],
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -1208,6 +1238,7 @@ def repository_element_listing(
|
||||
type: str = Query("abilities"),
|
||||
q: str = Query(""),
|
||||
class_filter: str = Query(""),
|
||||
attribute_filter: str = Query(""),
|
||||
entry_filter: str = Query(""),
|
||||
candidate_status_filter: str = Query("active"),
|
||||
support_orientation_filter: str = Query(""),
|
||||
@@ -1252,13 +1283,15 @@ def repository_element_listing(
|
||||
elements,
|
||||
"",
|
||||
"",
|
||||
entry_filter,
|
||||
candidate_status_filter,
|
||||
"",
|
||||
entry_filter=entry_filter,
|
||||
candidate_status_filter=candidate_status_filter,
|
||||
)
|
||||
filtered = filter_element_rows(
|
||||
entry_scoped_elements,
|
||||
q,
|
||||
class_filter,
|
||||
attribute_filter,
|
||||
candidate_status_filter="all",
|
||||
support_orientation_filter=support_orientation_filter,
|
||||
)
|
||||
@@ -1283,11 +1316,13 @@ def repository_element_listing(
|
||||
<div class="grid">
|
||||
<label>Search <input name="q" value="{escape(q)}" placeholder="Name, parent, source, or class"></label>
|
||||
<label>Class <input name="class_filter" value="{escape(class_filter)}" list="element-classes" placeholder="Any class"></label>
|
||||
<label>Attribute <input name="attribute_filter" value="{escape(attribute_filter)}" list="element-attributes" placeholder="Any attribute"></label>
|
||||
{render_entry_filter(entry_filter) if scope != "facts" else ""}
|
||||
{render_candidate_status_filter(candidate_status_filter) if scope != "facts" else ""}
|
||||
{render_support_orientation_filter(support_orientation_filter) if type in {"supports", "evidence"} else ""}
|
||||
</div>
|
||||
{render_class_datalist(entry_scoped_elements)}
|
||||
{render_attribute_datalist(entry_scoped_elements)}
|
||||
<div class="actions">
|
||||
<button type="submit">Filter</button>
|
||||
<a class="button secondary" href="{filter_action}?scope={escape(listing_scope)}&type={escape(type)}{render_analysis_run_query_suffix(analysis_run_id)}">Clear</a>
|
||||
@@ -1562,6 +1597,8 @@ def edit_candidate_ability_from_form(
|
||||
name: str = Form(...),
|
||||
description: str = Form(""),
|
||||
confidence: float = Form(...),
|
||||
primary_class: str = Form("ability"),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.edit_candidate_ability(
|
||||
@@ -1571,6 +1608,8 @@ def edit_candidate_ability_from_form(
|
||||
name=name,
|
||||
description=description,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or "ability",
|
||||
attributes=split_csv(attributes),
|
||||
notes="Edited from web UI",
|
||||
)
|
||||
return RedirectResponse(
|
||||
@@ -1590,6 +1629,8 @@ def edit_candidate_capability_from_form(
|
||||
name: str = Form(...),
|
||||
description: str = Form(""),
|
||||
confidence: float = Form(...),
|
||||
primary_class: str = Form("capability"),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.edit_candidate_capability(
|
||||
@@ -1599,6 +1640,42 @@ def edit_candidate_capability_from_form(
|
||||
name=name,
|
||||
description=description,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or "capability",
|
||||
attributes=split_csv(attributes),
|
||||
notes="Edited from web UI",
|
||||
)
|
||||
return RedirectResponse(
|
||||
f"/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
"/candidate-features/{candidate_feature_id}/edit"
|
||||
)
|
||||
def edit_candidate_feature_from_form(
|
||||
repository_id: int,
|
||||
analysis_run_id: int,
|
||||
candidate_feature_id: int,
|
||||
name: str = Form(...),
|
||||
type: str = Form(...),
|
||||
location: str = Form(""),
|
||||
confidence: float = Form(...),
|
||||
primary_class: str = Form(""),
|
||||
attributes: str = Form(""),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.edit_candidate_feature(
|
||||
repository_id,
|
||||
analysis_run_id,
|
||||
candidate_feature_id,
|
||||
name=name,
|
||||
type=type,
|
||||
location=location,
|
||||
confidence=confidence,
|
||||
primary_class=primary_class or type,
|
||||
attributes=split_csv(attributes) or [type],
|
||||
notes="Edited from web UI",
|
||||
)
|
||||
return RedirectResponse(
|
||||
@@ -2245,6 +2322,7 @@ def graph_element_rows(
|
||||
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,
|
||||
)
|
||||
@@ -2261,6 +2339,7 @@ def graph_element_rows(
|
||||
item_kind="capabilities",
|
||||
description=capability.get("description", ""),
|
||||
confidence=capability.get("confidence", 1.0),
|
||||
attributes=capability.get("attributes", []),
|
||||
inputs=capability.get("inputs", []),
|
||||
outputs=capability.get("outputs", []),
|
||||
status=capability.get("status", ""),
|
||||
@@ -2271,12 +2350,14 @@ def graph_element_rows(
|
||||
if item_type == "features":
|
||||
rows.append(
|
||||
element_row(
|
||||
feature.get("type", "feature"),
|
||||
feature.get("primary_class", feature.get("type", "feature")),
|
||||
feature["name"],
|
||||
capability["name"],
|
||||
feature.get("source_refs", []),
|
||||
item_id=feature.get("id"),
|
||||
item_kind="features",
|
||||
type=feature.get("type", ""),
|
||||
attributes=feature.get("attributes", []),
|
||||
confidence=feature.get("confidence", 1.0),
|
||||
location=feature.get("location", ""),
|
||||
status=feature.get("status", ""),
|
||||
@@ -2367,12 +2448,14 @@ def filter_element_rows(
|
||||
rows: list[dict],
|
||||
query: str,
|
||||
class_filter: str,
|
||||
attribute_filter: str = "",
|
||||
entry_filter: str = "",
|
||||
candidate_status_filter: str = "",
|
||||
support_orientation_filter: str = "",
|
||||
) -> list[dict]:
|
||||
query = query.strip().lower()
|
||||
class_filter = class_filter.strip().lower()
|
||||
attribute_filter = attribute_filter.strip().lower()
|
||||
entry_filter = entry_filter.strip().lower()
|
||||
candidate_status_filter = candidate_status_filter.strip().lower()
|
||||
support_orientation_filter = support_orientation_filter.strip().lower()
|
||||
@@ -2387,9 +2470,15 @@ def filter_element_rows(
|
||||
row_class = str(row["primary_class"]).lower()
|
||||
if class_filter and class_filter not in row_class:
|
||||
continue
|
||||
row_attributes = [str(item).lower() for item in row.get("attributes", [])]
|
||||
if attribute_filter and not any(
|
||||
attribute_filter in item for item in row_attributes
|
||||
):
|
||||
continue
|
||||
haystack = " ".join(
|
||||
[
|
||||
str(row["primary_class"]),
|
||||
" ".join(str(item) for item in row.get("attributes", [])),
|
||||
str(row["name"]),
|
||||
str(row["parent"]),
|
||||
str(row.get("entry_state", "")),
|
||||
@@ -2443,7 +2532,7 @@ def render_element_row(
|
||||
return f"""
|
||||
<tr>
|
||||
<td>{render_entry_badge(row)}</td>
|
||||
<td><span class="pill">{escape(str(row["primary_class"]))}</span></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["parent"]))}</td>
|
||||
<td>{render_element_source_detail(row)}</td>
|
||||
@@ -2452,6 +2541,20 @@ def render_element_row(
|
||||
"""
|
||||
|
||||
|
||||
def render_attribute_pills(row: dict) -> str:
|
||||
attributes = [
|
||||
str(attribute)
|
||||
for attribute in row.get("attributes", [])
|
||||
if str(attribute) and str(attribute) != str(row.get("primary_class", ""))
|
||||
]
|
||||
if not attributes:
|
||||
return ""
|
||||
return "".join(
|
||||
f' <span class="pill">{escape(attribute)}</span>'
|
||||
for attribute in attributes
|
||||
)
|
||||
|
||||
|
||||
def render_element_source_detail(row: dict) -> str:
|
||||
if row.get("item_kind") == "evidence":
|
||||
target = escape(str(row.get("target_kind") or "capability"))
|
||||
@@ -2647,7 +2750,7 @@ def render_candidate_element_actions(
|
||||
)
|
||||
status = row.get("status", "candidate")
|
||||
edit = ""
|
||||
if item_kind in {"abilities", "capabilities"}:
|
||||
if item_kind in {"abilities", "capabilities", "features"}:
|
||||
edit_action = (
|
||||
f"/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}"
|
||||
f"/{collection}/{item_id}/edit"
|
||||
@@ -2722,6 +2825,7 @@ def render_support_orientation_filter(support_orientation_filter: str) -> str:
|
||||
def render_element_edit_fields(row: dict) -> str:
|
||||
item_kind = row["item_kind"]
|
||||
name = escape(str(row["name"]))
|
||||
classification = render_classification_edit_fields(row)
|
||||
if item_kind == "scope":
|
||||
return f"""
|
||||
<label>Name <input name="name" value="{name}" required></label>
|
||||
@@ -2734,6 +2838,7 @@ def render_element_edit_fields(row: dict) -> str:
|
||||
return f"""
|
||||
<label>Name <input name="name" value="{name}" required></label>
|
||||
<label>Type <input name="type" value="{feature_type}" required></label>
|
||||
{classification}
|
||||
<label>Location <input name="location" value="{location}"></label>
|
||||
"""
|
||||
if item_kind == "evidence":
|
||||
@@ -2746,7 +2851,10 @@ def render_element_edit_fields(row: dict) -> str:
|
||||
<label>Reference ID <input name="reference_id" type="number" min="1" value="{row.get('reference_id') or ''}"></label>
|
||||
<label>Strength <input name="strength" value="{escape(str(row.get("strength", "medium")))}" required></label>
|
||||
"""
|
||||
return f'<label>Name <input name="name" value="{name}" required></label>'
|
||||
return f"""
|
||||
<label>Name <input name="name" value="{name}" required></label>
|
||||
{classification}
|
||||
"""
|
||||
|
||||
|
||||
def render_element_hidden_fields(row: dict) -> str:
|
||||
@@ -2774,7 +2882,17 @@ def render_element_hidden_fields(row: dict) -> str:
|
||||
|
||||
|
||||
def render_candidate_edit_fields(row: dict) -> str:
|
||||
return f'<label>Name <input name="name" value="{escape(str(row["name"]))}" required></label>'
|
||||
if row.get("item_kind") == "features":
|
||||
return f"""
|
||||
<label>Name <input name="name" value="{escape(str(row["name"]))}" required></label>
|
||||
<label>Type <input name="type" value="{escape(str(row.get("type", row.get("primary_class", ""))))}" required></label>
|
||||
{render_classification_edit_fields(row)}
|
||||
<label>Location <input name="location" value="{escape(str(row.get("location", "")))}"></label>
|
||||
"""
|
||||
return f"""
|
||||
<label>Name <input name="name" value="{escape(str(row["name"]))}" required></label>
|
||||
{render_classification_edit_fields(row)}
|
||||
"""
|
||||
|
||||
|
||||
def render_candidate_hidden_fields(row: dict) -> str:
|
||||
@@ -2784,6 +2902,17 @@ def render_candidate_hidden_fields(row: dict) -> str:
|
||||
)
|
||||
|
||||
|
||||
def render_classification_edit_fields(row: dict) -> str:
|
||||
return f"""
|
||||
<label>Primary class <input name="primary_class" value="{escape(str(row.get("primary_class", "")))}" required></label>
|
||||
<label>Attributes <input name="attributes" value="{escape(attributes_text(row))}" placeholder="Comma-separated"></label>
|
||||
"""
|
||||
|
||||
|
||||
def attributes_text(row: dict) -> str:
|
||||
return ", ".join(str(item) for item in row.get("attributes", []) if str(item))
|
||||
|
||||
|
||||
def render_class_datalist(rows: list[dict]) -> str:
|
||||
classes = sorted({str(row["primary_class"]) for row in rows if row["primary_class"]})
|
||||
options = "".join(
|
||||
@@ -2793,6 +2922,22 @@ def render_class_datalist(rows: list[dict]) -> str:
|
||||
return f'<datalist id="element-classes">{options}</datalist>'
|
||||
|
||||
|
||||
def render_attribute_datalist(rows: list[dict]) -> str:
|
||||
attributes = sorted(
|
||||
{
|
||||
str(attribute)
|
||||
for row in rows
|
||||
for attribute in row.get("attributes", [])
|
||||
if str(attribute)
|
||||
}
|
||||
)
|
||||
options = "".join(
|
||||
f'<option value="{escape(item)}"></option>'
|
||||
for item in attributes
|
||||
)
|
||||
return f'<datalist id="element-attributes">{options}</datalist>'
|
||||
|
||||
|
||||
def render_optional_hidden(name: str, value: int | None) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
@@ -2958,10 +3103,17 @@ def render_candidate_edit_form(
|
||||
f"/{collection}/{candidate['id']}/edit"
|
||||
)
|
||||
confidence = f"{candidate['confidence']:.2f}"
|
||||
extra_fields = ""
|
||||
if collection == "candidate-features":
|
||||
extra_fields = f"""
|
||||
<label>Type <input name="type" value="{escape(candidate['type'])}" required></label>
|
||||
<label>Location <input name="location" value="{escape(candidate.get('location', ''))}"></label>
|
||||
"""
|
||||
return f"""
|
||||
<form class="stack" method="post" action="{action}">
|
||||
<label>Name <input name="name" value="{escape(candidate['name'])}" required></label>
|
||||
<label>Description <textarea name="description" rows="2">{escape(candidate['description'])}</textarea></label>
|
||||
{extra_fields or f'<label>Description <textarea name="description" rows="2">{escape(candidate.get("description", ""))}</textarea></label>'}
|
||||
{render_classification_edit_fields(candidate)}
|
||||
<label>Confidence <input name="confidence" type="number" min="0" max="1" step="0.01" value="{confidence}" required></label>
|
||||
<button class="secondary" type="submit">Save Edit</button>
|
||||
</form>
|
||||
@@ -3012,8 +3164,10 @@ def render_candidate_feature(
|
||||
<span class="pill">ID {feature["id"]}</span>
|
||||
<span class="pill">{escape(feature["status"])}</span>
|
||||
<span class="pill">{escape(feature["type"])}</span>
|
||||
<span class="pill">{escape(feature.get("primary_class", feature["type"]))}</span>
|
||||
<span class="source">{escape(feature["location"])}</span>
|
||||
{render_candidate_reject_form('candidate-features', feature, repository_id, analysis_run_id)}
|
||||
{render_candidate_edit_form('candidate-features', feature, repository_id, analysis_run_id)}
|
||||
{render_candidate_relink_form('candidate-features', feature, repository_id, analysis_run_id, 'target_capability_id', 'Target capability ID')}
|
||||
{render_candidate_merge_form('candidate-features', feature, repository_id, analysis_run_id, 'target_feature_id', 'Merge into feature ID')}
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user