Example Search Function for Dynamic Scheduling

Hi,

I’m trying to implement a Search Function in Dynamic Scheduling. I’ve been following the documentation but I’m struggling to convert the framework provided into a working function.

If anyone has simple working example to share that would be great, thanks.

Hi @spencer !

My name’s Molly and I’m the lead on Dynamic Scheduling. Happy to help!!

Below are details about Search Functions and two corresponding examples. Let me know if this is helpful :). If you are still running into issues, please feel free to also send your code (anonymized if there is any revealing information) and I can take a look.

Best,
Molly

INPUT AND OUTPUT

INPUT:
Search functions can be triggered from a puck’s right click menu or from a row’s (or a blank space in time) right click menu. Where you call this search function determines its input structure.

  • If you are calling it from a puck’s right click menu, you must pass in puck as an input. The input variable must be called puck exactly. : suggestionFunctionName(puck: <insertObjectTypeAPINameHere>).
  • If you are calling it from a row’s right click menu, you must pass in the rowId and selectedTime as inputs. The input variables must be called rowId and selectedTime exactly: suggestionFunctionName(rowId: string, selectedTime: Timestamp).

OUTPUT:
Search functions, regardless of if called from a row right click or puck right click, can RETURN slots of time to highlight and/or pucks to highlight.

See docs for the output interface. See examples below for a function that returns just time slots and a function that returns just pucks to highlight.

Here are two examples

EXAMPLE 1:

  1. This function is triggered from a puck’s right click menu and returns highlighted time slots.
  2. Description of function: The search function helps find where an order can be rescheduled by factoring in [1] that material being produced on that that order and [2] the scheduled start date. To be considered a viable option the a production line must produce that material as defined by a property on the production line object. Additionally it must have capacity to produce the order which is determined by comparing the production line’s capacity to the total number of orders currently scheduled to occur on that line for the day provided by the “triggering” order.
  3. Function:
@Function()
public searchValidSlotsForOrder(puck: LegolasProductionOrder): ISearchResult {
    const materialType = puck.material.get()?.materialType;
    if (!materialType) {
        throw new UserFacingError("Linked material for order has no material type");
    }
    if (!puck.plantId) {
        throw new UserFacingError("Order has no plant");
    }
    if (!puck.scheduledStartDate) {
        throw new UserFacingError("Order has no scheduled start date");
    }
    if (!puck.durationMs) {
        throw new UserFacingError("Order has no duration");
    }

    const linesSpec = Objects.search().legolasProductionLine().filter(l => l.lineMaterialTypes.contains(materialType));
    const lines = linesSpec.all();
    const orders = linesSpec.searchAroundProductionOrder()
        .filter(o => o.plantId.exactMatch(puck.plantId!))
        .filter(o => o.scheduledStartDate.exactMatch(puck.scheduledStartDate!))
        .all();
    
    const ordersByLineId = groupBy(orders, o => o.productionLineId);

    const slots: IHighlight[] = [];

    lines.forEach(l => {
        const totalAfterMove = 1 + (ordersByLineId[l.lineId]?.length ?? 0);
        if (totalAfterMove <= (l.lineCapacity ?? Infinity)) {
            slots.push({
                type: HighlightType.SLOT,
                domain: {
                    start: puck.scheduledStartDate!.valueOf(),
                    end: puck.scheduledStartDate!.valueOf() + puck.durationMs!,
                },
                containerId: l.lineId,
            });
        }
    });

    return {
        rowGroup: {
            title: "Recommended Slots",
            containerIds: slots.map(s => s.containerId!),
            highlights: slots,
        }
    };
}

EXAMPLE 2:

  1. This function is triggered from a row (or blank space)'s right click menu and returns puck(s).
  2. Description: This search function helps finds what events can be assigned to a individual by factoring in [1] the time of the slot and [2] the individual’s topics of interests. To be considered a viable option an event must be at the same time as the triggering slot and be on one of the topics of interests.
  3. Function:
@Function()
public breakoutSessionMatchSearch(rowId: string, selectedTime: Timestamp): ISearchResultV2 {
    const attendee = Objects.search().conferenceAttendee().filter(a => a.attendeeId.exactMatch(rowId)).all()[0];
    const highlights: IHighlight[] = [];
    if (attendee && attendee.attendeeInterests) {
        const preferences = attendee.attendeeInterests!.split(',').map(p => p.trim());
        const scheds = Objects.search()
            .conferenceEvent().filter(e => e.topic.contains(...preferences))
            .searchAroundConferenceSchedule().filter(s => Filters.not(s.attendeeId.hasProperty()))
            .all();
        const uniqueOptions = uniqBy(scheds, s => s.eventId);
        uniqueOptions.forEach(s => {
            highlights.push({
                type: "PUCK",
                puckId: `${s.typeId}::${s.scheduleId}`,
            });
        });
    }
    return {
        rowGroup: {
            title: `Breakout event matches for ${rowId}`,
            containerIds: highlights.length > 0 ? [rowId, ""] : [],
            highlights: highlights,
        },
    }
}

Hi @MollyCarmody !

Thank you, that’s great and I’ll give that a go today. Please could you elaborate a bit on ‘containters’ in the context of ‘containerIds’?

In Exmaple 1, would the ‘line’ be a row in the scheduling gantt chart and therefore the ‘containerId’ would be that line objects primary key?

Edit: I’m also not sure what to select for ‘function input’ in workshop when i’m applying the published function. I would assume the function input is the puck I right click on, but the drop down is asking me for a variable?

Hi @spencer!

These are good questions.

  1. You are right about line in Example 1. It is the “Row” and the containerId is its primarykey
  2. In config, you do not have to supply anything for function input here (I know it says required but ignore). It will automatically pass the “puck” in in the background.

Note: In general, rules and our function structure of them is something we are reconsidering. Where you are confused is a common place we see across customers and is something we want to change!

Thanks for your help @MollyCarmody

I managed to get an (extremely basic) example working that I can build out!