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
|
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):
|
def stage_communications(h):
|
||||||
"""Fold each asset's primary IP from the source communications table. There
|
"""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
|
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.
|
Edges whose endpoints did not become assets (locations/skipped/dups) drop.
|
||||||
Dedups on the (source, target, type) triple."""
|
Dedups on the (source, target, type) triple."""
|
||||||
# Seed relationship types (case-insensitive collation folds Controls onto
|
# 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',
|
for t in h.source.rows('shopdb_src',
|
||||||
'SELECT relationshiptypeid, relationshiptype FROM relationshiptypes WHERE isactive=1'):
|
'SELECT relationshiptypeid, relationshiptype FROM relationshiptypes WHERE isactive=1'):
|
||||||
name = (t['relationshiptype'] or '').strip()
|
name = (t['relationshiptype'] or '').strip()
|
||||||
if not name:
|
if not name:
|
||||||
continue
|
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},
|
newid = _upsert(h, '/api/assets/relationshiptypes', {'relationshiptype': name},
|
||||||
'relationshiptype', 'relationshiptypeid', '/api/assets/relationshiptypes')
|
'relationshiptype', 'relationshiptypeid', '/api/assets/relationshiptypes')
|
||||||
if newid:
|
if newid:
|
||||||
h.ids.put('relationshiptype', t['relationshiptypeid'], newid)
|
h.ids.put('relationshiptype', t['relationshiptypeid'], newid)
|
||||||
|
if lname == 'controls':
|
||||||
|
controls_newid = newid
|
||||||
|
|
||||||
seen = set()
|
seen = set()
|
||||||
made = dropped = 0
|
made = dropped = 0
|
||||||
@@ -536,7 +576,12 @@ def stage_relationships(h):
|
|||||||
'FROM machinerelationships WHERE isactive=1'):
|
'FROM machinerelationships WHERE isactive=1'):
|
||||||
source = h.ids.get('asset', r['machineid'])
|
source = h.ids.get('asset', r['machineid'])
|
||||||
target = h.ids.get('asset', r['related_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:
|
if not (source and target and typeid) or source == target:
|
||||||
dropped += 1
|
dropped += 1
|
||||||
continue
|
continue
|
||||||
@@ -656,6 +701,7 @@ STAGES = {
|
|||||||
'catalog': stage_catalog,
|
'catalog': stage_catalog,
|
||||||
'assets': stage_assets,
|
'assets': stage_assets,
|
||||||
'locations': stage_locations,
|
'locations': stage_locations,
|
||||||
|
'printers': stage_printers,
|
||||||
'communications': stage_communications,
|
'communications': stage_communications,
|
||||||
'applications': stage_applications,
|
'applications': stage_applications,
|
||||||
'warranties': stage_warranties,
|
'warranties': stage_warranties,
|
||||||
@@ -672,9 +718,9 @@ def main():
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--stages',
|
'--stages',
|
||||||
default='reference,employees,catalog,assets,locations,communications,'
|
default='reference,employees,catalog,assets,locations,printers,'
|
||||||
'applications,warranties,notifications,knowledgebase,'
|
'communications,applications,warranties,notifications,'
|
||||||
'relationships,subnets,usb,verify',
|
'knowledgebase,relationships,subnets,usb,verify',
|
||||||
help='comma list of stages to run')
|
help='comma list of stages to run')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user