Build your app

Last updatedJuly 21, 2021

This page details the steps for building your routing app with Nextmv. If this is your first Nextmv app, be sure you have completed all of the prerequisites.

Note, Github access is required to get started. Please contact support@nextmv.io to request access.

Write input data as JSON

Input data must be written in [JSON][json] for use with Nextmv (see below for a sample input file from the Dispatch App).

Nextmv's tools are designed to operate directly on business data (in JSON) to produce decisions that are actionable by software systems. This makes decisions more interpretable and easier to test. It also makes integration with data warehouses and business intelligence platforms significantly easier.

{
    "vehicles": [
        {
            "id": "Mvrs-N-Shkrs-1",
            "start": { "lon": -96.786455, "lat": 32.82729 },
            "end": { "lon": -96.786455, "lat": 32.82729 }
        },
        {
            "id": "Mvrs-N-Shkrs-2",
            "start": { "lon": -96.786455, "lat": 32.82729 },
            "end": { "lon": -96.786455, "lat": 32.82729 }
        }
    ],
    "stops": [
        {
            "id": "Parent's Basement",
            "position": { "lon": -96.827094, "lat": 33.004745 }
        },
        {
            "id": "Van Down By The River",
            "position": { "lon": -96.86074, "lat": 33.005741 }
        },
        {
            "id": "McMansion",
            "position": { "lon": -96.820915, "lat": 32.969456 }
        },
        {
            "id": "Cozy English Cottage",
            "position": { "lon": -96.695259, "lat": 32.921629 }
        },
        {
            "id": "Apartment Above the Bodega",
            "position": { "lon": -96.673973, "lat": 32.875507 }
        },
        {
            "id": "18th Floor - Elevator is Broken",
            "position": { "lon": -96.682899, "lat": 32.810318 }
        },
        {
            "id": "Still Packing",
            "position": { "lon": -96.75642, "lat": 32.66026 }
        },
        {
            "id": "Hasn't Started Packing",
            "position": { "lon": -96.87521, "lat": 32.673555 }
        },
        {
            "id": "Shack",
            "position": { "lon": -96.888943, "lat": 32.772914 }
        },
        {
            "id": "Mother-In-Law Suite",
            "position": { "lon": -97.044811, "lat": 32.862358 }
        },
        {
            "id": "Apartment Above Garage",
            "position": { "lon": -97.021465, "lat": 32.933848 }
        },
        {
            "id": "Dilapidated Old House",
            "position": { "lon": -96.87521, "lat": 32.84563 }
        },
        {
            "id": "Barn Loft",
            "position": { "lon": -96.893749, "lat": 32.649276 }
        },
        {
            "id": "American Hoarder",
            "position": { "lon": -96.943874, "lat": 32.653323 }
        },
        {
            "id": "5th Floor Walk Up",
            "position": { "lon": -96.846371, "lat": 32.748662 }
        },
        {
            "id": "Family of 9",
            "position": { "lon": -96.842251, "lat": 32.735956 }
        },
        {
            "id": "Don't forget the Yacht",
            "position": { "lon": -96.840191, "lat": 32.714583 }
        },
        {
            "id": "7 Cats 2 dogs and a horse",
            "position": { "lon": -96.808605, "lat": 32.710539 }
        },
        {
            "id": "Triplets",
            "position": { "lon": -96.595058, "lat": 32.82486 }
        },
        {
            "id": "Everything is in trash bags",
            "position": { "lon": -96.64175, "lat": 32.772914 }
        },
        {
            "id": "Tetris Queen",
            "position": { "lon": -96.573772, "lat": 32.94134 }
        },
        {
            "id": "Boathouse",
            "position": { "lon": -96.87521, "lat": 32.895804 }
        },
        {
            "id": "House on a Boat",
            "position": { "lon": -96.950741, "lat": 32.844476 }
        },
        {
            "id": "Pool House",
            "position": { "lon": -96.910915, "lat": 32.939035 }
        },
        {
            "id": "Cabin In The Woods",
            "position": { "lon": -96.592999, "lat": 32.745775 }
        },
        {
            "id": "Haunted House",
            "position": { "lon": -97.271404, "lat": 32.933272 }
        },
        {
            "id": "House on the Hill",
            "position": { "lon": -97.20274, "lat": 32.902722 }
        },
        {
            "id": "Brick and Mortar",
            "position": { "lon": -96.990566, "lat": 32.85659 }
        },
        {
            "id": "Stucco",
            "position": { "lon": -96.831951, "lat": 32.618629 }
        },
        {
            "id": "Cat Condo",
            "position": { "lon": -96.785259, "lat": 32.706495 }
        },
        {
            "id": "French Villa",
            "position": { "lon": -96.874523, "lat": 32.706495 }
        },
        {
            "id": "Trailer Park",
            "position": { "lon": -96.992626, "lat": 32.800045 }
        },
        {
            "id": "Tiny Home",
            "position": { "lon": -97.012539, "lat": 33.019675 }
        },
        {
            "id": "Tree Hollow",
            "position": { "lon": -96.961727, "lat": 32.973028 }
        },
        {
            "id": "Fairy Garden",
            "position": { "lon": -97.002926, "lat": 32.796005 }
        }
    ]
}

Define a Go struct

Next, you will need to define a Go struct for your data (typically this is stored in a data.go file as part of your model). The Go struct for the sample data with vehicles and stops is provided below.

// Input contains Vehicles and Stops.
type input struct {
    Vehicles []Vehicle `json:"vehicles"`
    Stops []Stops      `json:"stops"`
}

Define the state

Next, you need to define a decision or system state to track (typically this is stored in a state.go file).

// state handles the translation between the current Hop fleet engine fleet.State and
// the app's own state (represented through the app's schema attributes).
type state struct {
    model fleet.State
    input input
}

You can choose to store any data on your model state (e.g., the final location of the driver, the cost of the route, or the current path as a slice of integers to refer to the locations by index).

Add methods

Add Feasible, Next, and any other methods (e.g., Value) as needed.

The Next method determines how to hop from one state to another. It points to fleet engine's Next function and updates an app's state.

func (s state) Next() model.Expander {
    expander := s.model.Next()
    if expander == nil {
        return nil
    }

    expandingFunction := func() model.State {
        nextState := expander.Expand()
        if nextState == nil {
            return nil
        }

        return state{
            model: nextState.(fleet.State),
            input:      s.input,
        }
    }

    return expand.Lazy(expandingFunction)
}

The Feasible method determines if the current state is a possible solution. It points to the fleet engine's Feasible function.

func (s state) Feasible() bool {
    return s.model.Feasible()
}

The Value method determines the objective's quantity for the current state. It points to the fleet engine's Value function.

func (s state) Value() int {
    return s.model.Value()
}

Go build your app

Finally, run go build (e.g., in cmd/cli for a CLI runner if your project follows the recommended structure) to create a binary for your model.

The binary created will read input structures from standard input and write state structures to standard output. Note, if using the CLI runner, you can add flags to configure where to read input from and where to write output to, along with other options (see example below).

./model-cli \
         -hop.runner.input.path data/input.json \
         -hop.solver.limits.duration 10s \
         -hop.runner.output.path data/output.json
Was this helpful?