devaanand commited on
Commit
ed98758
·
1 Parent(s): fd38128

Add /guide tester page and /samples demo-data downloads

Browse files
.dockerignore CHANGED
@@ -6,6 +6,5 @@ backend/tests
6
  backend/.pytest_cache
7
  **/__pycache__
8
  frontend
9
- sample_data
10
  scripts
11
  *.md
 
6
  backend/.pytest_cache
7
  **/__pycache__
8
  frontend
 
9
  scripts
10
  *.md
DEPLOY.md CHANGED
@@ -12,10 +12,17 @@ Space settings; after that, deploys are just `git push hf main`.
12
  | SUPABASE_ANON_KEY | from the same page (anon public key, NOT service_role) |
13
  | CCR_APP_URL | https://devaanand-ccr-platform.hf.space |
14
  | CCR_COOKIE_SECURE | 1 |
 
15
 
16
  Retention (CCR_ANON_TTL_HOURS=24) and model pre-warm are already defaults in
17
  the Dockerfile.
18
 
 
 
 
 
 
 
19
  ## Supabase setup for Google sign-in (one time)
20
 
21
  1. supabase.com > New project (free tier).
 
12
  | SUPABASE_ANON_KEY | from the same page (anon public key, NOT service_role) |
13
  | CCR_APP_URL | https://devaanand-ccr-platform.hf.space |
14
  | CCR_COOKIE_SECURE | 1 |
15
+ | CCR_MAX_ROWS | 20000 (global upload ceiling; code default is 100000) |
16
 
17
  Retention (CCR_ANON_TTL_HOURS=24) and model pre-warm are already defaults in
18
  the Dockerfile.
19
 
20
+ ## Tester guide
21
+
22
+ The deployed instance serves a click-through testing guide at /guide with
23
+ download links for every demo corpus (served from /samples). Send the PI and
24
+ students that URL; no files need to be shared out of band.
25
+
26
  ## Supabase setup for Google sign-in (one time)
27
 
28
  1. supabase.com > New project (free tier).
Dockerfile CHANGED
@@ -41,6 +41,9 @@ COPY backend/static ./static
41
  # registry.py/construct_lib.py resolve packages/ two levels above app/
42
  # (= "/" here), so /packages is exactly where they look.
43
  COPY packages /packages
 
 
 
44
 
45
  EXPOSE 7860
46
  CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860}"]
 
41
  # registry.py/construct_lib.py resolve packages/ two levels above app/
42
  # (= "/" here), so /packages is exactly where they look.
43
  COPY packages /packages
44
+ # Synthetic demo corpora served at /samples for the tester guide (/guide);
45
+ # resolved the same way as packages/.
46
+ COPY sample_data /sample_data
47
 
48
  EXPOSE 7860
49
  CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860}"]
