● Zion Boggan
repos/Docket/app/how-we-pick/page.tsx
zionboggan.com ↗
159 lines · tsx
History for this file →
1
import type { Metadata } from "next";
2
import Link from "next/link";
3
import { Card, PageHeader, SectionHeader } from "@/components/ui";
4
 
5
export const metadata: Metadata = {
6
  title: "How we pick",
7
  description:
8
    "The ranking algorithm and neutrality rules behind every item in the Docket register.",
9
};
10
 
11
export default function HowWePickPage() {
12
  return (
13
    <div
14
      className="mx-auto w-full max-w-[860px] px-6 sm:px-8"
15
      style={{
16
        paddingTop: "clamp(32px, 6vw, 56px)",
17
        paddingBottom: "clamp(48px, 8vw, 80px)",
18
      }}
19
    >
20
      <Link
21
        href="/"
22
        className="focus-ring tap no-underline hover:underline"
23
        style={{
24
          display: "inline-flex",
25
          alignItems: "center",
26
          gap: "8px",
27
          fontFamily: "var(--font-mono), ui-monospace, monospace",
28
          fontSize: "13px",
29
          letterSpacing: "0.08em",
30
          color: "var(--dk-signal)",
31
          marginBottom: "32px",
32
        }}
33
      >
34
        ← Home
35
      </Link>
36
      <PageHeader
37
        eyebrow="Trust · methodology"
38
        title="How we pick what to show"
39
        lead="Ranking is a function of official-action data only. No engagement, no coverage counts, no editorial pinning. The function is public."
40
      />
41
 
42
      {}
43
      <div className="mt-10">
44
        <SectionHeader
45
          eyebrow="Ranking"
46
          title="The four inputs"
47
          description="The score takes only these fields. A unit test asserts no other field is referenced."
48
        />
49
        <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
50
          <InputCard
51
            name="actionDate"
52
            desc="Date of the official legislative or spending action"
53
          />
54
          <InputCard
55
            name="dollarAmount"
56
            desc="Dollar magnitude of the action, where one exists"
57
          />
58
          <InputCard
59
            name="affectedEstimate"
60
            desc="People affected, per a CBO or agency estimate"
61
          />
62
          <InputCard
63
            name="actionType"
64
            desc="Introduced, reported, voted, signed, awarded, outlaid"
65
          />
66
        </div>
67
        <Card className="mt-4 p-5">
68
          <p className="label mb-2">Forbidden inputs</p>
69
          <ul className="grid gap-1.5 font-mono text-[12px] text-ash sm:grid-cols-2">
70
            <li>· news mention counts</li>
71
            <li>· social shares / engagement</li>
72
            <li>· click or view rates</li>
73
            <li>· “trending” signals</li>
74
            <li>· editorial pins</li>
75
            <li>· party or partisan valence</li>
76
          </ul>
77
        </Card>
78
      </div>
79
 
80
      {}
81
      <div className="mt-12">
82
        <SectionHeader
83
          eyebrow="Neutrality"
84
          title="The five rules"
85
          description="Encoded in the codebase as lint rules and tests, not just a style guide."
86
        />
87
        <ol className="space-y-3">
88
          {RULES.map((r, i) => (
89
            <li key={i}>
90
              <Card className="p-5">
91
                <div className="flex items-start gap-4">
92
                  <span
93
                    className="display mt-0.5 text-xl"
94
                    style={{ color: "var(--dk-ash)" }}
95
                    aria-hidden="true"
96
                  >
97
                    {String(i + 1).padStart(2, "0")}
98
                  </span>
99
                  <div>
100
                    <p className="font-mono text-[13px] text-paper">{r.title}</p>
101
                    <p className="prose-lead mt-1 text-ash">{r.body}</p>
102
                  </div>
103
                </div>
104
              </Card>
105
            </li>
106
          ))}
107
        </ol>
108
      </div>
109
 
110
      {}
111
      <div className="mt-12">
112
        <SectionHeader
113
          eyebrow="Visualization"
114
          title="How votes and parties are drawn"
115
        />
116
        <Card className="p-5">
117
          <ul className="space-y-2 font-mono text-[12px] text-ash">
118
            <li>· Party affiliation and vote splits are factual data and may be shown.</li>
119
            <li>· Tones are desaturated and of equal weight -no saturated red vs blue.</li>
120
            <li>· Default sort of member or vote lists is by vote, then alphabetical -not grouped by party.</li>
121
            <li>· Charts use a neutral yea / nay encoding where the question is not party.</li>
122
          </ul>
123
        </Card>
124
      </div>
125
    </div>
126
  );
127
}
128
 
129
function InputCard({ name, desc }: { name: string; desc: string }) {
130
  return (
131
    <Card className="p-4">
132
      <p className="font-mono text-[13px] text-paper">{name}</p>
133
      <p className="label mt-2 text-ash-2">{desc}</p>
134
    </Card>
135
  );
136
}
137
 
138
const RULES = [
139
  {
140
    title: "Government-source-only",
141
    body: "Every displayed fact carries a sourceUrl pointing to an allowlisted government domain. No card renders without a visible Source link.",
142
  },
143
  {
144
    title: "No adjectives",
145
    body: "Generated and display text contains no qualitative modifiers. Numbers, status, dates, and names only. A banned-words lint fails the build.",
146
  },
147
  {
148
    title: "Surface by official action",
149
    body: "Feed ranking references only action date, dollar magnitude, people affected, and action type. No engagement, coverage, or social inputs.",
150
  },
151
  {
152
    title: "No comment sections",
153
    body: "No free-text comments, reactions, ratings, or shares on bills, votes, or spending. Permitted engagement is a clarity vote on a summary, not on a bill's merit.",
154
  },
155
  {
156
    title: "Extraction templates for summaries",
157
    body: "Prose summaries, when they land, are field extraction into a fixed template -not open-ended generation. Each passes the banned-words lint and a polarization check.",
158
  },
159
];