Files
Momento/architectural-grid/src/services/temporalService.ts
Antigravity e2672cd2c2
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped
feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 14:27:29 +00:00

77 lines
2.6 KiB
TypeScript

import { Note, NoteAccessLog, NotePrediction } from '../types';
/**
* Simulates finding the dominant frequency in access logs for a specific note
* returning the period in days.
*/
export function detectAccessCycle(logs: NoteAccessLog[]): number | null {
if (logs.length < 5) return null;
const accessDays = logs
.map(log => new Date(log.accessedAt).getTime())
.sort((a, b) => a - b);
const intervals: number[] = [];
for (let i = 1; i < accessDays.length; i++) {
intervals.push((accessDays[i] - accessDays[i - 1]) / (1000 * 60 * 60 * 24));
}
// Simple heuristic: if intervals are consistently around a value, that's our cycle
// We'll calculate the median interval
const sortedIntervals = [...intervals].sort((a, b) => a - b);
const median = sortedIntervals[Math.floor(sortedIntervals.length / 2)];
// Check if enough intervals are close to median
const withinThreshold = intervals.filter(v => Math.abs(v - median) < Math.max(2, median * 0.2));
if (withinThreshold.length >= intervals.length * 0.6) {
return median;
}
return null;
}
export function predictNextAccess(note: Note, logs: NoteAccessLog[]): NotePrediction | null {
const cycleDays = detectAccessCycle(logs);
if (!cycleDays) return null;
const lastAccess = new Date(logs[logs.length - 1].accessedAt);
const nextAccessDate = new Date(lastAccess.getTime() + cycleDays * 24 * 60 * 60 * 1000);
const now = new Date();
const daysUntilNext = (nextAccessDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
// Only predict if it's coming up in the next 2 weeks
if (daysUntilNext > 0 && daysUntilNext < 14) {
return {
noteId: note.id,
predictedRelevanceDate: nextAccessDate.toISOString(),
confidence: 0.7,
reason: `Historical access pattern suggests a ${Math.round(cycleDays)}-day cycle.`,
generatedAt: now.toISOString()
};
}
return null;
}
export function getCoaccessedNotes(baseNoteId: string, logs: NoteAccessLog[], allNotes: Note[]): Note[] {
const WINDOW_MS = 30 * 60 * 1000; // 30 minutes
const baseNoteLogs = logs.filter(l => l.noteId === baseNoteId);
const coaccessedIds = new Set<string>();
baseNoteLogs.forEach(baseLog => {
const baseTime = new Date(baseLog.accessedAt).getTime();
logs.forEach(otherLog => {
if (otherLog.noteId === baseNoteId) return;
const otherTime = new Date(otherLog.accessedAt).getTime();
if (Math.abs(baseTime - otherTime) < WINDOW_MS) {
coaccessedIds.add(otherLog.noteId);
}
});
});
return allNotes.filter(n => coaccessedIds.has(n.id));
}