Adding automatic attachment point addition
This commit is contained in:
1 parent
c5d1186b95
commit
87ba8cd169
4 files changed
+175
-1
No files matched your search
@@ -2,7 +2,8 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(uv run *)",
|
||||
"Bash(uv sync *)"
|
||||
"Bash(uv sync *)",
|
||||
"Bash(git stash *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,26 @@ async def get(db: AsyncSession, iso_thread_id: int) -> IsoThread | None:
|
||||
return await db.get(IsoThread, iso_thread_id)
|
||||
|
||||
|
||||
async def get_by_values(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
diameter_mm: float,
|
||||
pitch_mm: float,
|
||||
depth_mm: float | None,
|
||||
) -> IsoThread | None:
|
||||
depth_clause = (
|
||||
IsoThread.depth_mm.is_(None) if depth_mm is None else IsoThread.depth_mm == depth_mm
|
||||
)
|
||||
result = await db.execute(
|
||||
select(IsoThread).where(
|
||||
IsoThread.diameter_mm == diameter_mm,
|
||||
IsoThread.pitch_mm == pitch_mm,
|
||||
depth_clause,
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[IsoThread]:
|
||||
result = await db.execute(
|
||||
select(IsoThread).order_by(IsoThread.id).offset(skip).limit(limit)
|
||||
|
||||
@@ -8,6 +8,28 @@ async def get(db: AsyncSession, unc_thread_id: int) -> UncThread | None:
|
||||
return await db.get(UncThread, unc_thread_id)
|
||||
|
||||
|
||||
async def get_by_values(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
diameter_fraction: str,
|
||||
tpi: int,
|
||||
series: UncSeries,
|
||||
depth_in: float | None,
|
||||
) -> UncThread | None:
|
||||
depth_clause = (
|
||||
UncThread.depth_in.is_(None) if depth_in is None else UncThread.depth_in == depth_in
|
||||
)
|
||||
result = await db.execute(
|
||||
select(UncThread).where(
|
||||
UncThread.diameter_fraction == diameter_fraction,
|
||||
UncThread.tpi == tpi,
|
||||
UncThread.series == series,
|
||||
depth_clause,
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def list_all(db: AsyncSession, skip: int = 0, limit: int = 100) -> list[UncThread]:
|
||||
result = await db.execute(
|
||||
select(UncThread).order_by(UncThread.id).offset(skip).limit(limit)
|
||||
|
||||
@@ -17,20 +17,149 @@ Phase 2 – create entries for new ImportedParts
|
||||
• Type ← first PartType whose name is a substring of the description
|
||||
(longest name wins; skipped if no match)
|
||||
• SubType ← null
|
||||
• Attachment point ← always named "Occular"; one of:
|
||||
- ISO/UNC thread parsed from TBXThreadAttribute (using the
|
||||
matching ProductMaxThreadDepth mm/in if populated), or
|
||||
- StaticConnection from ProductODmm/ProductODin (whichever
|
||||
unit is missing is derived via mm/in conversion) if the
|
||||
thread attribute is absent or unparseable
|
||||
Skipped entirely if none of those fields are usable.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.crud import attachment_point as attachment_point_crud
|
||||
from app.crud import attachment_point_name as attachment_point_name_crud
|
||||
from app.crud import iso_thread as iso_thread_crud
|
||||
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 unc_thread as unc_thread_crud
|
||||
from app.models.associations import augmented_part_imported_parts
|
||||
from app.models.augmented_part import AugmentedPart
|
||||
from app.models.imported_part import ImportedPart
|
||||
from app.models.part_type import PartType
|
||||
from app.models.unc_thread import UncSeries
|
||||
from app.schemas.augmented_part import AugmentedPartSyncResult
|
||||
|
||||
ATTACHMENT_POINT_NAME = "Occular"
|
||||
MM_PER_INCH = 25.4
|
||||
|
||||
_ISO_THREAD_RE = re.compile(
|
||||
r"^M\s*(?P<diameter>\d+(?:\.\d+)?)\s*[xX]\s*(?P<pitch>\d+(?:\.\d+)?)$"
|
||||
)
|
||||
_UNC_THREAD_RE = re.compile(
|
||||
r"^(?P<diameter>\d+(?:\.\d+)?(?:/\d+)?)\s*-\s*(?P<tpi>\d+)"
|
||||
r"\s*(?P<series>UNEF|UNC|UNF|UNS|UN)?$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def _parse_float(value: str | None) -> float | None:
|
||||
if value is None:
|
||||
return None
|
||||
value = value.strip()
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _parse_thread_attribute(value: str) -> tuple[str, dict] | None:
|
||||
"""Parse a TBXThreadAttribute string as an ISO metric or UNC/UNF thread spec."""
|
||||
m = _ISO_THREAD_RE.match(value)
|
||||
if m:
|
||||
return "iso", {
|
||||
"diameter_mm": float(m.group("diameter")),
|
||||
"pitch_mm": float(m.group("pitch")),
|
||||
}
|
||||
|
||||
m = _UNC_THREAD_RE.match(value)
|
||||
if m:
|
||||
# Source data often omits the series letters; UNS ("unified special") is the
|
||||
# correct designation when the diameter/TPI pairing isn't a standard pitch.
|
||||
series = UncSeries((m.group("series") or "UNS").upper())
|
||||
return "unc", {
|
||||
"diameter_fraction": m.group("diameter"),
|
||||
"tpi": int(m.group("tpi")),
|
||||
"series": series,
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def _get_or_create_attachment_point_name(db: AsyncSession, name: str):
|
||||
apn = await attachment_point_name_crud.get_by_name(db, name)
|
||||
if apn is None:
|
||||
apn = await attachment_point_name_crud.create(db, name=name)
|
||||
return apn
|
||||
|
||||
|
||||
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
|
||||
(StaticConnection). Falls through to the OD-based connection if the thread
|
||||
attribute is present but unparseable.
|
||||
"""
|
||||
thread_value = (ip.TBXThreadAttribute or "").strip()
|
||||
parsed = _parse_thread_attribute(thread_value) if thread_value else None
|
||||
|
||||
if parsed is not None:
|
||||
kind, fields = parsed
|
||||
apn = await _get_or_create_attachment_point_name(db, ATTACHMENT_POINT_NAME)
|
||||
|
||||
if kind == "iso":
|
||||
depth_mm = _parse_float(ip.ProductMaxThreadDepthmm)
|
||||
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(
|
||||
db,
|
||||
augmented_part_id=ap.id,
|
||||
attachment_point_name_id=apn.id,
|
||||
iso_thread_id=thread.id,
|
||||
)
|
||||
else:
|
||||
depth_in = _parse_float(ip.ProductMaxThreadDepthin)
|
||||
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(
|
||||
db,
|
||||
augmented_part_id=ap.id,
|
||||
attachment_point_name_id=apn.id,
|
||||
unc_thread_id=thread.id,
|
||||
)
|
||||
return
|
||||
|
||||
od_mm = _parse_float(ip.ProductODmm)
|
||||
od_in = _parse_float(ip.ProductODin)
|
||||
if od_mm is None and od_in is None:
|
||||
return
|
||||
if od_mm is None:
|
||||
od_mm = od_in * MM_PER_INCH
|
||||
elif od_in is None:
|
||||
od_in = od_mm / MM_PER_INCH
|
||||
|
||||
apn = await _get_or_create_attachment_point_name(db, ATTACHMENT_POINT_NAME)
|
||||
sc = await static_connection_crud.get_by_od_mm(db, od_mm)
|
||||
if sc is None:
|
||||
sc = await static_connection_crud.create(db, od_mm=od_mm, od_in=od_in)
|
||||
|
||||
await attachment_point_crud.create(
|
||||
db,
|
||||
augmented_part_id=ap.id,
|
||||
attachment_point_name_id=apn.id,
|
||||
static_connection_id=sc.id,
|
||||
)
|
||||
|
||||
|
||||
async def sync_one(
|
||||
db: AsyncSession,
|
||||
@@ -73,6 +202,7 @@ async def sync_one(
|
||||
imported_part_id=ip.InventoryID,
|
||||
)
|
||||
)
|
||||
await _add_attachment_point(db, ap, ip)
|
||||
await db.commit()
|
||||
return ap
|
||||
|
||||
@@ -196,6 +326,7 @@ async def sync(db: AsyncSession) -> AugmentedPartSyncResult:
|
||||
imported_part_id=ip.InventoryID,
|
||||
)
|
||||
)
|
||||
await _add_attachment_point(db, ap, ip)
|
||||
created += 1
|
||||
|
||||
await db.commit()
|
||||
|
||||
Reference in new issue
Block a user