Skip to content

Filtering and ranking a list

from(items) starts an expression about a list. The same conditions given accepts filter it; a scale ranks it; take limits it. The model judges each item once. Partitioning, sorting, and limiting run locally.

.where(condition) states the filter. .and(), .or(), and .unless() refine it exactly as they do on a predicate, with the same three-valued logic. Each item is judged independently, and a false code predicate on an item drops it without a request for that item.

import { from } from "jevlish";
import { tickets } from "./support.js";
import { reportsProblem, saysResolved } from "./vocabulary.js";
const result = await from(tickets)
.seenAs((t) => ({ subject: t.subject, body: t.body }))
.where((t) => t.status === "open")
.and(reportsProblem)
.unless(saysResolved);
result.accepted; // Ticket[]: the condition resolved to true, in input order
result.rejected; // Ticket[]: resolved to false
result.uncertain; // Ticket[]: did not meet the policy; unresolved, not rejected
result.evidence.requests.length; // one request per item that needed the model

The result has three buckets, and every item lands in exactly one:

  • accepted: the condition resolved to true.
  • rejected: it resolved to false.
  • uncertain: the policy would not decide. These are not rejected. They are the items you have not yet made a decision about.

evidence covers the whole query: every request that went out and every judgment that was recorded, across all items.

.rankedBy(scale, order) adds a Score question for every item not rejected by code, in the same request as its semantic filter. After the filter resolves, scores are read only for accepted items and accepted is sorted by them. This avoids a second round trip, but an item the model later rejects or leaves uncertain was still scored. order is "highest first" (the default) or "lowest first".

import { from } from "jevlish";
import { tickets } from "./support.js";
import { disruption, reportsProblem } from "./vocabulary.js";
const queue = await from(tickets)
.seenAs((t) => ({ subject: t.subject, body: t.body }))
.where((t) => t.status === "open")
.and(reportsProblem)
.rankedBy(disruption, "highest first")
.take(5);
for (const { item, score, normalized } of queue.scored) {
item.id; // an accepted ticket, highest disruption first
score; // 0 .. disruption.top
normalized; // 0 .. 1
}
// Ranking alone, with no filter, scores every item.
const byDisruption = await from(tickets).rankedBy(disruption, "lowest first");
byDisruption.accepted;

A ranked result adds scored: each accepted item with its score (0 to scale.top) and normalized score (0 to 1), in ranked order. An item whose score does not meet policy.score.minConfidence moves to uncertain even if its filter passed.

from(items).rankedBy(scale) with no .where() scores everything.

.take(n) keeps the first n of accepted after ranking. It does not reduce what is asked. To spend less, narrow the list in code first, or put a code predicate in .where() so rejected rows never leave the process.

.seenAs(fn) on from projects each item, the same as on given, and goes before .where(). .withPolicy({...}) overrides thresholds either before or after the query is built.

.plan() on a query reports subjectCount, decidedByCode, and the requests that would be sent, with notes describing the filter, ranking, and limit. See Running and planning.