computers: declare subordinate devices instead of coding each one
A PC that drives a device which is its own asset had been implemented twice. METROLOGY_TOOL_MAP covered CMM, Keyence, Genspect and wax-trace, minting a measuring_tool. A separate path keyed on one hardcoded pc-type minted a Part Marker machine and filed it under its operation. Both create a device, link the PC with controls, and archive that link when the PC is re-imaged: one mechanism with different nouns, written out twice because the second case arrived later. That is the same trap as the site literals in ADR-015 - a pattern implemented per instance rather than declared - and it has a known next occurrence. Part markers already share operation numbers, and any site with two marking lasers or two wax-trace units on one number needs identical treatment. One SUBORDINATE_DEVICE_MAP now declares asset type, type name, naming suffix, whether the device files partof the operation, and the relationship label. The labels are unchanged per case on purpose: those values are in the production database and only rows carrying them are archived by a collector push. A site overrides or adds an entry through subordinatedevice_<pctype> settings, per ADR-015, so the next case needs no code. A malformed override falls back to the default rather than failing the push, because a bad setting must not stop a bay reporting its inventory. metrology_tool_for stays as a shim over the same map: filters.py and the older tests read it, and unifying must not change what it returns. A test pins that. Also adds flask relationships check-shared-machines, which finds the next 0615 rather than waiting for someone to notice duplicate backups. Several devices legitimately sharing a number and two PCs mis-numbered at imaging look the same from outside; the difference is whether child assets exist, so that is what it reports. Read-only.
This commit is contained in:
@@ -1217,3 +1217,88 @@ def csv_import(path, directory, tablename, commit):
|
||||
'Looks good: %d would be created, %d updated.' % (total_new, total_upd),
|
||||
fg='green', bold=True))
|
||||
click.echo('Run again with --commit to apply.')
|
||||
|
||||
|
||||
@relationships_cli.command('check-shared-machines')
|
||||
@with_appcontext
|
||||
def check_shared_machines():
|
||||
"""Find machine numbers that more than one PC reports against.
|
||||
|
||||
Two very different situations look identical from the outside, and both
|
||||
were found the hard way rather than by asking:
|
||||
|
||||
LEGITIMATE - several devices genuinely share one number. Part markers do:
|
||||
0613, 0615 and WJPRT each carry more than one, and their configurations
|
||||
differ by COM port. Modelled correctly, each device is its own asset filed
|
||||
`partof` the operation, so the operation has CHILD ASSETS.
|
||||
|
||||
A FAULT - two PCs carrying the same machine number, usually a mistake at
|
||||
imaging. Nothing is filed under the operation, the PCs contest one link,
|
||||
and whichever reported last appears to own the machine.
|
||||
|
||||
The difference is whether child assets exist, which is exactly what this
|
||||
reports. Read-only.
|
||||
"""
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models import Asset, AssetRelationship, RelationshipType
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
controls = RelationshipType.query.filter_by(relationshiptype='controls').first()
|
||||
partof = RelationshipType.query.filter_by(relationshiptype='partof').first()
|
||||
if not controls:
|
||||
click.echo(click.style("No 'controls' relationship type; "
|
||||
'run flask seed reference-data.', fg='yellow'))
|
||||
return
|
||||
|
||||
pcasset = aliased(Asset)
|
||||
machineasset = aliased(Asset)
|
||||
|
||||
# Every active collector-made PC -> machine link, grouped by machine.
|
||||
rows = (db.session.query(machineasset.assetid, machineasset.assetnumber,
|
||||
pcasset.assetnumber)
|
||||
.select_from(AssetRelationship)
|
||||
.join(pcasset, AssetRelationship.sourceassetid == pcasset.assetid)
|
||||
.join(machineasset, AssetRelationship.targetassetid == machineasset.assetid)
|
||||
.filter(AssetRelationship.relationshiptypeid == controls.relationshiptypeid,
|
||||
AssetRelationship.label == 'collector:machine',
|
||||
AssetRelationship.isactive.is_(True))
|
||||
.all())
|
||||
|
||||
bymachine = {}
|
||||
for assetid, machinenumber, pcnumber in rows:
|
||||
bymachine.setdefault((assetid, machinenumber), []).append(pcnumber)
|
||||
|
||||
shared = {k: v for k, v in bymachine.items() if len(v) > 1}
|
||||
if not shared:
|
||||
click.echo(click.style('No machine number is claimed by more than one PC.',
|
||||
fg='green'))
|
||||
return
|
||||
|
||||
faults = 0
|
||||
for (assetid, machinenumber), pcs in sorted(shared.items(), key=lambda kv: kv[0][1] or ''):
|
||||
children = 0
|
||||
if partof:
|
||||
children = (AssetRelationship.query
|
||||
.filter_by(targetassetid=assetid,
|
||||
relationshiptypeid=partof.relationshiptypeid,
|
||||
isactive=True)
|
||||
.count())
|
||||
if children:
|
||||
click.echo(' {:<10} {} PCs, {} child asset(s) - modelled'.format(
|
||||
machinenumber, len(pcs), children))
|
||||
else:
|
||||
faults += 1
|
||||
click.echo(click.style(
|
||||
' {:<10} {} PCs, NO child assets - {}'.format(
|
||||
machinenumber, len(pcs), ', '.join(sorted(pcs))), fg='yellow'))
|
||||
|
||||
click.echo()
|
||||
if faults:
|
||||
click.echo(click.style(
|
||||
'{} machine number(s) claimed by several PCs with nothing filed '
|
||||
'under them.'.format(faults), fg='yellow', bold=True))
|
||||
click.echo('Either the PCs are mis-numbered - fix that on the PC - or the '
|
||||
'device type needs an entry in SUBORDINATE_DEVICE_MAP so each '
|
||||
'device becomes its own asset.')
|
||||
else:
|
||||
click.echo(click.style('Every shared number has child assets.', fg='green'))
|
||||
|
||||
Reference in New Issue
Block a user