WJ import loader: printers stage + Controlled-By relationship reversal
printers: printers come from the printers TABLE (not the machines hub), so a
dedicated stage - assetnumber synthesized PRN-{printerid}, IP folded via the
create route, host machineid resolved to a location when it is a LocationOnly
row. Skips inactive. 50 of 56 imported.
relationships: a "Controlled By" edge now flips to the forward Controls
direction (source/target swapped, mapped to the Controls type) instead of
importing a redundant inverse type.
Final fresh full run: 983 assets (933 machines-hub + 50 printers), zero endpoint
errors, all 15 stages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -319,6 +319,36 @@ def stage_assets(h):
|
||||
return counts
|
||||
|
||||
|
||||
def stage_printers(h):
|
||||
"""Printers come from the printers TABLE (not the machines hub). assetnumber
|
||||
is synthesized PRN-{printerid} (no machinenumber). The printer's IP folds
|
||||
into a Communication via the create route. Location resolves from the host
|
||||
machineid when it is one of the LocationOnly rows. Skips inactive."""
|
||||
made = 0
|
||||
for p in h.source.rows('shopdb_src',
|
||||
'SELECT printerid, modelid, printerwindowsname, printercsfname, '
|
||||
'serialnumber, fqdn, ipaddress, machineid, maptop, mapleft, iscsf, '
|
||||
'installpath, printernotes, printerpin FROM printers WHERE isactive=1'):
|
||||
payload = {
|
||||
'assetnumber': f"PRN-{p['printerid']}",
|
||||
'name': (p['printerwindowsname'] or '').strip() or None,
|
||||
'serialnumber': (p['serialnumber'] or '').strip() or None,
|
||||
'hostname': (p['fqdn'] or '').strip() or None,
|
||||
'windowsname': (p['printerwindowsname'] or '').strip() or None,
|
||||
'sharename': (p['printercsfname'] or '').strip() or None,
|
||||
'ipaddress': (p['ipaddress'] or '').strip() or None,
|
||||
'iscsf': _truthy_bit(p['iscsf']),
|
||||
'installpath': p['installpath'], 'pin': p['printerpin'],
|
||||
'notes': p['printernotes'], 'mapx': p['mapleft'], 'mapy': p['maptop'],
|
||||
'modelnumberid': h.ids.get('model', p['modelid']),
|
||||
'locationid': h.ids.get('location', p['machineid']),
|
||||
}
|
||||
status, _ = h.post('/api/printers', payload)
|
||||
if status in (200, 201):
|
||||
made += 1
|
||||
return {'printers': made}
|
||||
|
||||
|
||||
def stage_communications(h):
|
||||
"""Fold each asset's primary IP from the source communications table. There
|
||||
is no bulk-communications endpoint, so this is one of the plan's documented
|
||||
@@ -518,16 +548,26 @@ def stage_relationships(h):
|
||||
Edges whose endpoints did not become assets (locations/skipped/dups) drop.
|
||||
Dedups on the (source, target, type) triple."""
|
||||
# Seed relationship types (case-insensitive collation folds Controls onto
|
||||
# the seeded 'controls', etc), capturing the crosswalk.
|
||||
# the seeded 'controls', etc), capturing the crosswalk. A "Controlled By"
|
||||
# type is the reverse of "Controls" - flag it so edges get flipped to the
|
||||
# forward direction instead of importing a redundant inverse type.
|
||||
reverse_typeids = set()
|
||||
controls_newid = None
|
||||
for t in h.source.rows('shopdb_src',
|
||||
'SELECT relationshiptypeid, relationshiptype FROM relationshiptypes WHERE isactive=1'):
|
||||
name = (t['relationshiptype'] or '').strip()
|
||||
if not name:
|
||||
continue
|
||||
lname = name.lower()
|
||||
if 'controlled by' in lname:
|
||||
reverse_typeids.add(t['relationshiptypeid'])
|
||||
continue # do not create an inverse type; edges map to Controls
|
||||
newid = _upsert(h, '/api/assets/relationshiptypes', {'relationshiptype': name},
|
||||
'relationshiptype', 'relationshiptypeid', '/api/assets/relationshiptypes')
|
||||
if newid:
|
||||
h.ids.put('relationshiptype', t['relationshiptypeid'], newid)
|
||||
if lname == 'controls':
|
||||
controls_newid = newid
|
||||
|
||||
seen = set()
|
||||
made = dropped = 0
|
||||
@@ -536,7 +576,12 @@ def stage_relationships(h):
|
||||
'FROM machinerelationships WHERE isactive=1'):
|
||||
source = h.ids.get('asset', r['machineid'])
|
||||
target = h.ids.get('asset', r['related_machineid'])
|
||||
typeid = h.ids.get('relationshiptype', r['relationshiptypeid'])
|
||||
# "Controlled By" edges flip to the forward Controls direction.
|
||||
if r['relationshiptypeid'] in reverse_typeids:
|
||||
source, target = target, source
|
||||
typeid = controls_newid
|
||||
else:
|
||||
typeid = h.ids.get('relationshiptype', r['relationshiptypeid'])
|
||||
if not (source and target and typeid) or source == target:
|
||||
dropped += 1
|
||||
continue
|
||||
@@ -656,6 +701,7 @@ STAGES = {
|
||||
'catalog': stage_catalog,
|
||||
'assets': stage_assets,
|
||||
'locations': stage_locations,
|
||||
'printers': stage_printers,
|
||||
'communications': stage_communications,
|
||||
'applications': stage_applications,
|
||||
'warranties': stage_warranties,
|
||||
@@ -672,9 +718,9 @@ def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--stages',
|
||||
default='reference,employees,catalog,assets,locations,communications,'
|
||||
'applications,warranties,notifications,knowledgebase,'
|
||||
'relationships,subnets,usb,verify',
|
||||
default='reference,employees,catalog,assets,locations,printers,'
|
||||
'communications,applications,warranties,notifications,'
|
||||
'knowledgebase,relationships,subnets,usb,verify',
|
||||
help='comma list of stages to run')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user