diff --git a/v4.0/Configuration/Basemap Configuration.docx b/v4.0/Configuration/Basemap Configuration.docx new file mode 100644 index 0000000..53858f3 Binary files /dev/null and b/v4.0/Configuration/Basemap Configuration.docx differ diff --git a/v4.0/Configuration/ERM API Configuration.docx b/v4.0/Configuration/ERM API Configuration.docx new file mode 100644 index 0000000..f86f03a Binary files /dev/null and b/v4.0/Configuration/ERM API Configuration.docx differ diff --git a/v4.0/Configuration/ERM Web Application Configuration.docx b/v4.0/Configuration/ERM Web Application Configuration.docx new file mode 100644 index 0000000..0ea4a1a Binary files /dev/null and b/v4.0/Configuration/ERM Web Application Configuration.docx differ diff --git a/v4.0/Install-Deployment/ERM Application Deployment Guide.docx b/v4.0/Install-Deployment/ERM Application Deployment Guide.docx new file mode 100644 index 0000000..4b21c3b Binary files /dev/null and b/v4.0/Install-Deployment/ERM Application Deployment Guide.docx differ diff --git a/v4.0/Install-Deployment/ERM Environment Setup Guide.docx b/v4.0/Install-Deployment/ERM Environment Setup Guide.docx new file mode 100644 index 0000000..e94045d Binary files /dev/null and b/v4.0/Install-Deployment/ERM Environment Setup Guide.docx differ diff --git a/v4.0/Operational-Documentation/Delete Plan - Job Aid.md b/v4.0/Operational-Documentation/Delete Plan - Job Aid.md new file mode 100644 index 0000000..4aba88c --- /dev/null +++ b/v4.0/Operational-Documentation/Delete Plan - Job Aid.md @@ -0,0 +1,32 @@ + **Deleting a Plan** from ERM - [Job Aid](https://en.wiktionary.org/wiki/job_aid) +This job aid uses the Portal UI and ArcGIS Pro to find and delete plans. These tasks could be automated through the [Python API](https://developers.arcgis.com/python/) as well +1. Open the ERM_Registry table in ArcGIS Pro. +2. Search for a plan by the "Dispatch Location", "First Event" (Creation time), "Last Event" (Last updated time), or any of the other fields. Optionaly, set a definition query on any field. +![Image for Pro Query](https://user-images.githubusercontent.com/3834298/90927990-a97aac80-e3bb-11ea-9e40-0c90c28fe903.jpg) +3. Note the "Item Id" which corresponds to Feature Layer ID. The "Webmap Id" which corresponds to the Web Map, and (if applicable), the "Dashboard Id" which corresponds to a plan dashboard if one was created. + +*Note: If you are using the **Workforce Extension for ERM**, save the "Item ID" somewhere you can paste it later if you also need to clear the Workforce project.* + +4. In Portal, search for and delete the following items in the following order +* If applicable: the Dashboard +* Webmap item +![Image for delete web map](https://user-images.githubusercontent.com/3834298/90928011-b4cdd800-e3bb-11ea-9c39-65be90843180.jpg) +* Feature layer +* Row for the plan in ERM_Registry + +The plan is now completely deleted and will no longer appear in any items inventory including RPE. + +If you are using the **Workforce Extension for ERM**, follow Steps 5-7 below to delete records published into Workforce from ERM + +*Note: This process is not needed for cleaning out older (prior days) features since those features will be purged on a schedule. This process is intended to truncate an active plan so that it can be replaced by a new plan.* + +5. Retrive the ItemID you noted in Step 3 above. +6. Generate a token using the steps below + * a. Go to https://\/portal/sharing/rest/generateToken. \ represents the fully qualified domain name of the server that portal is deployed on. + * b. Enter Username and Password. Use Portal credentials for an administrator level account. + * c. For Webapp URL, enter the Portal URL. This should match the config.portalUrl value in the Middleware API config file. +![Image for generate token](https://user-images.githubusercontent.com/3834298/94065006-211f6b00-fdb0-11ea-9b81-90c254e36db9.png) + +7. Paste https://\/ermapi/workforce/deletePlan?planItemId=XXX&token=YYY into your browser. \ represents the fully qualified domain name of the server that Middleware API is deployed on. + * Replace XXX with the plan ID previously identified. + * Replace YYY with the token that was generated. diff --git a/v4.0/Operational-Documentation/ERM - Add Unique Index to field.docx b/v4.0/Operational-Documentation/ERM - Add Unique Index to field.docx new file mode 100644 index 0000000..1fa2fa6 Binary files /dev/null and b/v4.0/Operational-Documentation/ERM - Add Unique Index to field.docx differ diff --git a/v4.0/Operational-Documentation/ERM_CopySolveParameters.py b/v4.0/Operational-Documentation/ERM_CopySolveParameters.py new file mode 100644 index 0000000..6002c80 --- /dev/null +++ b/v4.0/Operational-Documentation/ERM_CopySolveParameters.py @@ -0,0 +1,45 @@ + +""" +-------------------------------- +Name: ERM_Copy Solve Parameters.py +Purpose: Copy default solve parameter records for each location +Author: Mike Nelson +Created 5/26/2020 +Copyright: (c) Esri +ArcGIS Version: 2.4 (Pro) +PYTHON Version: 3.6 (API 1.8) +Requirements: update variables for feature class/table paths + Expects DepotTemplate to be fully populated before running +-------------------------------- +""" + +import arcpy + + +def get_value_list(in_layer, field_name): + with arcpy.da.SearchCursor(in_layer, [field_name]) as cursor: + return sorted({row[0] for row in cursor}) + +################### UPDATE VARIABLES ############################## +depot_template = r"C:\ERM\services\fgdbs\ERM_Plan_Defaults.gdb\DepotTemplate" +depot_name_field = "depotname" +solve_param_defaults_table = r"C:\ERM\services\fgdbs\ERM_Solve_Parameters.gdb\Solve_Parameters_Restrictions_DefaultValues" +solve_param_table = r"C:\ERM\services\fgdbs\ERM_Solve_Parameters.gdb\Solve_Parameters_Restrictions" +solve_para_depot_field = "displocname" + +# make view for tables for selection/calculate tools to use +solve_param_view = "solve_param_view" +arcpy.MakeTableView_management(solve_param_table, solve_param_view) + +# get list of all depot locations +depot_list = get_value_list(depot_template, depot_name_field) + +for depot in depot_list: + arcpy.AddMessage(f"Processing {depot}...") + + arcpy.AddMessage("Append default records...") + arcpy.Append_management(solve_param_defaults_table, solve_param_table, "NO_TEST") + arcpy.AddMessage("Select blank records...") + arcpy.management.SelectLayerByAttribute(solve_param_view, "NEW_SELECTION", solve_para_depot_field + " = ''", None) + arcpy.AddMessage("Calculate depot name...") + arcpy.management.CalculateField(solve_param_view, solve_para_depot_field, f"'{depot}'", "PYTHON3", '', "TEXT") diff --git a/v4.0/Operational-Documentation/ERM_CreateGroups.py b/v4.0/Operational-Documentation/ERM_CreateGroups.py new file mode 100644 index 0000000..b0d2e63 --- /dev/null +++ b/v4.0/Operational-Documentation/ERM_CreateGroups.py @@ -0,0 +1,37 @@ +""" +-------------------------------- +Name: ERM_CreateGroups.py +Purpose: Create Portal groups used by Enterprise Route Management system +Author: Mike Nelson +Created 5/26/2020 +Copyright: (c) Esri +ArcGIS Version: 2.4 (Pro) +PYTHON Version: 3.6 (API 1.8) +Requirements: update group_list variable. + Create Environment variables exist for credentials, or just change variables to store directly +-------------------------------- +""" + +import arcpy, os, sys +from arcgis.gis import GIS +from os import environ + +group_list = ["COV", "OCC", "GOL", "INL"] + +# portal credentials +erm_portal = environ["ERM_PORTAL"] +erm_user = environ["ERM_USER"] +erm_pswd = environ["ERM_PWD"] + +# Sign in/connect to portal +gis = GIS(erm_portal, erm_user, erm_pswd, verify_cert=False) + +for group_name in group_list: + tag_list = f"dispatch-location-{group_name}, ERM" + arcpy.AddMessage(f"Creating group {group_name}...") + geocaching_group = gis.groups.create(title=group_name, + tags=tag_list, + description=f"ERM group for location {group_name}", + access='org', + is_invitation_only='False') + diff --git a/v4.0/Operational-Documentation/ZoneChecker.ipynb b/v4.0/Operational-Documentation/ZoneChecker.ipynb new file mode 100644 index 0000000..d2277a4 --- /dev/null +++ b/v4.0/Operational-Documentation/ZoneChecker.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Slivers, Excessive Vertices, Interior Voids, Excessive Parts, OID, Shape Specifics\n", + "False,False,True,False,DispLoc=ABC RouteName=ABCN parts=2 length=1094062 area=9966623390 point count=1159 ring count=58\n", + "False,False,True,False,DispLoc=ABC RouteName=ABCW parts=1 length=989373 area=10664017204 point count=950 ring count=35\n", + "False,False,True,False,DispLoc=ABC RouteName=ABCE parts=2 length=734292 area=6943725857 point count=748 ring count=28\n", + "False,False,True,False,DispLoc=ABC RouteName=ABC_LG parts=2 length=636477 area=5623342121 point count=759 ring count=29\n", + "False,False,True,False,DispLoc=ABC RouteName=ABC_City parts=1 length=316778 area=1699683040 point count=462 ring count=23\n", + "False,False,True,False,DispLoc=ABC RouteName=ABC_Gated parts=1 length=890466 area=8401019328 point count=1117 ring count=39\n", + "False,False,True,False,DispLoc=ABC RouteName=ABC_Resi parts=2 length=325441 area=1532937368 point count=457 ring count=39\n" + ] + } + ], + "source": [ + "import arcpy\n", + "from arcgis.gis import *\n", + "#these next two support random string generation\n", + "from random import choice\n", + "from string import ascii_uppercase\n", + "\n", + "\n", + "arcpy.env.overwriteOutput = True\n", + "\n", + "#Get the layer from the TOC\n", + "featurelayer = \"ZoneTemplate\"\n", + "zoneNameField = \"displocname\"\n", + "fields = [\"displocname\",\"routename\",\"objectid\",\"shape@\"]\n", + "\n", + "#Set an expression to optionally use as a whereclause\n", + "expression = \"{} = 'DEN'\".format(arcpy.AddFieldDelimiters(featurelayer, zoneNameField))\n", + "\n", + "#create a table in scratch space to use as output \n", + "explicit_scratch = \"C:/temp\"\n", + "GDB_GUID = ''.join(choice(ascii_uppercase) for i in range(12))\n", + "explicit_scratch_ws = arcpy.CreateFileGDB_management(explicit_scratch, \"scratch_\" + GDB_GUID + \".gdb\")\n", + "arcpy.env.workspace = os.path.join(explicit_scratch, \"scratch_\" + GDB_GUID + \".gdb\")\n", + "\n", + "failedZoneCheck_tbl = arcpy.management.CreateTable(arcpy.env.workspace, \"failedZoneCheck\")\n", + "arcpy.AddField_management(failedZoneCheck_tbl, \"slivers\", \"TEXT\", field_alias=\"Sliver Polygons\", field_length = 5)\n", + "arcpy.AddField_management(failedZoneCheck_tbl, \"toodense\", \"TEXT\", field_alias=\"Excessive Vertices\", field_length = 5)\n", + "arcpy.AddField_management(failedZoneCheck_tbl, \"donuts\", \"TEXT\", field_alias=\"Interior Voids\", field_length = 5)\n", + "arcpy.AddField_management(failedZoneCheck_tbl, \"manyparts\", \"TEXT\", field_alias=\"Exessive Parts\", field_length = 5)\n", + "arcpy.AddField_management(failedZoneCheck_tbl, \"sourceOID\", \"LONG\", field_alias=\"Source OBJECTID\")\n", + "arcpy.AddField_management(failedZoneCheck_tbl, \"zoneinfo\", \"TEXT\", field_alias=\"Zone Metadata\", field_length = 200)\n", + "\n", + "#Set the fields we will use for the insert cursor\n", + "insertFields = ['slivers','toodense','donuts','manyparts','sourceOID','zoneinfo']\n", + "updateCur = arcpy.da.InsertCursor(failedZoneCheck_tbl,insertFields)\n", + "\n", + "#We are sorting the zone records by service area name and zone name, which will order them so that duplicate geometry zones are \n", + "#grouped together. We will use a \"lastArea\" shape.area tracker to recognize when we have encountered a new shape to evaluate - no need to report \n", + "#issues with each identical shape record. \n", + "lastArea = int(0)\n", + "print (\"Slivers, Excessive Vertices, Interior Voids, Excessive Parts, OID, Shape Specifics\") \n", + "\n", + "#uncomment the version with a whereclause if you want to evaluate a subset of zones - or just set a definition query on the layer\n", + "for row in sorted(arcpy.da.SearchCursor(featurelayer, fields)):\n", + "#for row in sorted(arcpy.da.SearchCursor(featurelayer, fields, where_clause = expression)):\n", + " shape = row[3]\n", + " thisArea = int(shape.area)\n", + " if (thisArea != lastArea):\n", + " if(shape.length > shape.area):\n", + " sliverShape = True\n", + " else:\n", + " sliverShape = False\n", + " if(shape.length/shape.pointCount <200):\n", + " excessiveVertices = True\n", + " else:\n", + " excessiveVertices = False\n", + " if(shape.boundary().partCount - 1 > shape.partCount):\n", + " interiorRings = True\n", + " else:\n", + " interiorRings = False\n", + " if(shape.partCount > 4):\n", + " manyParts = True\n", + " else:\n", + " manyParts = False\n", + " if(sliverShape or excessiveVertices or interiorRings or manyParts): \n", + " rowMetadata = \"DispLoc={} RouteName={} parts={:d} length={:d} area={:d} point count={:d} ring count={:d}\".format(\n", + " row[0],\n", + " row[1],\n", + " shape.partCount, \n", + " int(shape.length), \n", + " int(shape.area), shape.pointCount, \n", + " shape.boundary().partCount)\n", + " \n", + " updateCur.insertRow((str(sliverShape),str(excessiveVertices),str(interiorRings),str(manyParts),row[2],rowMetadata))\n", + " print(str(sliverShape) + \",\" + str(excessiveVertices) + \",\" + str(interiorRings) + \",\" + str(manyParts) + \",\" + rowMetadata)\n", + " \n", + " \n", + " lastArea = thisArea\n", + "del updateCur " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ArcGISPro", + "language": "Python", + "name": "python3" + }, + "language_info": { + "file_extension": ".py", + "name": "python", + "version": "3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/v4.0/Operational-Documentation/Zones Sanity Check.md b/v4.0/Operational-Documentation/Zones Sanity Check.md new file mode 100644 index 0000000..752640a --- /dev/null +++ b/v4.0/Operational-Documentation/Zones Sanity Check.md @@ -0,0 +1,31 @@ + **Sanity Quality Check for Route Zones**- [Job Aid](https://en.wiktionary.org/wiki/job_aid) +This job aid uses an ArcGIS Pro notebook to run geometric evaluations of a ZoneTemplate layer and creates a report of zone polygon shapes that would likely cause a VRP solve request to fail. +1. Create a new project in ArcGIS Pro +2. Add a your ZoneTemplate layer to the map +![Image for Add Zones](https://github.com/rConger/doc_images/blob/main/ZonesLayer.jpg?raw=true) + +3. Fron the Insert menu, choose to insert an existing notebook and browse to the location where you downloaded [ZoneChecker.ipynb](https://github.com/EsriPS/enterprise-route-management/blob/master/Operational-Documentation/ZoneChecker.ipynb) to add it to the project + +![Image for Add Notebook](https://github.com/rConger/doc_images/blob/main/addNotebook1.jpg?raw=true) + +4. From the Notebook folder in the project catalog view, find the notebook and open it +![Image for Open Notebook](https://github.com/rConger/doc_images/blob/main/OpenNotebook.jpg?raw=true) + +5. Make any necessary changes to temporary files path or zones layer name, then run the notebook +![Image for Adjust and Run Notebook](https://github.com/rConger/doc_images/blob/main/AdjustNotebookAndRun.jpg?raw=true) + +6. Depending on the number of zones, the notebook may take up to 2 minutes to run. When it's finished, you'll notice run report data in the notebook output an a new table added to the TOC. +![Image for Run Complete](https://github.com/rConger/doc_images/blob/main/RunReport.jpg?raw=true) + +7. Open the new table added and have a look at the attributes. first four columns indicate the quality check that was failed. Records with at least one failed metric will be added to the table. Any given record could fail more than one check. +![Image for Report Table](https://github.com/rConger/doc_images/blob/main/ReportTable.jpg?raw=true) + +The tests corresponding to the columns are: +* **Sliver Polygons** - any feature where perimiter length >= area +* **Excessive Vertices** - any feature whose aveage vertex spacing is closer than 200 meters +* **Interior Voids** - any feature where the number if rings is more than one greater than the number of feature parts +* **Excessive Parts** - any feture with more than 4 discontigous parts (e.g. islands) + +8. You can join the report table to the original Zones polygon layer on failedZoneCheck.Source OBJECTID -> ZoneTemplate.OBJECTID to more easily examine any failed feature. For example: +![Image for Failed Check](https://github.com/rConger/doc_images/blob/main/FailedCheck1.jpg?raw=true) +![Image for Failed Check](https://github.com/rConger/doc_images/blob/main/FailedCheck2.jpg?raw=true) diff --git a/v4.0/Schema/BSI Collections Sync Schema b/v4.0/Schema/BSI Collections Sync Schema new file mode 100644 index 0000000..f40b5ba --- /dev/null +++ b/v4.0/Schema/BSI Collections Sync Schema @@ -0,0 +1,14 @@ +| Field Name | Data Type | Description | Optional/Required | +|----------------------|------------|-------------------------------------------------------------------------------------------|-------------------| +| CollectionName | String 128 | Name of this collection | Required | +| Destination | String 50 | Freeform contextual description | Optional | +| DestinationETA | Date | Time this collection is expected to be arrived/available for loading onto P&D routes. UTC | Optional | +| DispatchInstructions | String 100 | Freeform contextual description | Optional | +| EarliestCommit | Date | Time of the earliest service commitment in this collection. UTC | Optional | +| FinalDestination | String 50 | Freeform contextual description | Optional | +| LocalOrders | Int | Descriptive Double precision | Optional | +| LocationDescription | String 50 | Freeform contextual description | Optional | +| Origin | String 50 | Freeform contextual description | Optional | +| ServiceLevel | String 50 | Freeform contextual description | Optional | +| ToBeRemoved | String 5 | true or false - Should this order be deleted from the plan | Optional | +| TotalOrders | Int | Freeform contextual description | Optional | diff --git a/v4.0/Schema/BSI Order Pairs Sync Schema b/v4.0/Schema/BSI Order Pairs Sync Schema new file mode 100644 index 0000000..cd2615d --- /dev/null +++ b/v4.0/Schema/BSI Order Pairs Sync Schema @@ -0,0 +1,6 @@ +| Field Name | Data Type | Description | Optional/Required | +|-----------------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| FirstOrderName | String 128 | Instructions to optimize an order relative to another. PU order prior to DEL the same order. FK to GeoOrder.orderid | Required | +| SecondOrderName | String 128 | Instructions to optimize an order relative to another. DEL order after it's PU. FK to GeoOrder.orderid | Required | +| MaxTransitTime | Float | max time this order can spend on a route from the time it's picked up to the time it's delivered. Minutes | Optional | +| ToBeRemoved | String 5 | true or false - Should this order pair be removed from the plan? | Optional | diff --git a/v4.0/Schema/BSI Orders Sync Schema b/v4.0/Schema/BSI Orders Sync Schema new file mode 100644 index 0000000..312ea11 --- /dev/null +++ b/v4.0/Schema/BSI Orders Sync Schema @@ -0,0 +1,50 @@ +|Field Name | Data Type | Description | Optional/Required | +|-----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| OrderID | String 128 | The order ID as globally unique within a given plan scope (String 128) | Required | +| AssignmentRule | Int | Rule for how this order should be assigned to a route. Set to 3 = Optimizer decides | Required | +| CollectionName | String 128 | Name of the collection that this order is a part of | Required | +| CurbApproach | Int | Requirement for how this location must be approached. Not Null small int. e.g. Either Side of Vehicle = 0 | Required | +| ServiceTime | Float | Time in minutes (fractional) it is anticipated this order will take to complete service | Required | +| StopType | String 8 | Pickup or Delivery | Required | +| TimeZone | String 50 | Time zone descriptive. Placeholder for future functionality. Time zone set in Depot feature. | Optional | +| X | Double | Longitude in geographic coordinate system | Required | +| Y | Double | Latitude in geographic coordinate system | Required | +| Address | String 100 | Freeform contextual description. (String 100) nullable | Optional | +| AgentRevenue | Double | Descriptive Double precision | Optional | +| AppointmentCode | String 1 | Freeform contextual description | Optional | +| City | String 50 | Freeform contextual description | Optional | +| Consignee | String 100 | Freeform contextual description | Optional | +| Consigner | String 100 | Freeform contextual description | Optional | +| ConsigneeNotes | String 50 | Freeform contextual description | Optional | +| ConsignerNotes | String 50 | Freeform contextual description | Optional | +| CorporateRevenue | Double | Descriptive Double precision | Optional | +| DepotName | String 10 | Depot.depotname where this order originates and/or terminates | Optional | +| Destination | String 50 | Freeform destination. Supply chain context. An order should terminate at the destination | Optional | +| DispLocDesc | String 100 | Descriptive | Optional | +| DispLocName | String 10 | Dispatch area. where this order will be dispatched. A dispatch area might contain more than one depot | Optional | +| Geoorder_Volume | Double | Cubic volume in this order for calculating capacity | Optional | +| Geoorder_Pieces | Double | Number of pieces in this order for calculating capacity | Optional | +| Geoorder_Weight | Double | Weight of this order for calculating capacity | Optional | +| Geoorder_Units | Double | Number of Units of this order for calculating capacity | Optional | +| Hazmat | String 128 | Freeform contextual description | Optional | +| InboundArriveTime | Date | Date/time (in UTC) when this order is available for dispatch from depot | Optional | +| IsLocked | String 5 | true or false - is this order locked to the route? | Optional | +| LoadID | String 128 | The load into which this order is grouped. Common accross all GeoOrders in the load. Editable by the BSI. Temp by ERM | Optional +| MaxViolationTime1 | Double | Maximum time in minutes that this order service can be violated (late) for first time window | Optional | +| ModelRevenue | Double | Assignment Priority in the case of order demand outstripping route capacity for a shift | Optional | +| OrderDescription | String 256 | Freeform order description | Optional | +| Origin | String 50 | Freeform origin description. supply chain. An order might originate at a location not referenced in the current plan | Optional | +| OutboundDepartTime | Date | Date/time (in UTC) when this order must be delivered to depot | Optional | +| PostalCode | String 10 | Freeform contextual description | Optional | +| RouteName | String 128 | Only to be used in combination with an assignmentrule other than 3 | Optional | +| RouteRevenue | Double | Descriptive Double precision | Optional | +| Sequence | Int | Only to be used in combination with an assignmentrule other than 3 | Optional | +| ServiceCommitDate | Date | Descriptive | Optional | +| ServiceLevel | String 50 | Freeform contextual description | Optional | +| Specialty | String 50 | Specialties required by this order's stop as a space delimited list | Optional | +| State_Province | String 2 | Freeform contextual description | Optional | +| Street | String 50 | Freeform contextual description | Optional | +| TransitNotes | String 50 | Freeform contextual description | Optional | +| TWEnd1 | Date | First time of day when this order closes (UTC) | Optional | +| TWStart1 | Date | First time of day when this order opens (UTC) | Optional | +| ToBeRemoved | String 5 | true or false - Should this order be deleted from the plan | Optional | diff --git a/v4.0/Schema/ERM Data Model schema.docx b/v4.0/Schema/ERM Data Model schema.docx new file mode 100644 index 0000000..fedab1d Binary files /dev/null and b/v4.0/Schema/ERM Data Model schema.docx differ diff --git a/v4.0/User Guide/UserGuide.md b/v4.0/User Guide/UserGuide.md new file mode 100644 index 0000000..2c69764 --- /dev/null +++ b/v4.0/User Guide/UserGuide.md @@ -0,0 +1,50 @@ +# Enterprise Route Management Read Me +Enterprise Route Management (ERM) is an optimization pattern provided by Esri which leverages a combination of off the shelf tools from the Esri stack, a user facing interface for exposure that does not require the user to be familiar with GIS, and a number of back-end tools to communicate between the various tools and systems. +## Key Terms +- Routes - these represent the mobile resources who do work. This can be just an employee or in cases of capacitated problems (e.g. a truck driver who drives a truck that has a maximum capacity) +- Orders - these represent the work that needs to be done, like a work order, service order, pickup or delivery +- Dispatch Location - the office location for which the user is planning for +- Depots - the start and/or ending location of routes +- Zones - optional geographic zones that may be used to confine routes to a specific area +- Plan - the dispatch location, date and time combination that will be used to request the related orders, routes and depots to be used in the optimization +- Vehicle Routing Problem (VRP) - the service that optimally distributes the orders across the routes while considering the constraints applied +- Travel Mode - the set of road rules that apply to the routes in the plan +- Assignment rule - This is applied to orders and routes and determines how the VRP treats them + - Orders + - Override - the solver will override its current status and place it on the best route + - Exclude - the solver will ignore this orders + - Preserve Route and Relative Sequence - keep the order on the assigned route and keep its sequence in the same relative spot to the other assigned orders + - Anchor First - force this order to be the first stop on a route + - Anchor Last - force this order to be the last stop on a route + - Routes + - Include - optimize this route + - Exclude - do not optimize this route +## Logging Into the App +Users sign in with the Portal named user. Users are part of Portal groups which dictate which dispatch locations users are authorized to plan for. +## Creating a New Plan +The most common workflow for users is starting a new plan for the current day. This is achieved by clicking on the "Create New Plan" button. Users then select the dispatch location they want to plan for from the drop down, select a date and time they want to plan for. The definition of the time and day may differ based on customer business systems but it generally represents the cutoff time window for any orders that need to be included in the plan. +## Open a Prior Plan +In some cases, a user may want to revisit a plan they, or someone else in their planning location, already started. To do so, users can click on "View Plan" next to any plan shown on the first screen displayed when the user logs in. +## Parts of a Plan + +### Collections +Collections are a way of grouping orders that make it easy to remove orders from a plan. Commonly, they represent things like trailers or stores full of orders, and by unchecking a collection and updating, all the orders in that collection are excluded and effectively removed from the plan until the collection is added back to the plan. +### Orders +Orders represent the work that needs to be done and have a variety of attributes on them. While some attributes are purely contextual information for the users, some constrain the routing problem. Some examples of these are time windows (what are the upper and lower bounds of time that an order can be serviced), specialty (are there only certain types of routes that can service this order), capacity (weight, length, pallet count, etc.). Administrators control which attributes are visible and/or editable. +### Routes +Routes are the mobile resources that can service orders. Like orders, they have some attributes that are contextual and others that constrain the optimization problem. Some examples of constraining attributes are start time, capacity, specialty, max total time, and max total distance. The VRP considers the constraints of the orders, the constraints of the routes and the cost settings on the routes and tries to solve the problem by servicing the most amount of orders it can in the lowest cost fashion. Also, like orders, administrators control which attributes are visible and/or editable. +## Optimizing a Plan +By default, users may click on "Optimize Plan" to run the VRP against all orders and routes to determine the best distribution of orders across routes. Alternatively, they can also: +- Select only a limited number of routes and optimize only those routes +- Select only a limited number of routes to re-sequence those routes only +- Select only a limited number of routes and validate their current assignments do not violate any constraints + +Administrators control which optimization modes are available. After running a route or routes through the VRP, solved routes will show their status tag change from "Unsolved" to "Solved." +## Manually Modifying a Plan +Outside of optimization, routes and orders can have manual assignment. An unassigned order or orders can be selected, users can then click on "Assign Orders" and select the desired route to put it on. The same can be done to orders assigned to a route in order to put them on another route. Users can also drag an order up or down in a route to change its sequence. All routes must be sent back to the solver to validate any manual assignments. +## Refreshing a Plan +At any time, a user my click on the "Refresh Plan" button. This makes a request to the business system to load any new orders into the plan as well as any attribute changes for orders that are already in the plan. It may remove orders from a plan that should no longer be included. The same applies to collections. +## Creating a Dashboard +At any time, a user may click on "Create Dashboard." This creates a report dashboard in the format that the administrator has defined for the organization. After the dashboard is created, the user may click on "Open Dashboard" to view the dashboard for that plan. +## Committing Routes +Users may click "Commit Routes" to commit all routes or select a subset set of routes to commit. The commit action sends a message to the business system on which routes are done being planned so that it may retrieve any required information need to update the business system. If using Esri mobile apps, this also triggers the synchronization of the plan to those apps. \ No newline at end of file