Adding top-level Scopes table
This commit is contained in:
1 parent
bc8b67d585
commit
3febc15a8f
11 files changed
+470
-1
No files matched your search
@@ -0,0 +1,114 @@
|
||||
"""Assemble the scope-centric fit guide.
|
||||
|
||||
Builds the data for the top-level ``/scopes`` page: one row per augmented part
|
||||
of part type "Scope", one column per non-Scope part type, where each cell lists
|
||||
the parts of that type that fit the row's scope.
|
||||
"""
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.crud import augmented_part as augmented_part_crud
|
||||
from app.crud import fit as fit_crud
|
||||
from app.crud import part_type as part_type_crud
|
||||
from app.crud import scope_info as scope_info_crud
|
||||
from app.models.fit import FitLabel
|
||||
|
||||
SCOPE_TYPE_NAME = "Scope"
|
||||
|
||||
# "exact" outranks "possible" when the same part fits a scope via several fits.
|
||||
_LABEL_RANK = {FitLabel.EXACT.value: 2, FitLabel.POSSIBLE.value: 1}
|
||||
|
||||
|
||||
async def build(db: AsyncSession) -> dict:
|
||||
"""Return the context for ``scopes.html``.
|
||||
|
||||
Shape::
|
||||
|
||||
{
|
||||
"column_types": [PartType, ...], # non-Scope, ordered by name
|
||||
"rows": [
|
||||
{
|
||||
"scope": AugmentedPart,
|
||||
"info": ScopeInfo | None,
|
||||
"cells": {part_type_id: [{"part": AugmentedPart, "label": str}]},
|
||||
},
|
||||
...,
|
||||
],
|
||||
}
|
||||
"""
|
||||
part_types = await part_type_crud.list_all(db, limit=1000)
|
||||
scope_type = next(
|
||||
(pt for pt in part_types if pt.name == SCOPE_TYPE_NAME), None
|
||||
)
|
||||
column_types = [
|
||||
pt for pt in part_types if scope_type is None or pt.id != scope_type.id
|
||||
]
|
||||
|
||||
if scope_type is None:
|
||||
return {"column_types": column_types, "rows": []}
|
||||
|
||||
scopes = await augmented_part_crud.list_by_part_type(db, scope_type.id)
|
||||
scope_ids = {s.id for s in scopes}
|
||||
if not scope_ids:
|
||||
return {"column_types": column_types, "rows": []}
|
||||
|
||||
fits = await fit_crud.list_for_parts(
|
||||
db, list(scope_ids), exclude_label=FitLabel.NONE
|
||||
)
|
||||
|
||||
# Resolve the "other" (non-scope) part on each fit in a single batched query.
|
||||
other_ids: set[int] = set()
|
||||
for fit in fits:
|
||||
scope_id, other_id = _scope_and_other(fit, scope_ids)
|
||||
if scope_id is not None and other_id not in scope_ids:
|
||||
other_ids.add(other_id)
|
||||
other_parts = {
|
||||
p.id: p for p in await augmented_part_crud.get_many(db, list(other_ids))
|
||||
}
|
||||
|
||||
infos = {
|
||||
info.augmented_part_id: info
|
||||
for info in await scope_info_crud.list_by_augmented_parts(db, list(scope_ids))
|
||||
}
|
||||
|
||||
# cell_map[scope_id][type_id][other_part_id] -> {"part", "label"}, dedup'd so a
|
||||
# part appears once per cell, keeping its strongest fit label.
|
||||
cell_map: dict[int, dict[int, dict[int, dict]]] = {
|
||||
sid: {} for sid in scope_ids
|
||||
}
|
||||
for fit in fits:
|
||||
scope_id, other_id = _scope_and_other(fit, scope_ids)
|
||||
if scope_id is None:
|
||||
continue
|
||||
other = other_parts.get(other_id)
|
||||
if other is None or other.part_type_id == scope_type.id:
|
||||
continue # scope-to-scope fits have no column
|
||||
by_type = cell_map[scope_id].setdefault(other.part_type_id, {})
|
||||
existing = by_type.get(other.id)
|
||||
label = fit.label.value
|
||||
if existing is None or _LABEL_RANK[label] > _LABEL_RANK[existing["label"]]:
|
||||
by_type[other.id] = {"part": other, "label": label}
|
||||
|
||||
rows = []
|
||||
for scope in scopes:
|
||||
cells = {
|
||||
type_id: sorted(
|
||||
entries.values(), key=lambda e: e["part"].description.lower()
|
||||
)
|
||||
for type_id, entries in cell_map[scope.id].items()
|
||||
}
|
||||
rows.append(
|
||||
{"scope": scope, "info": infos.get(scope.id), "cells": cells}
|
||||
)
|
||||
|
||||
return {"column_types": column_types, "rows": rows}
|
||||
|
||||
|
||||
def _scope_and_other(fit, scope_ids: set[int]) -> tuple[int | None, int]:
|
||||
"""Return (scope_part_id, other_part_id) for a fit, or (None, _) if neither
|
||||
side is a scope. When both sides are scopes, side A is treated as the scope."""
|
||||
if fit.augmented_part_a_id in scope_ids:
|
||||
return fit.augmented_part_a_id, fit.augmented_part_b_id
|
||||
if fit.augmented_part_b_id in scope_ids:
|
||||
return fit.augmented_part_b_id, fit.augmented_part_a_id
|
||||
return None, fit.augmented_part_b_id
|
||||
Reference in new issue
Block a user