File size: 28,673 Bytes
ed98758
 
 
 
 
4e87534
9fce59c
ed98758
 
 
4e87534
 
ed98758
 
 
 
4e87534
ed98758
 
 
 
 
 
 
 
c53fa39
 
 
54bd1cf
c53fa39
 
 
 
 
54bd1cf
c53fa39
 
54bd1cf
c53fa39
 
 
 
 
 
54bd1cf
 
c53fa39
 
 
 
 
 
ed98758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d7891e4
 
 
 
 
 
 
 
 
46b1454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4e87534
46b1454
 
 
 
 
 
 
ed98758
 
 
54bd1cf
4e87534
 
 
 
 
 
 
 
 
 
6a6c7cf
 
54bd1cf
 
8586c1b
 
c53fa39
 
54bd1cf
 
ed98758
 
54bd1cf
ed98758
9fce59c
8586c1b
 
 
ed98758
 
0b29b70
ed98758
d7891e4
 
0b29b70
 
 
 
ed98758
 
 
b89016c
 
 
 
 
 
 
 
b195b7b
069d95d
b195b7b
 
b89016c
 
 
 
 
 
 
 
 
 
329d800
bfb6467
 
 
 
329d800
 
bfb6467
329d800
bfb6467
329d800
 
bf6c4d6
329d800
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed98758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329d800
ed98758
 
 
 
 
 
 
 
 
 
 
 
 
b195b7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed98758
 
069d95d
ed98758
 
 
 
 
 
 
069d95d
ed98758
9fce59c
ed98758
 
 
 
9fce59c
ed98758
 
 
bf6c4d6
 
 
 
 
 
 
069d95d
bf6c4d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed98758
 
 
 
 
 
 
 
 
 
 
 
9fce59c
ed98758
 
 
 
 
 
 
 
b195b7b
 
ed98758
b195b7b
 
 
 
ed98758
 
ffe862f
 
 
 
 
ed98758
 
 
 
9fce59c
ed98758
 
 
 
9fce59c
0b29b70
ed98758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bfb6467
 
 
ed98758
 
 
 
 
 
 
 
 
 
 
 
 
 
