Adding support for clamp inference

This commit is contained in:
Ian Roddis committed 2026-08-17 21:16:59 -03:00
1 parent ab5315b862
commit 85cb58ef65
2 files changed
+59 -16

No files matched your search

+52 -10
View File
@@ -38,6 +38,7 @@ from app.crud import manufacturer as manufacturer_crud
from app.crud import part_colour as part_colour_crud
from app.crud import part_type as part_type_crud
from app.crud import static_connection as static_connection_crud
from app.crud import clamp_connection as clamp_connection_crud
from app.crud import unc_thread as unc_thread_crud
from app.models.associations import augmented_part_imported_parts
from app.models.augmented_part import AugmentedPart
@@ -70,6 +71,11 @@ _UNC_THREAD_RE = re.compile(
re.IGNORECASE,
)
_CLAMP_RANGE_RE = re.compile(
r"\((?P<min_diameter>\d*\.?\d+)\s*-\s*(?P<max_diameter>\d*\.?\d+)\s*mm\)",
re.IGNORECASE,
)
def _parse_float(value: str | None) -> float | None:
if value is None:
@@ -101,6 +107,13 @@ def _parse_thread_attribute(value: str) -> tuple[str, dict] | None:
"series": series,
}
if m := _CLAMP_RANGE_RE.match(value):
# Source data often omits the series letters; UNS ("unified special") is the
# correct designation when the diameter/TPI pairing isn't a standard pitch.
return "clamp", {
"min_diametere": float(m.group("min_diameter")),
"max_diameter": float(m.group("max_diameter")),
}
return None
@@ -111,7 +124,9 @@ async def _get_or_create_attachment_point_name(db: AsyncSession, name: str):
return apn
async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: ImportedPart) -> None:
async def _add_attachment_point(
db: AsyncSession, ap: AugmentedPart, ip: ImportedPart
) -> None:
"""Infer and create an attachment point for a newly-synced AugmentedPart.
Preference order: TBXThreadAttribute (ISO/UNC thread) then ProductODmm/in
@@ -131,7 +146,9 @@ async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: Importe
if kind == "iso":
depth_mm = _parse_float(ip.ProductMaxThreadDepthmm)
thread = await iso_thread_crud.get_by_values(db, depth_mm=depth_mm, **fields)
thread = await iso_thread_crud.get_by_values(
db, depth_mm=depth_mm, **fields
)
if thread is None:
thread = await iso_thread_crud.create(db, depth_mm=depth_mm, **fields)
await attachment_point_crud.create(
@@ -140,9 +157,12 @@ async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: Importe
attachment_point_name_id=apn.id,
iso_thread_id=thread.id,
)
return
elif kind == "unc":
depth_in = _parse_float(ip.ProductMaxThreadDepthin)
thread = await unc_thread_crud.get_by_values(db, depth_in=depth_in, **fields)
thread = await unc_thread_crud.get_by_values(
db, depth_in=depth_in, **fields
)
if thread is None:
thread = await unc_thread_crud.create(db, depth_in=depth_in, **fields)
await attachment_point_crud.create(
@@ -151,9 +171,21 @@ async def _add_attachment_point(db: AsyncSession, ap: AugmentedPart, ip: Importe
attachment_point_name_id=apn.id,
unc_thread_id=thread.id,
)
else:
pass
return
elif kind == "clamp":
clamp = await clamp_connection_crud.get_by_values(db, **fields)
if clamp is None:
clamp = await clamp_connection_crud.create(db, **fields)
await attachment_point_crud.create(
db,
augmented_part_id=ap.id,
attachment_point_name_id=apn.id,
clamp_connection_id=clamp.id,
)
return
else:
# Unable to determine
pass
od_mm = _parse_float(ip.ProductODmm)
od_in = _parse_float(ip.ProductODin)
@@ -240,7 +272,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# ------------------------------------------------------------------ phase 1
# IDs referenced in existing associations
result = await db.execute(select(augmented_part_imported_parts.c.imported_part_id).distinct())
result = await db.execute(
select(augmented_part_imported_parts.c.imported_part_id).distinct()
)
all_linked_ids: set[str] = {row[0] for row in result.all()}
# IDs that still exist in imported_parts
@@ -254,7 +288,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# Which AugmentedParts are affected?
result = await db.execute(
select(augmented_part_imported_parts.c.augmented_part_id).where(
augmented_part_imported_parts.c.imported_part_id.in_(orphaned_import_ids)
augmented_part_imported_parts.c.imported_part_id.in_(
orphaned_import_ids
)
)
)
orphaned_ap_ids: set[int] = {row[0] for row in result.all()}
@@ -262,7 +298,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# Remove the stale association rows
await db.execute(
augmented_part_imported_parts.delete().where(
augmented_part_imported_parts.c.imported_part_id.in_(orphaned_import_ids)
augmented_part_imported_parts.c.imported_part_id.in_(
orphaned_import_ids
)
)
)
await db.flush()
@@ -285,7 +323,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
# ------------------------------------------------------------------ phase 2
# Re-query linked IDs after phase 1 cleanup
result = await db.execute(select(augmented_part_imported_parts.c.imported_part_id).distinct())
result = await db.execute(
select(augmented_part_imported_parts.c.imported_part_id).distinct()
)
now_linked_ids: set[str] = {row[0] for row in result.all()}
unlinked_ids = current_ids - now_linked_ids
@@ -293,7 +333,9 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
skipped = 0
if not unlinked_ids:
return AugmentedPartSyncResult(created=created, deleted=deleted, skipped=skipped)
return AugmentedPartSyncResult(
created=created, deleted=deleted, skipped=skipped
)
result = await db.execute(
select(ImportedPart).where(ImportedPart.InventoryID.in_(unlinked_ids))
+7 -6
View File
@@ -2,14 +2,9 @@
- [ ] Add an `archived` attribute that indicates the part should not show up in default searches
- [ ] Admins and users with permissions can opt into seeing archived parts
- [ ] Add bulk "mark archived"
- [ ] AugmentedPart Details
- [x] Title should be the SKU
- [ ] Add new inference for attachment point "Clamp"
- [ ] Appears as `(44.5-47.5mm)` in the description
- [ ] Scope Import
- [ ] Test scopes with explicit fits populated
- [ ] Attachment Points
- [ ] Remove objective / occular labels and matching logic
- [ ] Dashboard
- [ ] Make this more useful.
- [ ] Number of
@@ -17,6 +12,10 @@
- [ ] Skipped parts from last Sync
- [ ] Handy buttons to run the import / sync / recompute actions
- [x] AugmentedPart Details
- [x] Title should be the SKU
- [x] Add new inference for attachment point "Clamp"
- [x] Appears as `(44.5-47.5mm)` in the description
- [x] Better layout for Scopes page
- [x] Layout is too wide, requiring side-scrolling
@@ -28,5 +27,7 @@
- [x] Part Import
- [x] Example query of `ZC5000` should return multiple parts
- [x] Filter text should search SKU and description
- [x] Attachment Points
- [x] Remove objective / occular labels and matching logic