backend/app/guide.html ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>CCR Platform — Testing Guide</title>
7
+ <style>
8
+ :root {
9
+ --bg: #ffffff; --fg: #1a1a1a; --muted: #666; --border: #ddd;
10
+ --accent: #2456a6; --card: #f7f7f8; --amber-bg: #fff7e0; --amber-border: #e0b84d;
11
+ --code-bg: #f0f0f2;
12
+ }
13
+ @media (prefers-color-scheme: dark) {
14
+ :root {
15
+ --bg: #16181c; --fg: #e6e6e6; --muted: #9a9a9a; --border: #3a3d44;
16
+ --accent: #7aa7e8; --card: #1f2228; --amber-bg: #2e2810; --amber-border: #8a6d1f;
17
+ --code-bg: #24272e;
18
+ }
19
+ }
20
+ * { box-sizing: border-box; }
21
+ body {
22
+ margin: 0; background: var(--bg); color: var(--fg);
23
+ font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
24
+ }
25
+ main { max-width: 860px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
26
+ h1 { font-size: 1.7rem; margin: 0 0 .25rem; }
27
+ h2 { font-size: 1.25rem; margin: 2.2rem 0 .6rem; padding-top: .6rem; border-top: 1px solid var(--border); }
28
+ h3 { font-size: 1.02rem; margin: 1.2rem 0 .4rem; }
29
+ p, li { color: var(--fg); }
30
+ .sub { color: var(--muted); margin: 0 0 1.2rem; }
31
+ a { color: var(--accent); }
32
+ code { background: var(--code-bg); padding: .1em .35em; border-radius: 4px; font-size: .9em; }
33
+ .note {
34
+ background: var(--amber-bg); border: 1px solid var(--amber-border);
35
+ border-radius: 8px; padding: .8rem 1rem; margin: 1rem 0;
36
+ }
37
+ .note b { display: block; margin-bottom: .25rem; }
38
+ .tablewrap { overflow-x: auto; }
39
+ table { border-collapse: collapse; width: 100%; margin: .8rem 0; font-size: .93rem; }
40
+ th, td { border: 1px solid var(--border); padding: .45rem .6rem; text-align: left; vertical-align: top; }
41
+ th { background: var(--card); }
42
+ ol li, ul li { margin: .3rem 0; }
43
+ .dl::before { content: "⬇ "; font-size: .85em; }
44
+ .top { position: fixed; right: 1rem; bottom: 1rem; background: var(--card);
45
+ border: 1px solid var(--border); border-radius: 8px; padding: .4rem .7rem;
46
+ text-decoration: none; font-size: .85rem; }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <main>
51
+
52
+ <h1>CCR Platform — Testing Guide</h1>
53
+ <p class="sub">Everything built so far, as click-through scenarios: what to do and exactly what
54
+ you should see. Every dataset referenced is downloadable right here — all texts are
55
+ synthetic, written for this kit; no real participant data anywhere.</p>
56
+
57
+ <div class="note">
58
+ <b>⚠ This is the free dev instance — two caveats before you start:</b>
59
+ <ul>
60
+ <li><b>Storage is ephemeral.</b> Accounts, projects, and saved runs are wiped whenever
61
+ the instance rebuilds or restarts. If your account disappears, that's expected —
62
+ just re-register. (Google sign-in recreates itself automatically.)</li>
63
+ <li><b>It sleeps when idle.</b> After ~48&nbsp;h without visitors, the first page load
64
+ takes about a minute to wake it up.</li>
65
+ </ul>
66
+ </div>
67
+
68
+ <h2 id="samples">0. Sample datasets</h2>
69
+ <p>One file per platform behavior. To test something, download the file named for it and
70
+ follow its scenario below. Unless stated otherwise, select language <b>English</b> and
71
+ model <b>MiniLM</b>.</p>
72
+ <div class="tablewrap">
73
+ <table>
74
+ <tr><th>File</th><th>Demonstrates</th></tr>
75
+ <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>
76
+ <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>
77
+ <tr><td><a class="dl" href="/samples/french_demo.csv" download>french_demo.csv</a></td><td>Language mismatch / model-language checks</td></tr>
78
+ <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>
79
+ <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>
80
+ <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>
81
+ <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>
82
+ <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>
83
+ <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>
84
+ <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>
85
+ <tr><td><a class="dl" href="/samples/xlsx_upload_demo.xlsx" download>xlsx_upload_demo.xlsx</a></td><td>Excel ingestion path</td></tr>
86
+ <tr><td><a class="dl" href="/samples/large_demo.csv" download>large_demo.csv</a></td><td>Anonymous upload caps (800 rows)</td></tr>
87
+ <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>
88
+ <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>
89
+ <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>
90
+ </table>
91
+ </div>
92
+
93
+ <h2 id="projects">1. Projects and sidebar</h2>
94
+ <ol>
95
+ <li>Create three projects. They appear under "Today", newest activity first.</li>
96
+ <li>Type in the sidebar search box: the list filters as you type.</li>
97
+ <li>Archive a project (project header &gt; Archive): it moves into the collapsed
98
+ "Archived" group; Unarchive brings it back. No data is lost either way.</li>
99
+ <li>Delete a project: requires typing the project name; removes its datasets,
100
+ runs, and files permanently.</li>
101
+ </ol>
102
+
103
+ <h2 id="uploads">2. Upload paths (Step 1 card)</h2>
104
+ <div class="tablewrap">
105
+ <table>
106
+ <tr><th>Upload</th><th>Expect</th></tr>
107
+ <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>
108
+ <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>
109
+ <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>
110
+ <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>
111
+ <tr><td><a href="/samples/xlsx_upload_demo.xlsx" download>xlsx_upload_demo.xlsx</a></td><td>Parses like a CSV</td></tr>
112
+ <tr><td>any <code>.txt</code> or <code>.pdf</code> file</td><td>Rejected: unsupported file type</td></tr>
113
+ </table>
114
+ </div>
115
+ <p>Anonymous limits (signed out): the Step 1 hint shows 2&nbsp;MB / 200 rows and says uploads
116
+ are deleted after analysis. Upload
117
+ <a href="/samples/large_demo.csv" download>large_demo.csv</a> (800 rows): rejected with a
118
+ "Sign in (top right)" message. Sign in and retry: accepted.</p>
119
+
120
+ <h2 id="constructs">3. Construct selection (Step 2 card)</h2>
121
+ <ol>
122
+ <li>Open the picker: search field + panel below it, library grouped by category,
123
+ with "Recently used" pinned on top after your first runs.</li>
124
+ <li>Type "GAD" or "empathy": matches by name and category; Arrow keys + Enter work.</li>
125
+ <li>Select any imported construct: items listed, plus the "not yet verified verbatim"
126
+ notice (expected for the whole imported library for now).</li>
127
+ </ol>
128
+
129
+ <h3>Custom construct, typed</h3>
130
+ <ol>
131
+ <li>"+ Custom construct" &gt; name it, paste items one per line.</li>
132
+ <li>Append <code>(R)</code> to one line: the form shows "1 item(s) marked reverse-scored".</li>
133
+ <li>Save: it appears in the picker under "My custom constructs"; run metadata carries
134
+ the reverse flag (check via Results &gt; metadata download).</li>
135
+ </ol>
136
+
137
+ <h3>Custom construct, from file</h3>
138
+ <ol>
139
+ <li>"+ Custom construct" &gt; "Upload items from CSV/XLSX".</li>
140
+ <li>Try <a href="/samples/construct_items_demo.csv" download>construct_items_demo.csv</a>
141
+ (<code>item,reverse</code> columns — 1/true/yes/R = reverse),
142
+ <a href="/samples/construct_items_marker_demo.csv" download>construct_items_marker_demo.csv</a>
143
+ (single column with <code>(R)</code> markers), or
144
+ <a href="/samples/construct_items_demo.xlsx" download>construct_items_demo.xlsx</a> (Excel).</li>
145
+ <li>Expect: items fill the textarea ((R) appended where flagged), the filename becomes
146
+ the suggested name, and parse notes list skipped blanks/duplicates. Nothing is saved
147
+ until you review and press Save. Item files are never retained on the server.</li>
148
+ </ol>
149
+
150
+ <h2 id="warnings">4. Language, models, and warnings (Step 3 card + results)</h2>
151
+ <p>Run each of these and open the results page; the amber warnings panel should show exactly:</p>
152
+ <div class="tablewrap">
153
+ <table>
154
+ <tr><th>Corpus</th><th>Selection</th><th>Expected warnings</th></tr>
155
+ <tr><td><a href="/samples/warnings_showcase.csv" download>warnings_showcase.csv</a></td><td>en + MiniLM</td>
156
+ <td>EMPTY_ROWS_DROPPED (2), DUPLICATE_TEXTS (2), TEXT_TOO_SHORT (3), TEXTS_MAYBE_TRUNCATED (2); no language warnings</td></tr>
157
+ <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>
158
+ <tr><td>french_demo.csv</td><td>fr + MiniLM</td><td>MODEL_LANGUAGE_UNSUPPORTED</td></tr>
159
+ <tr><td>french_demo.csv</td><td>fr + Multilingual E5</td><td>no language warnings</td></tr>
160
+ <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>
161
+ <tr><td><a href="/samples/long_documents_demo.csv" download>long_documents_demo.csv</a></td><td>en + MiniLM</td>
162
+ <td>TEXTS_MAYBE_TRUNCATED (4) + LANGUAGE_UNCERTAIN (only 10 rows, below the 20-row minimum — by design)</td></tr>
163
+ </table>
164
+ </div>
165
+ <p>Warnings are per-run snapshots: changing language/model requires a NEW run; old result
166
+ pages don't update.</p>
167
+
168
+ <h2 id="results">5. Results and reproducibility</h2>
169
+ <ol>
170
+ <li>Run <a href="/samples/moral_foundations_demo.csv" download>moral_foundations_demo.csv</a>
171
+ against two different MFQ-2 foundations: top texts change per foundation; the 6
172
+ neutral rows sink to the bottom.</li>
173
+ <li>Results page: histogram, mean/SD/min/max, per-item loadings, top/bottom texts.</li>
174
+ <li>Downloads: results CSV (input columns + sim_item_N + ccr_score), metadata JSON
175
+ (model revision, construct snapshot + item hash, language block, environment pins),
176
+ reproduction script + requirements file.</li>
177
+ <li>Reproduction check: <code>pip install -r requirements-repro.txt</code>, then
178
+ <code>python reproduce_analysis.py your_corpus.csv</code> on a machine with no
179
+ platform access; values should match the export (target ~1e-5 with real models).</li>
180
+ </ol>
181
+
182
+ <h2 id="accounts">6. Accounts</h2>
183
+ <ol>
184
+ <li>Sign in (top right) &gt; "Create a free account" &gt; email + password (min 8 chars) —
185
+ or use "Continue with Google".</li>
186
+ <li>You're signed in immediately; the header shows your name.</li>
187
+ <li>Sign out, sign back in; wrong password gives "Incorrect email or password";
188
+ registering the same email again gives "already exists".</li>
189
+ <li>Email is case-insensitive. No self-service password reset yet — reset = admin action.
190
+ (And remember: password accounts vanish when this dev instance rebuilds.)</li>
191
+ </ol>
192
+
193
+ <h2 id="anon">7. Anonymous tiers (test signed OUT)</h2>
194
+ <ol>
195
+ <li>Upload caps: see section 2.</li>
196
+ <li>Run limit: run 3 analyses. The Step 3 card counts "X of 3 free runs used today".
197
+ The 4th run is refused with a sign-in prompt. Counter resets next day (UTC).
198
+ Signing in removes the limit.</li>
199
+ <li>Delete-after-analysis: run any corpus, open results (fine, downloadable), note the
200
+ info warning "uploaded file was deleted after this analysis". Re-running that same
201
+ corpus: refused ("upload again, or sign in").</li>
202
+ <li>TTL purge: anonymous projects older than 24&nbsp;h are deleted entirely (startup + hourly).</li>
203
+ </ol>
204
+
205
+ <h2 id="signedin">8. Signed-in tier</h2>
206
+ <ol>
207
+ <li>Sign in, upload, run: no ANONYMOUS_DATA_REMOVED warning; re-running the same corpus
208
+ works (file kept).</li>
209
+ <li>Saved-run cap: Step 3 card shows "N of 15 saved runs used". At the cap, new runs are
210
+ refused until you delete old runs/projects (nothing is auto-deleted).</li>
211
+ <li>Ownership: your projects are invisible to signed-out visitors and other accounts.
212
+ Anonymous projects stay shared.</li>
213
+ </ol>
214
+
215
+ <h2 id="perf">9. Performance behaviors</h2>
216
+ <ol>
217
+ <li>Corpus-embedding cache: run the SAME corpus with a second construct (signed in, same
218
+ model): the run skips document embedding and completes in seconds; metadata shows
219
+ <code>"doc_embeddings_from_cache": true</code>.</li>
220
+ <li>Duplicate texts are embedded once
221
+ (<a href="/samples/warnings_showcase.csv" download>warnings_showcase.csv</a> has 2
222
+ dupes): identical scores for identical texts, less compute.</li>
223
+ </ol>
224
+
225
+ <h2 id="feedback">10. Found something off?</h2>
226
+ <p>Anything that doesn't match what this guide says it should do — or anything confusing,
227
+ slow, or missing — please note the section number and what you saw, and send it to
228
+ Deva (<a href="mailto:devaanand@umass.edu">devaanand@umass.edu</a>). Screenshots welcome.</p>
229
+
230
+ </main>
231
+ <a class="top" href="#">↑ Top</a>
232
+ </body>
233
+ </html>
backend/app/main.py CHANGED
@@ -745,6 +745,25 @@ def export_script_requirements(job_id: str, db: Session = Depends(get_db)):
745
  )
