@anthusai/papyrus 5.43.0-next.46 → 5.43.0-next.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1571,6 +1571,15 @@ const schema = a.schema({
1571
1571
  curationStatusReason: a.string(),
1572
1572
  newsroomFeedKey: a.string(),
1573
1573
  reviewedFeedKey: a.string(),
1574
+ // The References page reads the feed through two indexes (docs/reference-feed-indexes.md). `feedStateKey` is
1575
+ // "<corpusId>#unrated" or "<corpusId>#rated" for a current, not archived Reference (null otherwise: it is in no feed).
1576
+ // `predictionSortKey` orders Best bets, `feedArrivalAt` orders Newest. The prediction is the Cyclotron's ORIGINAL
1577
+ // P(accept), written once, and never rewritten by a vote.
1578
+ feedStateKey: a.string(),
1579
+ feedArrivalAt: a.datetime(),
1580
+ predictedAcceptProbability: a.float(),
1581
+ predictionDecidedAt: a.datetime(),
1582
+ predictionSortKey: a.string(),
1574
1583
  updatedAt: a.datetime(),
1575
1584
  })
1576
1585
  .secondaryIndexes((index) => [
@@ -1583,6 +1592,8 @@ const schema = a.schema({
1583
1592
  index("curationStatus").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusAndUpdatedAt"),
1584
1593
  index("curationStatusKey").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusKeyAndUpdatedAt"),
1585
1594
  index("reviewedFeedKey").sortKeys(["updatedAt"]).queryField("listReferencesByReviewedFeedAndUpdatedAt"),
1595
+ index("feedStateKey").sortKeys(["predictionSortKey"]).queryField("listReferencesByFeedStateAndPrediction"),
1596
+ index("feedStateKey").sortKeys(["feedArrivalAt"]).queryField("listReferencesByFeedStateAndArrival"),
1586
1597
  ])
1587
1598
  .authorization((allow) => [
1588
1599
  allow.groups(categoryWriteGroups).to(["read"]),
@@ -1684,6 +1684,15 @@ const schema = a.schema({
1684
1684
  curationStatusReason: a.string(),
1685
1685
  newsroomFeedKey: a.string(),
1686
1686
  reviewedFeedKey: a.string(),
1687
+ // The References page reads the feed through two indexes (docs/reference-feed-indexes.md). `feedStateKey` is
1688
+ // "<corpusId>#unrated" or "<corpusId>#rated" for a current, not archived Reference (null otherwise: it is in no feed).
1689
+ // `predictionSortKey` orders Best bets, `feedArrivalAt` orders Newest. The prediction is the Cyclotron's ORIGINAL
1690
+ // P(accept), written once, and never rewritten by a vote.
1691
+ feedStateKey: a.string(),
1692
+ feedArrivalAt: a.datetime(),
1693
+ predictedAcceptProbability: a.float(),
1694
+ predictionDecidedAt: a.datetime(),
1695
+ predictionSortKey: a.string(),
1687
1696
  updatedAt: a.datetime(),
1688
1697
  })
1689
1698
  .secondaryIndexes((index) => [
@@ -1696,6 +1705,8 @@ const schema = a.schema({
1696
1705
  index("curationStatus").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusAndUpdatedAt"),
1697
1706
  index("curationStatusKey").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusKeyAndUpdatedAt"),
1698
1707
  index("reviewedFeedKey").sortKeys(["updatedAt"]).queryField("listReferencesByReviewedFeedAndUpdatedAt"),
1708
+ index("feedStateKey").sortKeys(["predictionSortKey"]).queryField("listReferencesByFeedStateAndPrediction"),
1709
+ index("feedStateKey").sortKeys(["feedArrivalAt"]).queryField("listReferencesByFeedStateAndArrival"),
1699
1710
  ])
1700
1711
  .authorization((allow) => [
1701
1712
  allow.groups(categoryWriteGroups).to(["read"]),
@@ -1477,6 +1477,15 @@ function buildBaseDefinitions(includeEditions) {
1477
1477
  curationStatusReason: a.string(),
1478
1478
  newsroomFeedKey: a.string(),
1479
1479
  reviewedFeedKey: a.string(),
1480
+ // The References page reads the feed through two indexes (docs/reference-feed-indexes.md). `feedStateKey` is
1481
+ // "<corpusId>#unrated" or "<corpusId>#rated" for a current, not archived Reference (null otherwise: it is in no feed).
1482
+ // `predictionSortKey` orders Best bets, `feedArrivalAt` orders Newest. The prediction is the Cyclotron's ORIGINAL
1483
+ // P(accept), written once, and never rewritten by a vote.
1484
+ feedStateKey: a.string(),
1485
+ feedArrivalAt: a.datetime(),
1486
+ predictedAcceptProbability: a.float(),
1487
+ predictionDecidedAt: a.datetime(),
1488
+ predictionSortKey: a.string(),
1480
1489
  updatedAt: a.datetime(),
1481
1490
  })
1482
1491
  .secondaryIndexes((index) => [
@@ -1489,6 +1498,8 @@ function buildBaseDefinitions(includeEditions) {
1489
1498
  index("curationStatus").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusAndUpdatedAt"),
1490
1499
  index("curationStatusKey").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusKeyAndUpdatedAt"),
1491
1500
  index("reviewedFeedKey").sortKeys(["updatedAt"]).queryField("listReferencesByReviewedFeedAndUpdatedAt"),
1501
+ index("feedStateKey").sortKeys(["predictionSortKey"]).queryField("listReferencesByFeedStateAndPrediction"),
1502
+ index("feedStateKey").sortKeys(["feedArrivalAt"]).queryField("listReferencesByFeedStateAndArrival"),
1492
1503
  ])
1493
1504
  .authorization((allow) => [
1494
1505
  allow.groups(categoryWriteGroups).to(["read"]),
@@ -1588,6 +1588,15 @@ const definitions = {
1588
1588
  curationStatusReason: a.string(),
1589
1589
  newsroomFeedKey: a.string(),
1590
1590
  reviewedFeedKey: a.string(),
1591
+ // The References page reads the feed through two indexes (docs/reference-feed-indexes.md). `feedStateKey` is
1592
+ // "<corpusId>#unrated" or "<corpusId>#rated" for a current, not archived Reference (null otherwise: it is in no feed).
1593
+ // `predictionSortKey` orders Best bets, `feedArrivalAt` orders Newest. The prediction is the Cyclotron's ORIGINAL
1594
+ // P(accept), written once, and never rewritten by a vote.
1595
+ feedStateKey: a.string(),
1596
+ feedArrivalAt: a.datetime(),
1597
+ predictedAcceptProbability: a.float(),
1598
+ predictionDecidedAt: a.datetime(),
1599
+ predictionSortKey: a.string(),
1591
1600
  updatedAt: a.datetime(),
1592
1601
  })
1593
1602
  .secondaryIndexes((index) => [
@@ -1600,6 +1609,8 @@ const definitions = {
1600
1609
  index("curationStatus").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusAndUpdatedAt"),
1601
1610
  index("curationStatusKey").sortKeys(["curationStatusUpdatedAt"]).queryField("listReferencesByCurationStatusKeyAndUpdatedAt"),
1602
1611
  index("reviewedFeedKey").sortKeys(["updatedAt"]).queryField("listReferencesByReviewedFeedAndUpdatedAt"),
1612
+ index("feedStateKey").sortKeys(["predictionSortKey"]).queryField("listReferencesByFeedStateAndPrediction"),
1613
+ index("feedStateKey").sortKeys(["feedArrivalAt"]).queryField("listReferencesByFeedStateAndArrival"),
1603
1614
  ])
1604
1615
  .authorization((allow) => [
1605
1616
  allow.groups(categoryWriteGroups).to(["read"]),
@@ -25,6 +25,7 @@ import {
25
25
  type DelegationStore,
26
26
  } from "../shared/curation-delegation-flow";
27
27
  import { rejectionReasonCodeForWrittenReason } from "../../../lib/reference-policy";
28
+ import { referenceFeedKeys, referenceFeedStateKey } from "../../../lib/reference-feed-keys";
28
29
  import { claimGroups } from "../../../lib/staff-groups";
29
30
 
30
31
  type ReviewHandler = Schema["reviewSteeringProposal"]["functionHandler"];
@@ -256,6 +257,8 @@ async function reviewReferenceCuration(
256
257
  curationStatusReason: note,
257
258
  newsroomFeedKey: reference.newsroomFeedKey ?? "references",
258
259
  reviewedFeedKey: reviewedFeedKeyForStatus(nextStatus),
260
+ // A vote moves the Reference between the unrated and rated feeds; its original prediction and arrival stay as they were.
261
+ feedStateKey: referenceFeedStateKey({ ...reference, curationStatus: nextStatus }),
259
262
  updatedAt: now,
260
263
  }),
261
264
  "update Reference curation status",
@@ -642,6 +645,8 @@ async function setReferenceQualityRating(event: Parameters<ReferenceQualityHandl
642
645
  curationStatusReason: note,
643
646
  newsroomFeedKey: reference.newsroomFeedKey ?? "references",
644
647
  reviewedFeedKey: reviewedFeedKeyForStatus(nextStatus),
648
+ // A vote moves the Reference between the unrated and rated feeds; its original prediction and arrival stay as they were.
649
+ feedStateKey: referenceFeedStateKey({ ...reference, curationStatus: nextStatus }),
645
650
  updatedAt: now,
646
651
  }),
647
652
  "update Reference quality status",
@@ -818,6 +823,7 @@ async function moveReferenceCorpus(event: Parameters<MoveReferenceCorpusHandler>
818
823
  client.models.Reference.update({
819
824
  id: normalizeRequiredString(current.id, "reference.id"),
820
825
  versionState: "superseded",
826
+ feedStateKey: null, // a superseded version is in no feed
821
827
  updatedAt: now,
822
828
  }),
823
829
  "supersede current Reference version",
@@ -857,6 +863,19 @@ async function moveReferenceCorpus(event: Parameters<MoveReferenceCorpusHandler>
857
863
  curationStatusReason: normalizeOptionalString(current.curationStatusReason),
858
864
  newsroomFeedKey: normalizeOptionalString(current.newsroomFeedKey) ?? "references",
859
865
  reviewedFeedKey: normalizeOptionalString(current.reviewedFeedKey) ?? reviewedFeedKeyForStatus(curationStatus),
866
+ predictedAcceptProbability: typeof current.predictedAcceptProbability === "number" ? current.predictedAcceptProbability : null,
867
+ predictionDecidedAt: normalizeOptionalString(current.predictionDecidedAt),
868
+ ...referenceFeedKeys({
869
+ id: nextReferenceId,
870
+ corpusId: targetCorpusId,
871
+ curationStatus,
872
+ versionState: "current",
873
+ predictedAcceptProbability: typeof current.predictedAcceptProbability === "number" ? current.predictedAcceptProbability : null,
874
+ createdAt: normalizeOptionalString(current.createdAt) ?? now,
875
+ importedAt: normalizeOptionalString(current.importedAt),
876
+ retrievedAt: normalizeOptionalString(current.retrievedAt),
877
+ sourcePublishedAt: normalizeOptionalString(current.sourcePublishedAt),
878
+ }),
860
879
  updatedAt: now,
861
880
  }),
862
881
  "create moved Reference version",
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The keys the References page reads through (docs/reference-feed-indexes.md). The Python twin is
3
+ * src/papyrus_content/reference_feed_keys.py; scripts/fixtures/reference-feed-key-vectors.json holds the vectors both test.
4
+ *
5
+ * `feedStateKey` is "<corpusId>#unrated" (pending) or "<corpusId>#rated" (accepted or rejected); a Reference that is not the current
6
+ * version, or is archived, has none and is in neither feed. `predictionSortKey` is the Cyclotron's ORIGINAL P(accept), then arrival, then
7
+ * id; a Reference nobody predicted sorts as 0.5. A vote changes `feedStateKey` and nothing else here.
8
+ */
9
+ import { normalizeReferenceCurationStatus } from "./reference-policy";
10
+
11
+ export const UNDECIDED_PROBABILITY = 0.5;
12
+ const EPOCH = "1970-01-01T00:00:00.000Z";
13
+ const ARRIVAL_FIELDS = ["createdAt", "importedAt", "retrievedAt", "sourcePublishedAt", "versionCreatedAt"] as const;
14
+
15
+ export type ReferenceFeedKeyInput = {
16
+ id: string;
17
+ corpusId?: string | null;
18
+ curationStatus?: string | null;
19
+ versionState?: string | null;
20
+ predictedAcceptProbability?: number | null;
21
+ createdAt?: string | null;
22
+ importedAt?: string | null;
23
+ retrievedAt?: string | null;
24
+ sourcePublishedAt?: string | null;
25
+ versionCreatedAt?: string | null;
26
+ };
27
+
28
+ export type ReferenceFeedKeys = {
29
+ feedStateKey: string | null;
30
+ feedArrivalAt: string;
31
+ predictionSortKey: string;
32
+ };
33
+
34
+ function instant(value: string | null | undefined): string | null {
35
+ if (!value) return null;
36
+ const parsed = Date.parse(value);
37
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
38
+ }
39
+
40
+ export function referenceFeedArrivalAt(reference: ReferenceFeedKeyInput): string {
41
+ for (const field of ARRIVAL_FIELDS) {
42
+ const found = instant(reference[field]);
43
+ if (found) return found;
44
+ }
45
+ return EPOCH;
46
+ }
47
+
48
+ export function referenceFeedStateKey(reference: ReferenceFeedKeyInput): string | null {
49
+ if (reference.versionState && reference.versionState !== "current") return null;
50
+ if (!reference.corpusId) return null;
51
+ const status = normalizeReferenceCurationStatus(reference.curationStatus);
52
+ if (status === "archived") return null;
53
+ return `${reference.corpusId}#${status === "pending" ? "unrated" : "rated"}`;
54
+ }
55
+
56
+ export function predictionSortKeyFor(probability: number | null | undefined, arrival: string, referenceId: string): string {
57
+ const chosen = probability === null || probability === undefined ? UNDECIDED_PROBABILITY : Math.min(1, Math.max(0, probability));
58
+ return `${chosen.toFixed(6)}#${arrival}#${referenceId}`;
59
+ }
60
+
61
+ /** The three feed keys for a Reference as it stands. `probability` overrides the one stored on it. */
62
+ export function referenceFeedKeys(reference: ReferenceFeedKeyInput, probability?: number | null): ReferenceFeedKeys {
63
+ const arrival = referenceFeedArrivalAt(reference);
64
+ return {
65
+ feedStateKey: referenceFeedStateKey(reference),
66
+ feedArrivalAt: arrival,
67
+ predictionSortKey: predictionSortKeyFor(probability ?? reference.predictedAcceptProbability, arrival, reference.id),
68
+ };
69
+ }
package/package.json CHANGED
@@ -354,6 +354,7 @@
354
354
  "./lib/reference-capture": "./lib/reference-capture.ts",
355
355
  "./lib/reference-display": "./lib/reference-display.ts",
356
356
  "./lib/reference-feed-cache": "./lib/reference-feed-cache.ts",
357
+ "./lib/reference-feed-keys": "./lib/reference-feed-keys.ts",
357
358
  "./lib/reference-feed-ranking": "./lib/reference-feed-ranking.ts",
358
359
  "./lib/reference-policy": "./lib/reference-policy.ts",
359
360
  "./lib/reference-preview": "./lib/reference-preview.ts",
@@ -516,7 +517,7 @@
516
517
  "optional": true
517
518
  }
518
519
  },
519
- "version": "5.43.0-next.46",
520
+ "version": "5.43.0-next.47",
520
521
  "dependencies": {
521
522
  "@aws-amplify/adapter-nextjs": "^1.7.3",
522
523
  "@aws-crypto/sha256-js": "5.2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.43.0-next.46",
2
+ "version": "5.43.0-next.47",
3
3
  "routes": [
4
4
  {
5
5
  "file": "app/%5F_preview/[[...path]]/route.ts",