WJ import loader: route LocationOnly by the islocationonly bit, not machinetypeid
Some checks failed
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 8s

Of the 158 machinetypeid=1 rows, only 24 carry the islocationonly bit (real
named areas: DT Office, IT Closet, Materials, ...). The other 134 are active,
modelled shop machines just left untyped - routing all 158 to Locations dropped
those 134 real assets. Route on the bit instead; the 134 untyped rows import as
machines with a null subtype (machinetypeid=1 is not a real machine subtype, so
catalog skips seeding one).

Also process asset routes in richness order (computer > measuringtool > network
> machine) so on a duplicate machinenumber the PC - which carries installs + IP
a bare untyped machine does not - wins first-come.

Result on the scratch target: 933 assets (computer 663, machine 76, network 58,
measuringtool 136), 24 locations (was mis-routing 158), installs 850 (was 653 -
PCs no longer lose their numbers to bare machines), warranties 464, comms 461.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-13 11:55:25 -04:00
parent 6d79e469fa
commit 40a89b360a

View File

@@ -54,15 +54,23 @@ MEASURING_MTYPES = {3, 5, 8, 23, 47, 48} # CMM, Wax, Eddy, Measuring, Inspe
MEASURING_PCTYPES = {5, 6, 7, 8} # PC subtypes: CMM, Wax/Trace, Keyence, Genspect
COMPUTER_MTYPES = {33, 20} # PC, Server
NETWORK_MTYPES = {16, 17, 18, 19, 46} # Access, IDF, Camera, Switch, Firewall
LOCATION_MTYPES = {1} # LocationOnly -> core Locations
SKIP_MTYPES = {15, 44} # Printer (printers table), USB (cmmc source)
# LocationOnly is the islocationonly BIT, not machinetypeid=1: of the 158 type-1
# rows only 24 carry the bit (real named areas). The other 134 are active,
# modelled shop machines just left untyped - they must become assets, not
# Locations.
def _truthy_bit(value):
"""MySQL bit(1) comes back from pymysql as bytes b'\\x00'/b'\\x01'."""
return value not in (0, None, False, b'\x00', b'', '0', '')
# classic controllertypeid -> (vendor, model) hand split; 1=TBD dropped
CONTROLLER_SPLIT = {2: ('Fanuc', '31i-MB'), 6: ('Fanuc', None),
7: ('Okuma', None), 8: ('Makino', None)}
def _route(machinetypeid, pctypeid):
if machinetypeid in LOCATION_MTYPES:
def _route(machinetypeid, pctypeid, islocationonly=False):
if islocationonly:
return 'location'
if machinetypeid in SKIP_MTYPES:
return 'skip'
@@ -74,7 +82,7 @@ def _route(machinetypeid, pctypeid):
if machinetypeid == 33 and pctypeid in MEASURING_PCTYPES:
return 'measuringtool'
return 'computer'
return 'machine'
return 'machine' # includes the 134 untyped (machinetypeid=1) shop machines
def _modeltype_category(machinetypeid):
@@ -153,9 +161,11 @@ def stage_catalog(h):
'SELECT machinetypeid, machinetype FROM machinetypes')
for mt in mtypes:
name = (mt['machinetype'] or '').strip()
if not name:
continue
mtid = mt['machinetypeid']
# machinetypeid=1 (LocationOnly) is not a real machine subtype - the
# untyped shop machines that carry it import with a null subtype.
if not name or mtid == 1:
continue
# modeltype (types the models catalog)
modeltypeid = _upsert(h, '/api/modeltypes',
{'modeltype': name, 'category': _modeltype_category(mtid)},
@@ -236,8 +246,15 @@ def stage_assets(h):
if isinstance(row, dict) and row.get('assetnumber'):
seen_assetnumbers.add(row['assetnumber'].strip().lower())
# On a duplicate machinenumber, first-wins - so process the richer asset
# types first (a PC carries installs/IP a bare untyped machine does not).
route_rank = {'computer': 0, 'measuringtool': 1, 'network': 2, 'machine': 3}
machines = sorted(machines, key=lambda m: route_rank.get(
_route(m['machinetypeid'], m['pctypeid'], _truthy_bit(m['islocationonly'])), 4))
for m in machines:
route = _route(m['machinetypeid'], m['pctypeid'])
route = _route(m['machinetypeid'], m['pctypeid'],
_truthy_bit(m['islocationonly']))
if route == 'location':
counts['skip_location'] += 1
continue