746
 
747
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
  # ------------------------------------------------------------ static (SPA)
749
  STATIC_DIR = Path(__file__).resolve().parent.parent / "static"
750
  if STATIC_DIR.exists():
 
745
  )
746
 
747
 
748
+ # ------------------------------------------------------------ tester guide + samples
749
+ # /guide and /samples exist for the dev instance: a click-through testing guide
750
+ # for the PI/students and the synthetic demo corpora it references. guide.html
751
+ # lives in app/ (not static/, which `npm run build` wipes); sample_data/ sits at
752
+ # the repo root, same resolution as packages/ (= / in the container).
753
+ GUIDE_HTML = Path(__file__).resolve().parent / "guide.html"
754
+ SAMPLES_DIR = Path(__file__).resolve().parents[2] / "sample_data"
755
+
756
+
757
+ @app.get("/guide", include_in_schema=False)
758
+ def testing_guide():
759
+ if not GUIDE_HTML.exists():
760
+ raise HTTPException(404, "Guide not available on this instance.")
761
+ return FileResponse(GUIDE_HTML, media_type="text/html")
762
+
763
+
764
+ if SAMPLES_DIR.exists():
765
+ app.mount("/samples", StaticFiles(directory=SAMPLES_DIR), name="samples")
766
+
767
  # ------------------------------------------------------------ static (SPA)