bfb6467
 
 
 
 
d7891e4
bfb6467
9fce59c
b89016c
 
 
 
 
ed98758
 
 
46b1454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c53fa39
 
 
 
 
 
 
 
 
ed98758
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/svg+xml" href="/ccr-logo.svg">
<title>CCR Platform - Testing Guide</title>
<style>
  :root {
    --bg: #ffffff; --fg: #1a1a1a; --muted: #666; --border: #ddd;
    --accent: #26736f; --card: #f7f7f8; --amber-bg: #fff7e0; --amber-border: #e0b84d;
    --code-bg: #f0f0f2; --brand: #151515;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #16181c; --fg: #e6e6e6; --muted: #9a9a9a; --border: #3a3d44;
      --accent: #6cc9c6; --card: #1f2228; --amber-bg: #2e2810; --amber-border: #8a6d1f;
      --code-bg: #24272e;
    }
  }
  * { box-sizing: border-box; }
  body {
    margin: 0; background: var(--bg); color: var(--fg);
    font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  }
  /* Topbar mirrors the app header EXACTLY (gradient, height, logo size,
     tagline, link styles) so navigating app <-> docs doesn't jump. Update
     styles.css .header alongside any change here. */
  .topbar {
    background: linear-gradient(180deg, #1b1b1c, var(--brand)); color: #fff;
    padding: 14px 28px;
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    position: sticky; top: 0; z-index: 30;
    border-bottom: 1px solid rgba(255,255,255,.07);
  }
  .topbar .brand { display: inline-flex; align-items: center; text-decoration: none; }
  .brand-logo { height: 28px; width: auto; display: block; }
  .topbar .brand:hover { opacity: .88; }
  .topbar .lab {
    flex: 1 1 280px; min-width: 0; font-size: 12.5px; line-height: 1.5;
    color: #fff; opacity: .85;
  }
  .topbar nav { margin-left: auto; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
  .topbar nav a { color: rgba(255,255,255,.82); text-decoration: none; font-size: 13px; }
  .topbar nav a:hover { color: #fff; text-decoration: underline; }
  .topbar nav a.current { color: #fff; font-weight: 600; }
  .topbar .topbar-btn {
    background: rgba(255,255,255,.12); color: #fff;
    border: 1px solid rgba(255,255,255,.45);
    padding: 5px 14px; border-radius: 9px; font-size: 13px; text-decoration: none;
  }
  .topbar .topbar-btn:hover { background: rgba(255,255,255,.22); text-decoration: none; }
  main { max-width: 860px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
  h1 { font-size: 1.7rem; margin: 0 0 .25rem; }
  h2 { font-size: 1.25rem; margin: 2.2rem 0 .6rem; padding-top: .6rem; border-top: 1px solid var(--border); }
  h3 { font-size: 1.02rem; margin: 1.2rem 0 .4rem; }
  p, li { color: var(--fg); }
  .sub { color: var(--muted); margin: 0 0 1.2rem; }
  a { color: var(--accent); }
  code { background: var(--code-bg); padding: .1em .35em; border-radius: 4px; font-size: .9em; }
  .note {
    background: var(--amber-bg); border: 1px solid var(--amber-border);
    border-radius: 8px; padding: .8rem 1rem; margin: 1rem 0;
  }
  .note b { display: block; margin-bottom: .25rem; }
  .tablewrap { overflow-x: auto; }
  table { border-collapse: collapse; width: 100%; margin: .8rem 0; font-size: .93rem; }
  th, td { border: 1px solid var(--border); padding: .45rem .6rem; text-align: left; vertical-align: top; }
  th { background: var(--card); }
  ol li, ul li { margin: .3rem 0; }
  .dl::before { content: "⬇ "; font-size: .85em; }
  .top { position: fixed; right: 1rem; bottom: 1rem; background: var(--card);
    border: 1px solid var(--border); border-radius: 8px; padding: .4rem .7rem;
    text-decoration: none; font-size: .85rem; }
  figure { margin: 1rem 0 1.4rem; }
  figure svg { width: 100%; height: auto; border: 1px solid var(--border);
    border-radius: 8px; background: var(--card); }
  figcaption { color: var(--muted); font-size: .85rem; margin-top: .4rem; }
  .box { fill: var(--bg); stroke: var(--border); stroke-width: 1.5; }
  .box-accent { fill: none; stroke: var(--accent); stroke-width: 2; }
  .lbl { fill: var(--fg); font: 600 13px sans-serif; }
  .lbl-sm { fill: var(--muted); font: 11px sans-serif; }
  .flow { stroke: var(--muted); stroke-width: 1.5; fill: none; marker-end: url(#arw); }
  /* Shareable section links: a "#" appears on hover; click a heading to copy
     a direct link to that section. */
  h2[id], h3[id] { scroll-margin-top: 1rem; }
  .anchor {
    opacity: 0; text-decoration: none; color: var(--muted); font-weight: 400;
    margin-left: .4rem; cursor: pointer; transition: opacity .12s;
  }
  h2:hover .anchor, h3:hover .anchor, .anchor:focus { opacity: .7; }
  .anchor:hover { opacity: 1; color: var(--accent); }
  :target { animation: flash 1.4s ease-out; }
  @keyframes flash {
    from { background: var(--amber-bg); }
    to   { background: transparent; }
  }
  .toast {
    position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
    background: var(--brand); color: #fff; padding: .5rem .9rem; border-radius: 8px;
    font-size: .85rem; opacity: 0; pointer-events: none; transition: opacity .2s;
  }
  .toast.show { opacity: 1; }
  @media (prefers-reduced-motion: reduce) {
    :target { animation: none; }
    .anchor { transition: none; }
  }
</style>
</head>
<body>
<header class="topbar">
  <a class="brand" href="/"><svg class="brand-logo" viewBox="188 455 880 338" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
    <g fill="none" stroke="#fff" stroke-width="32">
      <path d="M 458.884 538.509 A 133 133 0 1 0 458.884 709.491"/>
      <path d="M 563.116 538.509 A 133 133 0 0 1 766.884 538.509"/>
      <path d="M 563.116 709.491 A 133 133 0 0 0 766.884 709.491"/>
      <path d="M 846 754 V 496 H 950 A 73 62 0 0 1 950 620 H 846"/>
      <path d="M 930 620 L 1035 759"/>
    </g>
    <circle cx="481" cy="624" r="15" fill="#339B98"/>
    <circle cx="538" cy="624" r="15" fill="#339B98"/>
  </svg></a>
  <span class="lab">Psychological Text Analysis with Contextualized Construct Representation</span>
  <nav>
    <a href="/welcome">About</a>
    <a href="/guide">Guide</a>
    <a href="/testing" class="current">Testing</a>
    <a href="/product" id="nav-product" hidden>How it works</a>
    <a class="topbar-btn" href="/">Open dashboard</a>
  </nav>
</header>
<main>

<h1>Testing guide</h1>
<p class="sub">Everything built so far, as click-through scenarios: what to do and exactly what
you should see. Every dataset referenced is downloadable right here - all texts are
synthetic, written for this kit; no real participant data anywhere. This is the lab's
internal testing guide; the plain how-to-use walkthrough for everyone lives at
<a href="/guide">the public guide</a>.</p>

<div class="note">
  <b>⚠ This is the dev instance - a couple of things to know:</b>
  <ul>
    <li><b>Your work is saved.</b> Account, projects, uploaded datasets, and runs
        are backed by persistent storage and stay put across restarts.</li>
    <li><b>It sleeps when idle.</b> After ~48&nbsp;h without visitors, the first page
        load takes about a minute to wake it up.</li>
    <li>Please still avoid uploading sensitive or identifiable data on this dev
        instance.</li>
  </ul>
</div>

<h2 id="quickstart">Quickstart: run YOUR study in 5 minutes</h2>
<ol>
  <li><b>Sign in</b> (top right - Google or email+password). Lab accounts have
      unlimited access.</li>
  <li><b>Create a project</b> named after your study.</li>
  <li><b>Upload your corpus</b> - any CSV/XLSX with one text per row. Other columns
      (IDs, conditions, demographics) are fine; they pass through untouched into the
      results file.</li>
  <li><b>Pick your construct(s)</b> - search the library (94 scales from the lab's
      collection), or click "+ New construct" and choose a card: type/paste items, upload a file, or draft items with AI for a construct with no validated questionnaire.
      Pick several (up to 10) to score them together in a single run and see how
      they correlate in your texts.</li>
  <li><b>Run.</b> Read the warnings panel first (it tells you if something about your
      data is off), then check the top/bottom scoring texts - if those don't make sense
      for your construct, trust that instinct and report it.</li>
  <li><b>Export</b> the results CSV (your columns + per-item similarities + CCR score)
      and, if you want, the Python script that reproduces the run on your own machine.</li>
</ol>
<p>The sections below are structured test scenarios with sample data - useful for
systematically stress-testing the platform, but <b>your real data is the best test we
have</b>.</p>

<h2 id="limits">Limits at a glance</h2>
<p>Accounts come in four tiers, set by the admins: <b>external user</b> (the default on
sign-up), <b>lab member</b>, <b>maintainer</b>, and <b>PI</b>. Signing in lifts the
anonymous caps; lab members and above have no saved-run cap. The row limit is usually
what you hit first, not the file size.</p>
<div class="tablewrap">
<table>
  <tr><th>Limit</th><th>Signed out</th><th>External user</th><th>Lab member +</th></tr>
  <tr><td>Upload size</td><td>5&nbsp;MB</td><td>50&nbsp;MB</td><td>50&nbsp;MB</td></tr>
  <tr><td>Rows per file</td><td>200</td><td>50,000</td><td>50,000</td></tr>
  <tr><td>Runs per day</td><td>3, then sign in</td><td>unlimited</td><td>unlimited</td></tr>
  <tr><td>Saved runs kept</td><td>none (file deleted after each run)</td><td>15</td><td>unlimited</td></tr>
  <tr><td>AI item drafts per day</td><td>0 (sign in to use)</td><td>20</td><td>20</td></tr>
</table>
</div>
<p>Every model below is available on all three, including signed out.</p>

<h2 id="models">Models available</h2>
<p>Chosen on the Step 3 card. Scores are only comparable <i>within</i> the same model -
never across model families. Larger models are slower and, the first time anyone selects
one on this instance, take a couple of extra minutes to download before the run starts;
after that they stay warm.</p>
<div class="tablewrap">
<table>
  <tr><th>Model</th><th>Best for</th><th>Notes</th></tr>
  <tr><td>MiniLM L6 v2 <b>(default)</b></td><td>English, general use</td><td>Fast; the CCR reference model. Start here.</td></tr>
  <tr><td>E5 Large v2</td><td>English, higher quality</td><td>Stronger but noticeably slower.</td></tr>
  <tr><td>Multilingual E5 Base</td><td>50+ languages</td><td>Use for non-English corpora.</td></tr>
  <tr><td>PsyEmbedding BERT / RoBERTa / GTE / E5 Large</td><td>Psychological text</td><td>Four lab fine-tunes for psychology research. English; heavier, so the first run downloads ~1.3&nbsp;GB.</td></tr>
</table>
</div>

<h2 id="samples">0. Sample datasets</h2>
<p>One file per platform behavior. To test something, download the file named for it and
follow its scenario below. Unless stated otherwise, select language <b>English</b> and
model <b>MiniLM</b>.</p>
<div class="tablewrap">
<table>
  <tr><th>File</th><th>Demonstrates</th></tr>
  <tr><td><a class="dl" href="/samples/sample_corpus.csv" download>sample_corpus.csv</a></td><td>Clean baseline run (60 rows, no warnings)</td></tr>
  <tr><td><a class="dl" href="/samples/warnings_showcase.csv" download>warnings_showcase.csv</a></td><td>All text-QA warnings at once</td></tr>
  <tr><td><a class="dl" href="/samples/french_demo.csv" download>french_demo.csv</a></td><td>Language mismatch / model-language checks</td></tr>
  <tr><td><a class="dl" href="/samples/demo_spanish.csv" download>demo_spanish.csv</a></td><td>Same checks in Spanish + short-text rows</td></tr>
  <tr><td><a class="dl" href="/samples/mixed_language_demo.csv" download>mixed_language_demo.csv</a></td><td>Uncertain language detection (15 EN + 15 ES)</td></tr>
  <tr><td><a class="dl" href="/samples/long_documents_demo.csv" download>long_documents_demo.csv</a></td><td>Token-window truncation warning</td></tr>
  <tr><td><a class="dl" href="/samples/moral_foundations_demo.csv" download>moral_foundations_demo.csv</a></td><td>Meaningful score spread across MFQ-2 foundations</td></tr>
  <tr><td><a class="dl" href="/samples/multi_column_demo.csv" download>multi_column_demo.csv</a></td><td>Text-column auto-suggestion (5 columns)</td></tr>
  <tr><td><a class="dl" href="/samples/semicolon_delimited_demo.csv" download>semicolon_delimited_demo.csv</a></td><td>Delimiter sniffing (semicolons, commas inside texts)</td></tr>
  <tr><td><a class="dl" href="/samples/latin1_encoding_demo.csv" download>latin1_encoding_demo.csv</a></td><td>Non-UTF-8 encoding fallback</td></tr>
  <tr><td><a class="dl" href="/samples/xlsx_upload_demo.xlsx" download>xlsx_upload_demo.xlsx</a></td><td>Excel ingestion path</td></tr>
  <tr><td><a class="dl" href="/samples/large_demo.csv" download>large_demo.csv</a></td><td>Anonymous upload caps (800 rows)</td></tr>
  <tr><td><a class="dl" href="/samples/construct_items_demo.csv" download>construct_items_demo.csv</a></td><td>Construct upload: item + reverse columns</td></tr>
  <tr><td><a class="dl" href="/samples/construct_items_marker_demo.csv" download>construct_items_marker_demo.csv</a></td><td>Construct upload: (R) markers, blank + duplicate rows</td></tr>
  <tr><td><a class="dl" href="/samples/construct_items_demo.xlsx" download>construct_items_demo.xlsx</a></td><td>Construct upload from Excel</td></tr>
</table>
</div>

<h2 id="projects">1. Projects and sidebar</h2>
<ol>
  <li>Create three projects. They appear under "Today", newest activity first.</li>
  <li>Type in the sidebar search box: the list filters as you type.</li>
  <li>Archive a project (project header &gt; Archive): it moves into the collapsed
      "Archived" group; Unarchive brings it back. No data is lost either way.</li>
  <li>Delete a project: requires typing the project name; removes its datasets,
      runs, and files permanently.</li>
</ol>

<h2 id="uploads">2. Upload paths (Step 1 card)</h2>
<div class="tablewrap">
<table>
  <tr><th>Upload</th><th>Expect</th></tr>
  <tr><td><a href="/samples/sample_corpus.csv" download>sample_corpus.csv</a></td><td>Parses, 60 rows, <code>text</code> column suggested</td></tr>
  <tr><td><a href="/samples/multi_column_demo.csv" download>multi_column_demo.csv</a></td><td>5 columns; <code>comment_text</code> marked "(suggested)"</td></tr>
  <tr><td><a href="/samples/semicolon_delimited_demo.csv" download>semicolon_delimited_demo.csv</a></td><td>Parses into exactly id + text (commas inside texts intact)</td></tr>
  <tr><td><a href="/samples/latin1_encoding_demo.csv" download>latin1_encoding_demo.csv</a></td><td>Parses with a ⚠ note: decoded as latin-1; fiancée/café render correctly</td></tr>
  <tr><td><a href="/samples/xlsx_upload_demo.xlsx" download>xlsx_upload_demo.xlsx</a></td><td>Parses like a CSV</td></tr>
  <tr><td>any <code>.txt</code> or <code>.pdf</code> file</td><td>Rejected: unsupported file type</td></tr>
</table>
</div>
<p>Anonymous limits (signed out): the Step 1 hint shows 5&nbsp;MB / 200 rows and says uploads
are deleted after analysis. Upload
<a href="/samples/large_demo.csv" download>large_demo.csv</a> (800 rows): rejected with a
"Sign in (top right)" message. Sign in and retry: accepted.</p>

<h2 id="constructs">3. Construct selection (Step 2 card)</h2>
<ol>
  <li>Open the picker: search field + panel below it, library grouped by category,
      with "Recently used" pinned on top after your first runs.</li>
  <li>Type "GAD" or "empathy": matches by name and category; Arrow keys + Enter work.</li>
  <li>Select any imported construct: items listed, plus the "not yet verified verbatim"
      notice (expected for the whole imported library for now).</li>
</ol>

<h3>Multi-construct runs (new)</h3>
<ol>
  <li>Pick a construct, then open the picker again and pick a second - selected ones
      show a ✓ (clicking again removes). Each selection becomes a collapsible block
      with its items and a "remove" link. Up to 10 constructs per run.</li>
  <li>The run button reads "Run CCR analysis (2 constructs)". All constructs are
      scored on <b>one pass</b> over the corpus, so two constructs take about as long
      as one.</li>
  <li>Results open with a <b>"Construct interrelations"</b> card - the Pearson
      correlation between per-text scores, i.e. how the constructs co-occur in YOUR
      texts - plus a collapsible per-construct section (histogram, item loadings,
      top/bottom texts). Try
      <a href="/samples/moral_foundations_demo.csv" download>moral_foundations_demo.csv</a>
      with two MFQ-2 foundations.</li>
  <li>The export CSV keeps one row per text with per-construct prefixed columns
      (<code>mfq_care_sim_item_1</code><code>mfq_care_ccr_score</code>, …), so the
      correlations are fully recomputable; metadata and the reproduction script cover
      every construct in the run. Single-construct runs are unchanged.</li>
</ol>

<h3>Custom construct, typed</h3>
<ol>
  <li>"+ New construct" &gt; the "Type or paste" card &gt; name it, paste items one per line.</li>
  <li>Append <code>(R)</code> to one line: the form shows "1 item(s) marked reverse-scored".</li>
  <li>Save: it appears in the picker under "My custom constructs"; run metadata carries
      the reverse flag (check via Results &gt; metadata download).</li>
</ol>

<h3>Custom construct, from file</h3>
<ol>
  <li>"+ New construct" &gt; the "Upload a file" card.</li>
  <li>Try <a href="/samples/construct_items_demo.csv" download>construct_items_demo.csv</a>
      (<code>item,reverse</code> columns - 1/true/yes/R = reverse),
      <a href="/samples/construct_items_marker_demo.csv" download>construct_items_marker_demo.csv</a>
      (single column with <code>(R)</code> markers), or
      <a href="/samples/construct_items_demo.xlsx" download>construct_items_demo.xlsx</a> (Excel).</li>
  <li>Expect: items fill the textarea ((R) appended where flagged), the filename becomes
      the suggested name, and parse notes list skipped duplicates. Nothing is saved
      until you review and press Save. Item files are never retained on the server.</li>
</ol>

<h3 id="ai-draft">Custom construct, drafted with AI (new)</h3>
<p>For constructs with no validated questionnaire: the platform can draft candidate
items from the construct's name and a short explanation. The draft is a starting
point, <b>not</b> a validated scale - you review, edit, and delete before saving, and
everything the construct touches is labeled "AI-generated · not validated".
Signed-in users only, 20 drafts/day.</p>
<ol>
  <li><b>Happy path:</b> sign in &gt; "+ New construct" &gt; the "Draft with AI"
      tab. Name: <code>Digital overwhelm</code>. Description: <i>"Feeling that
      screens, notifications, and online demands exceed one's capacity to keep
      up."</i> Press "Draft items". Expect in a few seconds: ~10 first-person,
      positively-worded items in the textarea (no <code>(R)</code> items - by
      design), a "0 of 20 used today" style counter, an amber "AI-generated · not
      validated - drafted by &lt;model&gt;" notice, and sometimes short model notes
      (e.g. which facets it covered).</li>
  <li><b>Review is the point:</b> edit one item, delete a weak one, then Save.
      The construct appears in the picker under "My custom constructs" with an
      <b>AI-generated · not validated</b> tag - the tag stays even though you
      edited, because the seed was AI (the item hash records your edits).</li>
  <li><b>Library guardrail:</b> on the same tab, type <code>Satisfaction with
      Life</code> as the name. Expect a warning that the library already has this
      scale with validated items - use that instead of generating.</li>
  <li><b>It follows your definition:</b> draft the same name twice with two
      different descriptions (e.g. define "resilience" once as bouncing back from
      setbacks, once as tolerating discomfort). The items should track YOUR
      wording, not a generic textbook version - that is the feature working.</li>
  <li><b>Vague input:</b> give a nonsense name (<code>Zorblex</code>) with a vague
      description. Expect items anyway, plus model notes explaining it could not
      identify a standard construct - refine the description and redraft.</li>
  <li><b>Run + provenance:</b> run any corpus against your saved AI construct.
      The results page shows a caution line; the metadata JSON download has
      <code>source_type: "llm_generated"</code>, the drafting model + prompt
      version + date, and a machine-readable cautionary note. This travels into
      the reproduction script too.</li>
  <li><b>Signed out:</b> the "Draft with AI" tab shows a sign-in nudge instead of
      controls; the API refuses anonymous calls outright.</li>
</ol>
<div class="note">These items are drafted by an AI language model and have not been
psychometrically validated. Where a validated scale exists, prefer it; interpret
scores from AI-drafted constructs with appropriate caution. (Validation study -
AI-drafted vs. validated SWLS/MFQ items on the same texts - is planned before
public launch.)</div>

<h2 id="warnings">4. Language, models, and warnings (Step 3 card + results)</h2>
<p>Run each of these and open the results page; the amber warnings panel should show exactly:</p>
<div class="tablewrap">
<table>
  <tr><th>Corpus</th><th>Selection</th><th>Expected warnings</th></tr>
  <tr><td><a href="/samples/warnings_showcase.csv" download>warnings_showcase.csv</a></td><td>en + MiniLM</td>
      <td>EMPTY_ROWS_DROPPED (2), DUPLICATE_TEXTS (2), TEXT_TOO_SHORT (3), TEXTS_MAYBE_TRUNCATED (2); no language warnings</td></tr>
  <tr><td><a href="/samples/french_demo.csv" download>french_demo.csv</a></td><td>en + MiniLM</td><td>LANGUAGE_MISMATCH (detected fr, 100%)</td></tr>
  <tr><td>french_demo.csv</td><td>fr + MiniLM</td><td>MODEL_LANGUAGE_UNSUPPORTED</td></tr>
  <tr><td>french_demo.csv</td><td>fr + Multilingual E5</td><td>no language warnings</td></tr>
  <tr><td><a href="/samples/mixed_language_demo.csv" download>mixed_language_demo.csv</a></td><td>en + MiniLM</td><td>LANGUAGE_UNCERTAIN (majority 50%)</td></tr>
  <tr><td><a href="/samples/long_documents_demo.csv" download>long_documents_demo.csv</a></td><td>en + MiniLM</td>
      <td>TEXTS_MAYBE_TRUNCATED (4) + LANGUAGE_UNCERTAIN (only 10 rows, below the 20-row minimum - by design)</td></tr>
</table>
</div>
<p>Warnings are per-run snapshots: changing language/model requires a NEW run; old result
pages don't update.</p>

<h2 id="results">5. Results and reproducibility</h2>
<ol>
  <li>Run <a href="/samples/moral_foundations_demo.csv" download>moral_foundations_demo.csv</a>
      against two different MFQ-2 foundations (separately, or both in one
      multi-construct run): top texts change per foundation; the 6
      neutral rows sink to the bottom.</li>
  <li>Results page: histogram, mean/SD/min/max, per-item loadings, top/bottom texts;
      multi-construct runs add the correlation matrix up top.</li>
  <li>Downloads: results CSV (input columns + sim_item_N + ccr_score; multi-construct
      runs prefix these per construct), metadata JSON
      (model revision, construct snapshot + item hash, language block, environment pins),
      reproduction script + requirements file.</li>
  <li>Reproduction check: both downloads carry your run's id, e.g.
      <code>pip install -r requirements-repro_&lt;run-id&gt;.txt</code>, then
      <code>python reproduce_analysis_&lt;run-id&gt;.py your_corpus.csv</code> on a machine
      with no platform access; the exact commands (with your run id and CSV name) are in
      the script's header. Values should match the export (target ~1e-5 with real models).</li>
</ol>

<h2 id="accounts">6. Accounts</h2>
<ol>
  <li>Sign in (top right) &gt; "Create a free account" &gt; email + password (min 8 chars) -
      or use "Continue with Google".</li>
  <li>You're signed in immediately; the header shows your name.</li>
  <li>Sign out, sign back in; wrong password gives "Incorrect email or password";
      registering the same email again gives "already exists".</li>
  <li>Email is case-insensitive. No self-service password reset yet - reset = admin action.
      (Accounts persist across restarts now - no need to re-register.)</li>
</ol>

<h2 id="anon">7. Anonymous tiers (test signed OUT)</h2>
<ol>
  <li>Upload caps: see section 2.</li>
  <li>Run limit: run 3 analyses. The Step 3 card counts "X of 3 free runs used today".
      The 4th run is refused with a sign-in prompt. Counter resets next day (UTC).
      Signing in removes the limit.</li>
  <li>Delete-after-analysis: run any corpus, open results (fine, downloadable), note the
      info warning "uploaded file was deleted after this analysis". Re-running that same
      corpus: refused ("upload again, or sign in").</li>
  <li>TTL purge: anonymous projects older than 24&nbsp;h are deleted entirely (startup + hourly).</li>
</ol>

<h2 id="signedin">8. Signed-in tier</h2>
<ol>
  <li>Sign in, upload, run: no ANONYMOUS_DATA_REMOVED warning; re-running the same corpus
      works (file kept).</li>
  <li>Saved-run budget: the Step 3 card shows "N of M saved runs used". Lab members and
      above have no cap; external accounts get 15. At the cap, new runs are refused until
      you delete old runs/projects (nothing is auto-deleted).</li>
  <li>Ownership: your projects are invisible to signed-out visitors and other accounts.
      Anonymous projects stay shared.</li>
</ol>

<h2 id="perf">9. Performance behaviors</h2>
<ol>
  <li>Corpus-embedding cache: run the SAME corpus with a second construct (signed in, same
      model): the run skips document embedding and completes in seconds; metadata shows
      <code>"doc_embeddings_from_cache": true</code>.</li>
  <li>Duplicate texts are embedded once
      (<a href="/samples/warnings_showcase.csv" download>warnings_showcase.csv</a> has 2
      dupes): identical scores for identical texts, less compute.</li>
</ol>

<h2 id="architecture">10. Under the hood</h2>
<p>Architecture, data flow, data retention, and the full access/roles model
(tiers, invite links, pre-assigned roles, audit trail) live on their own page:
<a href="/product"><b>Product &amp; Architecture →</b></a>. This guide stays
focused on using and testing the platform.</p>

<h2 id="feedback">11. Found something off?</h2>
<p>Anything that doesn't match what this guide says it should do - or anything confusing,
slow, or missing - post it in the lab's <b>#ccr Slack channel</b>: the 🐞 thread for bugs,
the 💡 thread for ideas and feature requests. One line is enough; note the section number
and what you saw; screenshots help. DMs to Deva work too, and email as a fallback
(<a href="mailto:devaanand@umass.edu">devaanand@umass.edu</a>). Nothing is too small -
"this button confused me" is exactly the kind of report we want.</p>

</main>
<a class="top" href="#">↑ Top</a>
<div class="toast" id="toast" role="status" aria-live="polite">Link copied</div>
<script>
(function () {
  var toast = document.getElementById('toast'), timer;
  function ping(msg) {
    toast.textContent = msg;
    toast.classList.add('show');
    clearTimeout(timer);
    timer = setTimeout(function () { toast.classList.remove('show'); }, 1600);
  }
  function share(id) {
    // location.hash also makes the browser scroll to and :target-flash the section.
    if (history.replaceState) history.replaceState(null, '', '#' + id);
    location.hash = id;
    var url = location.href;
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(url).then(
        function () { ping('Section link copied'); },
        function () { ping('Link in address bar'); }
      );
    } else {
      ping('Link in address bar');
    }
  }
  // Give every id'd heading a click-to-copy "#" affordance.
  var heads = document.querySelectorAll('h2[id], h3[id]');
  Array.prototype.forEach.call(heads, function (h) {
    var a = document.createElement('a');
    a.className = 'anchor';
    a.href = '#' + h.id;
    a.textContent = '#';
    a.setAttribute('aria-label', 'Copy link to this section');
    a.addEventListener('click', function (e) { e.preventDefault(); share(h.id); });
    h.appendChild(a);
    // Clicking the heading text itself (not a link inside it) also shares.
    h.style.cursor = 'pointer';
    h.addEventListener('click', function (e) {
      if (e.target.tagName === 'A') return;  // let real links behave normally
      share(h.id);
    });
  });
})();
</script>
<script>
  // Mirror the app header: the internal "How it works" link only shows for
  // lab members and above (the server gates /product regardless).
  fetch("/api/auth/me").then((r) => r.json()).then((me) => {
    if (["lab", "maintainer", "pi"].includes(me.role)) {
      document.getElementById("nav-product").hidden = false;
    }
  }).catch(() => {});
</script>
</body>
</html>