Help using attribute rules to copy/paste a row from one feature class to another on change

499
7
01-11-2024 10:53 AM
VanessaSimps
Occasional Contributor III

Hello all- I am working to set up our attribute rules. I would like to create a rule that will copy/paste an edited row of a feature class to another feature class when there is a status change from active to inactive. this is an example of what I think the flow would look like: 

VanessaSimps_0-1704996641923.png

I did find a couple of examples where you can update a related table when a feature's attribute is changed, but haven't been able to find anything where I can trigger a copy/paste from one feature to another. 

Edit another feature class with a calculation rule.

 

 

var fsAddress = FeatureSetByName($datastore, "Address_pnts", ["globalid"], false)
var fsListAddpnts = Intersects(fsAddress, $feature)
var AddList = []
var counter = 0
var noAddress = Count(fsListAddpnts)
if (noAddress > 0) {
    for (var address in fsListAddpnts) {
        AddList[counter] = {
            'globalid': address.globalid,
            'attributes': {
                'add_district_name': $feature.DistrictName
            }
        }
        counter++
    }
    return {
        'result': noAddress + ' addresses found in the district.',
        'edit': [{
            'className': 'Address_pnts',
            'updates': AddList
        }]
    }
} else {
    return 'No address points in district.'
}

 

 

 
And this: 
from this thread
 

 

 

return {
'edit': [{
'className': 'RetiredData',
'adds': [{
'attributes': {'ID': $feature.ID
},
'geometry': Geometry($feature)
}]
}]
}

//note ID is the name of a text attribute.

 

 

I feel like this second code block is close to what I want, but not sure if it will only work if I am using Utility Network? (we are not at this time). 

0 Kudos
7 Replies
VanessaSimps
Occasional Contributor III

I found this blog post and it is almost exactly what I want to do, but I do not need to use a buffer, I need to filter by attribute value. 

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

We just finish a tool that creates an attribute that does exactly what you want.  

Download the toolbox at the end of this post(Note only works with the UN at the moment, but the result AR could be made to work with non UN classes)

- Open Pro, navigate to the toolbox and expand Attribute Rules

- Open Create Abandon Classs

- Select the classes you wish to abandon/retire features from

- Specify the field and the value that will trigger the movement/abandonment

- This creates an AR on the selected classes and creates the class to copy the result to

- You cannot delete the updated feature, as the AR fires in the middle of an update edit, and deleting the features causes issue in the database

- We use a batch calculate AR on the abandon classes to delete the inactive rows in the source

https://github.com/Esri/Utility-Data-Management-Support-Tools/tree/Preview3.1.2

 

0 Kudos
VanessaSimps
Occasional Contributor III

Thank you @MikeMillerGIS I will need to look at this. We are not using UN at this time so I will probably have to do some finagling. I appreciate the help.

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

Here is the code to copy the feature

 

Expects($feature, '*');
var source_et_fields = ['created_user', 'created_date', 'last_edited_user', 'last_edited_date'];
var assoc_et_fields = ['CREATOR', 'CREATIONDATE', 'UPDATEDBY', 'LASTUPDATE'];
var system_prefix = 'orig';
var assoc_fs = FeatureSetByName($datastore, 'main.UN_6_Associations', ['*'], false);

// The field map defines the source to target fields, if this is an empty dict, it will be calculated on the fly
// for performance reasons, you may want to fill this out, but it would have to be maintained as you update your schema
// This code can generate the static field map and display it in the console;
var source_target_field_map = {};
var assoc_field_map = {};

// Feature must be out of service and abandoned
var process_when = {'Lifecyclestatus': [0]};

// Once a feature is abandon, we only want to add an abandon row if the values that we are monitoring changed.
var one_field_did_change = False;
for (var field in process_when) {
    var field_valid = false;
    for (var i in process_when[field]) {
        var val = process_when[field][i];
        if (val == null && IsEmpty($feature[field]) ) {
            if ($originalFeature[field] != $feature[field]){
               one_field_did_change = true;
            }
            field_valid = true;
            break;
        } else if ($feature[field] == val || Text($feature[field]) == val) {
            if ($originalFeature[field] != $feature[field]){
               one_field_did_change = true;
            }
            field_valid = true;
            break;
        }
    }
    if (!field_valid){
        return
    }
}
if (!one_field_did_change){
   return
}

function IsEmptyButBetter(data) {
    if (IsEmpty(data)) return true;
    for (var x in data) return false;
    return true;
}

