ai generated tasks check for duplicates, existing streaks, and more before creation
This commit is contained in:
@@ -15,6 +15,30 @@ const NONSENSE_LABEL_PATTERNS = [
|
||||
/^more (sleep|movement|hydration|mindfulness)$/i,
|
||||
];
|
||||
|
||||
const VAGUE_GERUND_LABEL_PATTERNS = [
|
||||
/^(running|swimming|walking|jogging|reading|stretching|cooking|hiking|biking|cycling|meditating|exercising|studying|cleaning)$/i,
|
||||
/^go (running|swimming|walking|jogging|reading|stretching|cooking|hiking|biking|cycling|meditating|exercising)$/i,
|
||||
/^try (running|swimming|walking|jogging|reading|stretching|cooking|hiking|biking|cycling|meditating|exercising)$/i,
|
||||
/^do (running|swimming|walking|jogging|reading|stretching|cooking|hiking|biking|cycling|meditating|exercising)$/i,
|
||||
];
|
||||
|
||||
const ACTIVITY_KEY_PATTERNS: Array<{ key: string; pattern: RegExp }> = [
|
||||
{ key: "run", pattern: /\b(run|running|jog|jogging)\b/i },
|
||||
{ key: "swim", pattern: /\b(swim|swimming|pool)\b/i },
|
||||
{ key: "walk", pattern: /\b(walk|walking|hike|hiking)\b/i },
|
||||
{ key: "yoga", pattern: /\byoga\b/i },
|
||||
{ key: "workout", pattern: /\b(workout|gym|exercise|exercising)\b/i },
|
||||
{ key: "bike", pattern: /\b(bike|biking|cycle|cycling)\b/i },
|
||||
{ key: "meditate", pattern: /\b(meditat\w*)\b/i },
|
||||
{ key: "read", pattern: /\b(read|reading)\b/i },
|
||||
{ key: "stretch", pattern: /\b(stretch|stretching)\b/i },
|
||||
{ key: "cook", pattern: /\b(cook|cooking|meal prep)\b/i },
|
||||
{ key: "water", pattern: /\b(water|hydrate|hydration)\b/i },
|
||||
{ key: "sleep", pattern: /\b(sleep|wind down|wind-down|bedtime)\b/i },
|
||||
{ key: "screen", pattern: /\b(screen|social media|scroll|scrolling)\b/i },
|
||||
{ key: "breathe", pattern: /\b(breathe|breathing|meditat\w*)\b/i },
|
||||
];
|
||||
|
||||
const ACTION_VERB_PATTERN =
|
||||
/\b(drink|drank|go|went|take|took|limit|limited|skip|skipped|complete|completed|eat|ate|meditate|stretch|stretched|walk|walked|run|ran|swim|swam|read|cook|cooked|wind|avoid|avoided|try|tried|start|started|finish|finished|do|did|get|got|fill|filled|choose|chose|cut|breathe|breathed|hike|hiked|bike|biked|workout|exercise|exercised|journal|write|wrote|call|called|tidy|tidied|clean|cleaned|rest|rested|hydrate|hydrated|move|moved|practice|practiced|put|set|turn|leave|left|join|joined|attend|attended|prepare|prepared|pack|packed|plan|planned|track|tracked|check|checked|brush|brushed|floss|flossed|shower|showered|bathe|bathed|sleep|slept|refill|refilled)\b/i;
|
||||
|
||||
@@ -53,6 +77,10 @@ export function isPlausibleTaskLabel(label: string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VAGUE_GERUND_LABEL_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (words.length === 2 && ABSTRACT_FOCUS_TOPICS.has(words[1])) {
|
||||
return false;
|
||||
}
|
||||
@@ -71,3 +99,101 @@ function normalizeForValidation(label: string) {
|
||||
export function filterPlausibleTaskLabels(labels: string[]) {
|
||||
return labels.filter(isPlausibleTaskLabel);
|
||||
}
|
||||
|
||||
function getActivityKey(label: string) {
|
||||
for (const { key, pattern } of ACTIVITY_KEY_PATTERNS) {
|
||||
if (pattern.test(label)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function labelSpecificityScore(label: string) {
|
||||
let score = label.length;
|
||||
|
||||
if (/^go for a\b/i.test(label)) {
|
||||
score += 25;
|
||||
}
|
||||
|
||||
if (/\b\d+\b/.test(label)) {
|
||||
score += 8;
|
||||
}
|
||||
|
||||
if (/\b(minute|minutes|hour|hours|glass|bottle)\b/i.test(label)) {
|
||||
score += 6;
|
||||
}
|
||||
|
||||
if (VAGUE_GERUND_LABEL_PATTERNS.some((pattern) => pattern.test(label))) {
|
||||
score -= 40;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export function dedupeSimilarTaskLabels(labels: string[]) {
|
||||
const ungrouped: string[] = [];
|
||||
const grouped = new Map<string, string>();
|
||||
|
||||
for (const label of labels) {
|
||||
const activityKey = getActivityKey(label);
|
||||
|
||||
if (!activityKey) {
|
||||
ungrouped.push(label);
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = grouped.get(activityKey);
|
||||
|
||||
if (!existing) {
|
||||
grouped.set(activityKey, label);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (labelSpecificityScore(label) > labelSpecificityScore(existing)) {
|
||||
grouped.set(activityKey, label);
|
||||
}
|
||||
}
|
||||
|
||||
const deduped = [...ungrouped, ...grouped.values()];
|
||||
|
||||
const seen = new Set<string>();
|
||||
|
||||
return deduped.filter((label) => {
|
||||
const key = label.toLowerCase();
|
||||
|
||||
if (seen.has(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function filterExcludedSimilarTaskLabels(
|
||||
labels: string[],
|
||||
excludeLabels: string[],
|
||||
) {
|
||||
if (excludeLabels.length === 0) {
|
||||
return labels;
|
||||
}
|
||||
|
||||
const excludedExact = new Set(excludeLabels.map((label) => label.toLowerCase()));
|
||||
const excludedActivityKeys = new Set(
|
||||
excludeLabels
|
||||
.map((label) => getActivityKey(label))
|
||||
.filter((key): key is string => key !== null),
|
||||
);
|
||||
|
||||
return labels.filter((label) => {
|
||||
if (excludedExact.has(label.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const activityKey = getActivityKey(label);
|
||||
|
||||
return !activityKey || !excludedActivityKeys.has(activityKey);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user