768
  STATIC_DIR = Path(__file__).resolve().parent.parent / "static"
769
  if STATIC_DIR.exists():
backend/tests/test_guide_and_samples.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tester-facing extras: the /guide page and the /samples demo-data mount.
2
+
3
+ Both exist for the hosted dev instance so the PI/students can test without
4
+ being handed files out of band. They are static content, but regressions here
5
+ (a renamed sample file, a moved directory) would silently break the guide's
6
+ download links, so pin the contract.
7
+ """
8
+
9
+ import re
10
+ from pathlib import Path
11
+
12
+ from fastapi.testclient import TestClient
13
+
14
+ from app.main import SAMPLES_DIR, app
15
+
16
+ client = TestClient(app)
17
+
18
+
19
+ def test_guide_serves_html():
20
+ r = client.get("/guide")
21
+ assert r.status_code == 200
22
+ assert "text/html" in r.headers["content-type"]
23
+ assert "Testing Guide" in r.text
24
+
25
+
26
+ def test_samples_mount_serves_files():
27
+ r = client.get("/samples/sample_corpus.csv")
28
+ assert r.status_code == 200
29
+ assert r.text.splitlines()[0].startswith("id,")
30
+
31
+
32
+ def test_every_sample_link_in_guide_resolves():
33
+ """Every /samples/<file> href in guide.html must exist in sample_data/."""
34
+ html = (Path(__file__).resolve().parents[1] / "app" / "guide.html").read_text()
35
+ linked = set(re.findall(r'href="/samples/([^"]+)"', html))
36
+ assert linked, "guide.html should link to sample files"
37
+ missing = sorted(f for f in linked if not (SAMPLES_DIR / f).exists())
38
+ assert not missing, f"guide links to samples that do not exist: {missing}"