function build_field_map(source_schema, target_schema, system_fields, system_field_map_prefix) {
    console('Field Map');
    console('------------------------------------');
    var source_lookup = [];
    var target_lookup = [];
    var field_map_local = {};

    var source_fields = source_schema['fields'];
    // Add the GlobalID and ObjectID fields to the list of ET fields for all the system fields
    var local_source_et_fields = [];
    for (var i in system_fields) {
        Push(local_source_et_fields, Lower(system_fields[i]));
    }
    Push(local_source_et_fields, Lower(source_schema['objectIdField']));
    Push(local_source_et_fields, Lower(source_schema['globalIdField']));

    for (var i in source_fields) {
        var field_info = source_fields[i];
        var field_name = Lower(field_info['name']);
        // Do not store the oid, globalid or ET info in the lookup, will be handled separately
        if (Includes(local_source_et_fields, field_name)) {
            continue;
        }
        Push(source_lookup, field_name);
    }

    var target_fields = target_schema['fields'];
    for (var i in target_fields) {
        var field_info = target_fields[i];
        // We cannot map input to non editable target fields, such as shape_length, validationstatus
        if (!field_info['editable']){
            continue
        }
        var field_name = Lower(field_info['name']);
        Push(target_lookup, field_name);
        if (Includes(source_lookup, field_name)) {
            console(`'${field_name}': '${field_name}',`);
            field_map_local[field_name] = field_name;
        }

    }
    if (system_field_map_prefix == '' || system_field_map_prefix == null) {
        console('End Field Map');
        console('------------------------------------');
        console(' ');
        return field_map_local;
    }
    for (var i in local_source_et_fields) {
        var field_name = local_source_et_fields[i];
        var target_field = Lower(`${system_field_map_prefix}_${field_name}`);
        if (Includes(target_lookup, target_field)) {
            console(`'${field_name}': '${target_field}',`);
            field_map_local[field_name] = target_field;
        }
    }
    console('End Field Map');
    console('------------------------------------');
    console(' ');
    return field_map_local;
}

// If the field map was not defined, use the schema to build a dict of source to target field
if (IsEmptyButBetter(source_target_field_map)) {
    var target_schema = Schema(FeatureSetByName($datastore, 'main.ABElectricDistributionDevice', ['*'], false));
    var source_schema = Schema($feature);
    source_target_field_map = build_field_map(source_schema, target_schema, source_et_fields, system_prefix);
}

if (IsEmptyButBetter(assoc_field_map)) {
    var target_schema = Schema(assoc_fs);
    var source_schema = Schema(FeatureSetByName($datastore, 'main.UN_6_Associations', ['*'], false));
    assoc_field_map = build_field_map(source_schema, target_schema, assoc_et_fields, system_prefix);
}

// Use the field map to copy the attributes from the current feature to the abandon layer
var target_atts = {};
for (var source_field in source_target_field_map) {
    target_atts[source_target_field_map[source_field]] = $feature[source_field];
}
// Create the return edits dict.  Add Geometry for FeatureClasses
var abandon_row = {"attributes": target_atts};
var geo = Geometry($feature);
if (!IsEmpty(geo)) {
    abandon_row['geometry'] = geo;
}

// Query the Association table to get all associations, use the field map to copy to copy the attributes from the current feature to the abandon layer
var feat_guid = $feature.globalid;
var filtered_fs = Filter(assoc_fs, 'FROMGLOBALID = @feat_guid or TOGLOBALID = @feat_guid');
// Loop over the associations, using the field map to create the list of adds
var assoc_rows = [];
for (var row in filtered_fs) {
    var assoc_atts = {};
    for (var assoc_field in assoc_field_map) {
        assoc_atts[assoc_field_map[assoc_field]] = row[assoc_field];
    }
    var assoc_row = {"attributes": assoc_atts};
    Push(assoc_rows, assoc_row);
}
var return_dict = {
    "edit": [{
        "className": 'main.ABElectricDistributionDevice',
        "adds": [abandon_row]
    }
    ]
};
if (Count(assoc_rows) > 0) {
    Push(return_dict['edit'],
        {
            "className": 'main.ABAssociations',
            "adds": assoc_rows
        });
}
return return_dict;

 

Here is the code on the abandon layer in a batch calc rule

if ($feature.AB_BYPASS != 0)
{
    return;
}
return {
    "result": 1,
    "edit": [{
            "className": "main.ElectricDistributionDevice",
            "deletes": [{
                    "objectID": $feature.ORIG_OBJECTID
                }
            ]
        }
    ]
}

 

VanessaSimps
Occasional Contributor III

I downloaded the toolbox, but I am not seeing the Attribute Rules toolbox? I looked through all the toolboxes and tools just to make sure I wasn't missing something?! 

VanessaSimps_0-1711568367695.png

 

Thanks for your assistance on this one.

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

We recently moved the tools to their own toolbox.  I will work on getting a copy posted for you to test out.

0 Kudos
MikeMillerGIS
Esri Frequent Contributor

Here is a copy of the toolbox with the abandon rows.  Can also start to test out some of the other templated rules.  

https://github.com/Esri/arcade-expressions/tree/master/attribute_assistant