SPB Git forge
7commits 1branches 0releases
229.0 KBsize
maindefault branch
12 days agolast push
TypeScript 91.8% HTML 3.2% JavaScript 3% SQL 1.4% CSS 0.7%
4.1 KB · 70 lines typescript
Raw Blame History
1import { describe, expect, it } from "vitest";2import type { ObservedEntity, PageState, SemanticAction } from "@src/shared";3import { WorldModel } from "./WorldModel.ts";4import { LoopDetector } from "./LoopDetector.ts";5import { scoreActions, relevanceOf } from "./InformationGain.ts";6import { HeuristicPlanner } from "./planner.ts";78const ent = (ref: string, name: string, type: ObservedEntity["type"] = "video"): ObservedEntity => ({ ref, type, platform: "youtube", platform_id: ref, url: `https://youtube.com/watch?v=${ref}`, name, fields: {}, provenance: [{ surface: "dom", confidence: 0.9 }, { surface: "network", confidence: 0.95 }], fingerprint: `youtube:${type}:${ref}` });910const state = (entities: ObservedEntity[], actions: SemanticAction[]): PageState => ({ url: "https://youtube.com/results?search_query=ai+quebec", title: "results", platform: "youtube", classification: { page_type: "SEARCH_RESULTS", confidence: 0.95, signals: [] }, entities, actions, media: [], summary_text: "AI Québec results", fingerprint: "fp", captured_at: new Date().toISOString() });1112describe("information gain + planner", () => {13  it("prefers relevant, unvisited, multi-surface entities and penalizes visited ones", async () => {14    const world = new WorldModel();15    const e1 = ent("v1", "Intelligence artificielle au Québec : table ronde");16    const e2 = ent("v2", "Recette de tarte aux pommes");17    world.observe([e1, e2], 0);18    world.markVisited(e2.fingerprint, e2.url);19    const actions: SemanticAction[] = [20      { id: "A1", type: "OPEN_VIDEO", target_ref: "v1", target_url: e1.url, label: "open v1", cost: 2 },21      { id: "A2", type: "OPEN_VIDEO", target_ref: "v2", target_url: e2.url, label: "open v2", cost: 2 },22      { id: "A3", type: "SCROLL_DOWN", label: "scroll", cost: 1 },23      { id: "A4", type: "END_SESSION", label: "end", cost: 0.5 },24    ];25    const st = state([e1, e2], actions);26    const scores = scoreActions({ goal: "intelligence artificielle Québec", mode: "research", world, state: st, recentActionTypes: [], recentUrls: [], stepsWithoutNewEntities: 0 }, actions);27    expect(scores[0]!.action_id).toBe("A1");28    const visited = scores.find((s) => s.action_id === "A2")!;29    expect(visited.penalties).toContain("already_seen");30    expect(scores.at(-1)!.action_id).toBe("A4");31    const d = await new HeuristicPlanner().plan({ goal: "intelligence artificielle Québec", mode: "research", world, state: st, recentActionTypes: [], recentUrls: [], stepsWithoutNewEntities: 0 }, 1);32    expect(d.chosen_action.id).toBe("A1");33    expect(d.reason.length).toBeGreaterThan(0);34  });3536  it("relevance follows lexical overlap with the goal", () => {37    expect(relevanceOf("AI Quebec panel", "AI Quebec")).toBeGreaterThan(relevanceOf("Apple pie recipe", "AI Quebec"));38  });3940  it("observe mode suppresses navigation", () => {41    const world = new WorldModel();42    const e1 = ent("v1", "AI");43    const actions: SemanticAction[] = [{ id: "A1", type: "OPEN_VIDEO", target_ref: "v1", target_url: e1.url, label: "open", cost: 2 }, { id: "A2", type: "SCROLL_DOWN", label: "scroll", cost: 1 }];44    const scores = scoreActions({ goal: "AI", mode: "observe", world, state: state([e1], actions), recentActionTypes: [], recentUrls: [], stepsWithoutNewEntities: 0 }, actions);45    expect(scores[0]!.action_id).toBe("A2");46  });47});4849describe("LoopDetector", () => {50  it("detects ABAB", () => {51    const l = new LoopDetector();52    for (const f of ["a", "b", "a", "b"]) l.record(f, "OPEN_VIDEO:x");53    expect(l.detect().loop).toBe(true);54  });55  it("tolerates repeated scrolling", () => {56    const l = new LoopDetector();57    for (let i = 0; i < 6; i++) l.record(`p${i}`, "SCROLL_DOWN:");58    expect(l.detect().loop).toBe(false);59  });60});6162describe("WorldModel novelty", () => {63  it("scores near-duplicates as low novelty", () => {64    const w = new WorldModel();65    w.observe([ent("a", "Intelligence artificielle Québec conférence 2026")], 0);66    expect(w.novelty(ent("b", "Intelligence artificielle Québec conférence 2026 partie 2"))).toBeLessThan(0.5);67    expect(w.novelty(ent("c", "Tarte aux pommes maison"))).toBeGreaterThan(0.9);68  });69});70