mirror of
https://github.com/morten-olsen/bob-the-algorithm.git
synced 2026-02-08 00:46:25 +01:00
fix
This commit is contained in:
committed by
Morten Olsen
parent
8259232a83
commit
6181eeb0c8
@@ -1,6 +1,5 @@
|
||||
import { Context, GraphNode } from "#/types/graph";
|
||||
import { UserLocation } from "#/types/location";
|
||||
import { Task } from "#/types/task";
|
||||
import { Task, Time, UserLocation } from "#/features/data";
|
||||
import { Context, GraphNode } from "../types";
|
||||
import { getImpossible, getNext } from "./get-next";
|
||||
|
||||
enum Strategies {
|
||||
@@ -29,7 +28,7 @@ type Status = RunningStatus | CompletedStatus;
|
||||
|
||||
type BuildGraphOptions = {
|
||||
location: UserLocation;
|
||||
time: Date;
|
||||
time: Time;
|
||||
tasks: Task[];
|
||||
context: Context;
|
||||
strategy?: Strategies;
|
||||
@@ -49,7 +48,7 @@ const fil = <T>(
|
||||
for (let b = 0; b < fn.length; b++) {
|
||||
if (fn[b](input[i])) {
|
||||
output[b].push(input[i]);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +149,9 @@ const buildGraph = async ({
|
||||
return complete([fullComplete]);
|
||||
}
|
||||
}
|
||||
deadList.push(...dead);
|
||||
if (strategy !== Strategies.all) {
|
||||
deadList.push(...dead);
|
||||
}
|
||||
}
|
||||
|
||||
return complete(completedList);
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { GraphNode } from "#/types/graph";
|
||||
import { PlanItem } from "#/types/plans";
|
||||
import { timeUtils } from "#/features/data";
|
||||
import { GraphNode, PlannedEntry } from "../types";
|
||||
|
||||
const constructDay = (node: GraphNode) => {
|
||||
let current: GraphNode | undefined = node;
|
||||
const plans: PlanItem[] = [];
|
||||
const plans: PlannedEntry[] = [];
|
||||
|
||||
while(current) {
|
||||
if (current.task) {
|
||||
plans.push({
|
||||
type: 'task',
|
||||
name: current.task?.name || 'start',
|
||||
external: current.task?.external,
|
||||
start: new Date(
|
||||
current.time.start.getTime()
|
||||
+ (current.transition?.time || 0),
|
||||
),
|
||||
name: current.task?.title || 'start',
|
||||
start: timeUtils.add(current.time.start, (current.transition?.time || 0)),
|
||||
end: current.time.end,
|
||||
score: current.score,
|
||||
})
|
||||
@@ -23,10 +19,7 @@ const constructDay = (node: GraphNode) => {
|
||||
plans.push({
|
||||
type: 'transition',
|
||||
start: current.time.start,
|
||||
end: new Date(
|
||||
current.time.start.getTime()
|
||||
+ current.transition.time,
|
||||
),
|
||||
end: timeUtils.add(current.time.start, current.transition.time),
|
||||
from: current.transition.from,
|
||||
to: current.transition.to,
|
||||
})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { GraphNode, Context } from '#/types/graph';
|
||||
import { Transition } from '#/types/location';
|
||||
import { Task } from '#/types/task';
|
||||
import { Task, Time, timeUtils } from '#/features/data';
|
||||
import { Transition } from '#/features/location';
|
||||
import { Context, GraphNode } from '../types';
|
||||
import { getRemainingLocations, listContainLocation } from './utils';
|
||||
|
||||
const DEFAULT_PRIORITY = 50;
|
||||
|
||||
const isDead = (impossible: Task[]) => {
|
||||
const missingRequered = impossible.find(t => t.required);
|
||||
return !!missingRequered;
|
||||
@@ -15,7 +17,7 @@ type GetImpossibleResult = {
|
||||
|
||||
export const getImpossible = (
|
||||
tasks: Task[],
|
||||
time: Date,
|
||||
time: Time,
|
||||
) => {
|
||||
const result: GetImpossibleResult = {
|
||||
remaining: [],
|
||||
@@ -23,7 +25,7 @@ export const getImpossible = (
|
||||
}
|
||||
|
||||
for (let task of tasks) {
|
||||
if (time > task.start.max) {
|
||||
if (timeUtils.largerThan(time, task.startTime.max)) {
|
||||
result.impossible.push(task);
|
||||
} else {
|
||||
result.remaining.push(task);
|
||||
@@ -47,17 +49,17 @@ const calculateScore = ({
|
||||
let score = 0;
|
||||
|
||||
tasks?.forEach((task) => {
|
||||
score += task.priority * 10;
|
||||
score += (task.priority || DEFAULT_PRIORITY) * 10;
|
||||
impossible.forEach((task) => {
|
||||
if (task.required) {
|
||||
score -= 10000 + (1 * task.priority);
|
||||
score -= 10000 + (1 * (task.priority || DEFAULT_PRIORITY));
|
||||
} else {
|
||||
score -= 100 + (1 * task.priority);
|
||||
score -= 100 + (1 * (task.priority || DEFAULT_PRIORITY));
|
||||
}
|
||||
});
|
||||
});
|
||||
if (transition) {
|
||||
const minutes = transition.time / 1000 / 60
|
||||
const minutes = transition.time;
|
||||
score -= 10 + (1 * minutes);
|
||||
}
|
||||
return score;
|
||||
@@ -71,7 +73,7 @@ const getNext = async (
|
||||
const remainingLocations = getRemainingLocations(currentNode.remainingTasks, currentNode.location);
|
||||
await Promise.all(remainingLocations.map(async(location) => {
|
||||
const transition = await context.getTransition(currentNode.location, location, currentNode.time.end);
|
||||
const endTime = new Date(currentNode.time.end.getTime() + transition.time);
|
||||
const endTime = timeUtils.add(currentNode.time.end, transition.time);
|
||||
const { remaining, impossible } = getImpossible(currentNode.remainingTasks, endTime);
|
||||
const score = calculateScore({
|
||||
transition,
|
||||
@@ -89,7 +91,7 @@ const getNext = async (
|
||||
score: currentNode.score + score,
|
||||
status: {
|
||||
completed: false,
|
||||
dead: isDead(impossible),
|
||||
dead: false, // TODO: fix isDead(impossible),
|
||||
},
|
||||
time: {
|
||||
start: currentNode.time.end,
|
||||
@@ -101,21 +103,14 @@ const getNext = async (
|
||||
const possibleTasks = currentNode.remainingTasks.filter(task => !task.locations || listContainLocation(task.locations, currentNode.location))
|
||||
await Promise.all(possibleTasks.map(async (orgTask) => {
|
||||
const task = {...orgTask};
|
||||
task.count = (task.count || 1) - 1
|
||||
let startTime = new Date(
|
||||
Math.max(
|
||||
currentNode.time.end.getTime(),
|
||||
task.start.min.getTime(),
|
||||
),
|
||||
);
|
||||
let startTime =
|
||||
timeUtils.max(
|
||||
currentNode.time.end,
|
||||
task.startTime.min,
|
||||
);
|
||||
const parentRemainging = currentNode.remainingTasks.filter(t => t !== orgTask);
|
||||
let endTime = new Date(startTime.getTime() + task.duration.min);
|
||||
const { remaining, impossible } = getImpossible(
|
||||
task.count > 0
|
||||
? [...parentRemainging, task]
|
||||
: parentRemainging,
|
||||
endTime,
|
||||
);
|
||||
let endTime = timeUtils.add(startTime, task.duration);
|
||||
const { remaining, impossible } = getImpossible(parentRemainging, endTime);
|
||||
const score = calculateScore({
|
||||
tasks: [task],
|
||||
impossible,
|
||||
|
||||
@@ -5,12 +5,12 @@ export const locationEqual = (a: UserLocation, b: UserLocation) => {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
if (a.location === b.location) {
|
||||
return true;
|
||||
}
|
||||
if (a.location && b.location && a.location.latitude === b.location.latitude && a.location.longitute === b.location.longitute) {
|
||||
return true;
|
||||
}
|
||||
// if (a.location === b.location) {
|
||||
// return true;
|
||||
// }
|
||||
// if (a.location && b.location && a.location.latitude === b.location.latitude && a.location.longitute === b.location.longitute) {
|
||||
// return true;
|
||||
// }
|
||||
if (a.title === b.title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createDataContext } from '#/utils/data-context';
|
||||
import { Time } from '../data';
|
||||
import { Strategies } from "./algorithm/build-graph";
|
||||
|
||||
type PlannerOptions = {
|
||||
strategy: Strategies;
|
||||
startTime: Time;
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -10,6 +12,7 @@ const {
|
||||
Provider: PlannerProvider,
|
||||
} = createDataContext<PlannerOptions>({
|
||||
createDefault: () => ({
|
||||
startTime: { hour: 7, minute: 0 },
|
||||
strategy: Strategies.firstComplet,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,22 +1,129 @@
|
||||
import { buildGraph, Status, Strategies } from "./algorithm/build-graph";
|
||||
import { useContext } from "react";
|
||||
import { PlanItem } from "#/types/plans";
|
||||
import { add } from 'date-fns';
|
||||
import { PlannerContext } from "./context";
|
||||
import { Task, UserLocation } from "../data";
|
||||
import { Task, Time, UserLocation } from "../data";
|
||||
import { useRoutines } from "../routines";
|
||||
import { useGoals } from "../goals/hooks";
|
||||
import { useAsyncCallback } from "../async";
|
||||
import { Day, dayUtils } from "../day";
|
||||
import { useGetOverride } from "../overrides";
|
||||
import { useGetAppointments } from "../appointments";
|
||||
import { useGetTransition } from "../location";
|
||||
import { PlannedEntry } from "./types";
|
||||
import { constructDay } from "./algorithm/construct-day";
|
||||
|
||||
export type UsePlanOptions = {
|
||||
export type PreparePlanOptions = {
|
||||
start: Day;
|
||||
end: Day;
|
||||
}
|
||||
|
||||
export type PlanOptions = PreparePlanOptions & {
|
||||
location: UserLocation;
|
||||
}
|
||||
|
||||
export type UsePlan = [
|
||||
(start?: Date) => Promise<any>,
|
||||
{
|
||||
result?: { agenda: PlanItem[], impossible: Task[] };
|
||||
status?: Status;
|
||||
loading: boolean;
|
||||
error?: any;
|
||||
export type PlanResultDay = {
|
||||
day: Day;
|
||||
start: Time;
|
||||
} & ({
|
||||
status: 'waiting',
|
||||
} | {
|
||||
status: 'running',
|
||||
nodes: number;
|
||||
strategy: Strategies;
|
||||
} | {
|
||||
status: 'done';
|
||||
nodes: number;
|
||||
strategy: Strategies;
|
||||
plan: PlannedEntry[];
|
||||
impossible: Task[];
|
||||
});
|
||||
|
||||
export type PlanResult = {
|
||||
impossible: Task[];
|
||||
days: {
|
||||
[day: string]: PlanResultDay;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const getDays = (start: Day, end: Day): Day[] => {
|
||||
const result: Day[] = [];
|
||||
let currentDate = dayUtils.dayToDate(start);
|
||||
const stopDate = dayUtils.dayToDate(end);
|
||||
while (currentDate <= stopDate) {
|
||||
result.push(dayUtils.dateToDay(currentDate));
|
||||
currentDate = add(currentDate, {
|
||||
days: 1,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const firstValue = <T>(...args: (T | undefined)[]): T => {
|
||||
for (let arg of args) {
|
||||
if (typeof arg !== 'undefined') {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
return undefined as unknown as T;
|
||||
}
|
||||
|
||||
export const useOptions = () => {
|
||||
const { data } = useContext(PlannerContext);
|
||||
return data;
|
||||
}
|
||||
|
||||
export const useSetOptions = () => {
|
||||
const { setData } = useContext(PlannerContext);
|
||||
return setData;
|
||||
}
|
||||
|
||||
const usePreparePlan = () => {
|
||||
const routines = useRoutines();
|
||||
const goals = useGoals();
|
||||
const getOverrides = useGetOverride();
|
||||
const [getAppontments] = useGetAppointments();
|
||||
|
||||
const preparePlan = useAsyncCallback(
|
||||
async ({ start, end }: PreparePlanOptions) => {
|
||||
const days = await Promise.all(getDays(start, end).map(async (day) => {
|
||||
const overrides = await getOverrides(day);
|
||||
const start: Time = firstValue(overrides.startTime, { hour: 7, minute: 0 });
|
||||
const appointments = await getAppontments(day);
|
||||
const tasks = [...routines, ...appointments].map<Task | undefined>((task) => {
|
||||
const override = overrides.tasks[task.id];
|
||||
if (override?.enabled === false) {
|
||||
return undefined;
|
||||
}
|
||||
const result: Task = {
|
||||
...task,
|
||||
startTime: {
|
||||
min: firstValue(override?.startMin, task.startTime.min),
|
||||
max: firstValue(override?.startMax, task.startTime.max),
|
||||
},
|
||||
duration: firstValue(override?.duration, task.duration),
|
||||
required: firstValue(override?.required, task.required),
|
||||
}
|
||||
return result;
|
||||
}).filter(Boolean).map(a => a as Exclude<typeof a, undefined>);
|
||||
|
||||
return {
|
||||
day,
|
||||
start,
|
||||
tasks,
|
||||
}
|
||||
|
||||
}));
|
||||
return {
|
||||
goals: [...goals],
|
||||
days,
|
||||
}
|
||||
},
|
||||
[routines, goals, getOverrides, getAppontments],
|
||||
);
|
||||
|
||||
return preparePlan;
|
||||
}
|
||||
|
||||
export const usePlanOptions = () => {
|
||||
const { data } = useContext(PlannerContext);
|
||||
@@ -29,6 +136,87 @@ export const useSetPlanOptions = () => {
|
||||
}
|
||||
|
||||
export const usePlan = () => {
|
||||
|
||||
}
|
||||
const [preparePlan] = usePreparePlan();
|
||||
const getTransition = useGetTransition();
|
||||
const options = usePlanOptions();
|
||||
const createPlan = useAsyncCallback(
|
||||
async ({ location, ...prepareOptions}: PlanOptions) => {
|
||||
const prepared = await preparePlan(prepareOptions);
|
||||
let result: PlanResult = {
|
||||
impossible: [],
|
||||
days: prepared.days.reduce((output, current) => ({
|
||||
...output,
|
||||
[dayUtils.toId(current.day)]: {
|
||||
day: current.day,
|
||||
start: current.start,
|
||||
status: 'waiting',
|
||||
},
|
||||
}), {} as {[name: string]: PlanResultDay})
|
||||
}
|
||||
const update = (next: PlanResult) => {
|
||||
result = next;
|
||||
}
|
||||
for (let day of prepared.days) {
|
||||
const id = dayUtils.toId(day.day);
|
||||
const dayGoal = prepared.goals;
|
||||
const graph = await buildGraph({
|
||||
location,
|
||||
time: day.start,
|
||||
tasks: [...day.tasks, ...dayGoal],
|
||||
strategy: options.strategy,
|
||||
context: {
|
||||
getTransition,
|
||||
},
|
||||
callback: (status) => {
|
||||
update({
|
||||
...result,
|
||||
days: {
|
||||
...result.days,
|
||||
[id]: {
|
||||
day: day.day,
|
||||
start: day.start,
|
||||
status: 'running',
|
||||
nodes: status.nodes,
|
||||
strategy: status.strategy,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
const [winner] = graph;
|
||||
if (!winner) {
|
||||
continue;
|
||||
}
|
||||
const plan = constructDay(winner);
|
||||
update({
|
||||
...result,
|
||||
days: {
|
||||
...result.days,
|
||||
[id]: {
|
||||
...result.days[id],
|
||||
impossible: winner.impossibeTasks,
|
||||
status: 'done',
|
||||
plan,
|
||||
}
|
||||
}
|
||||
})
|
||||
prepared.goals = prepared.goals.filter((goal) => {
|
||||
if (!dayGoal.find(d => d.id === goal.id)) {
|
||||
return true;
|
||||
}
|
||||
if (!winner.impossibeTasks.find(d => d.id === goal.id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
impossible: prepared.goals,
|
||||
};
|
||||
},
|
||||
[preparePlan, getTransition, options],
|
||||
);
|
||||
return createPlan;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Task, Time, UserLocation } from "../data";
|
||||
import { GetTransition, Transition } from "../location";
|
||||
|
||||
type Context = {
|
||||
getTransition: GetTransition;
|
||||
@@ -6,20 +8,22 @@ type Context = {
|
||||
export type PlannedTask = {
|
||||
type: 'task';
|
||||
name: string;
|
||||
start: Date;
|
||||
start: Time;
|
||||
external?: boolean;
|
||||
end: Date;
|
||||
end: Time;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export type PlannedTransition = {
|
||||
type: 'transition';
|
||||
start: Date;
|
||||
end: Date;
|
||||
start: Time;
|
||||
end: Time;
|
||||
from: UserLocation;
|
||||
to: UserLocation;
|
||||
};
|
||||
|
||||
export type PlannedEntry = PlannedTask | PlannedTransition;
|
||||
|
||||
type GraphNode = {
|
||||
location: UserLocation;
|
||||
task?: Task;
|
||||
@@ -29,8 +33,8 @@ type GraphNode = {
|
||||
impossibeTasks: Task[];
|
||||
score: number;
|
||||
time: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
start: Time;
|
||||
end: Time;
|
||||
};
|
||||
status: {
|
||||
dead: boolean;
|
||||
|
||||
Reference in New Issue
Block a user