<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://clairenord.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://clairenord.com/" rel="alternate" type="text/html" /><updated>2026-08-31T23:18:57+00:00</updated><id>https://clairenord.com/feed.xml</id><title type="html">Claire Nord</title><subtitle>Claire&apos;s personal website</subtitle><author><name>Claire Nord</name></author><entry><title type="html">Do we have AI SRE yet?</title><link href="https://clairenord.com/do-we-have-ai-sre-yet.html" rel="alternate" type="text/html" title="Do we have AI SRE yet?" /><published>2026-08-20T00:00:00+00:00</published><updated>2026-08-20T00:00:00+00:00</updated><id>https://clairenord.com/do-we-have-ai-sre-yet</id><content type="html" xml:base="https://clairenord.com/do-we-have-ai-sre-yet.html"><![CDATA[<p>I never thought I’d say this, but I miss being an SRE in the Old Days:tm:<sup id="fnref:pre-ai"><a href="#fn:pre-ai" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<p>No matter how much code agents write for me today, I’m still the <a href="https://fortune.com/article/how-apple-works-inside-the-worlds-biggest-startup/">directly
responsible individual</a> for every bug. So when things go
wrong, I still have to crack open my laptop<sup id="fnref:chicken-farm"><a href="#fn:chicken-farm" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>.</p>

<p>But unlike the old days, I don’t know every line of code! I’m more like a manager
on call – <em>yikes</em>.</p>

<h2 id="on-call-with-ai-slower-more-stressful">On call with AI: slower, more stressful</h2>

<ol>
  <li>Get paged in Slack</li>
  <li>Ack the page</li>
  <li>Immediately copy+paste the page into my coding agent with “omg what is happening!!”</li>
  <li>Agonize as it methodically investigates the issue</li>
  <li>Steer random context into the chat as it goes</li>
</ol>

<p class="caption"><img src="/assets/img/agent-outage.png" alt="A chat with a coding agent: the user says &quot;omg our site is down please figure out what's happening!!&quot; and the agent starts methodically investigating, listing repo contents and grepping for the production URL">
<em>make no mistakes</em></p>

<p>Generally with enough tokens and time the agents resolve the outage, but:</p>

<ul>
  <li>
    <p><strong>Outage comms are worse.</strong>
I’m just pasting random hypotheses from my agent into the Slack thread.
I don’t have as good of a sense of the ETA to resolution.</p>
  </li>
  <li>
    <p><strong>The agent doesn’t work in outage mode by default.</strong>
The default coding agent behavior is to investigate, then make a speculative
forward fix.
That’s the wrong approach during an outage!
We should <a href="https://www.oreilly.com/content/generic-mitigations/">mitigate first</a> with the lowest-risk change – roll back! turn off the flag! –
rather than shipping <em>more</em> new code with less testing.</p>
  </li>
</ul>

<p><img src="https://www.oreilly.com/content/wp-content/uploads/sites/2/2020/12/02-2048x1126.png" alt="A timeline diagram of two responses to an outage: applying a generic mitigation halts user impact early, while investigating first lets the damage continue until the perfect fix ships. A cartoon volcanic island decorates each timeline, and its islander is much happier in the mitigate-early one"></p>

<p class="caption"><em>Source: <a href="https://www.oreilly.com/content/generic-mitigations/">Generic Mitigations</a> by Jennifer Mace, O’Reilly Radar</em></p>

<p>After a few sweaty incidents, we’ve changed how we build to make being on call easier
with agents.</p>

<h2 id="feature-flag-everything">Feature flag everything</h2>

<p>The simplest tip I got from <a href="https://x.com/alonzuman">Alon</a> to ship faster was to <strong>feature flag
everything</strong> and test in prod.
When starting a new project I build feature flag support right away.
Feature flagging is an acknowledgment that I know <em>so</em> little about the code
that I need a big red switch to manage it.</p>

<p>This was mostly a mindset change to remember to prompt “…and put this behind a
flag”.
Beyond that, a line in <code class="language-plaintext highlighter-rouge">AGENTS.md</code> like “we use PostHog for feature flags” and a file
called <code class="language-plaintext highlighter-rouge">features.ts</code> are enough.</p>

<p>We don’t ask the agents to decide what to feature flag themselves yet.
Do you?
Should we?</p>

<h2 id="write-a-docskill-explaining-how-you-do-release-management">Write a doc/skill explaining how you do release management</h2>

<p>We built the <a href="https://napkinmath.club">Napkin Math</a> iOS and Android apps in the YC Spring
2026 batch using <a href="https://expo.dev">Expo</a> with React Native.</p>

<p>Expo’s magic trick is the “over-the-air update” – a React Native app is built
with a native layer and an update layer of JavaScript and assets that can be
updated over-the-air (OTA) without the user needing to visit the App Store.</p>

<p><img src="https://docs.expo.dev/static/images/eas-update/layers.png" alt="Expo update layers"></p>

<p class="caption"><em>Source: <a href="https://docs.expo.dev/eas-update/how-it-works/">How EAS Update works</a>, Expo Docs</em></p>

<p>OTAs can be super useful to mitigate an outage if done right!</p>

<details>
  <summary>More on how we did Expo updates</summary>

  <p>We generally followed <a href="https://expo.dev/blog/5-ota-update-best-practices-every-mobile-team-should-know">Expo OTA best practices</a>.</p>

  <p>We used the Expo <a href="https://docs.expo.dev/eas-update/runtime-versions/#use-a-runtime-version-policy-that-automatically-updates-the-runtime-version-when-native-code-is-updated"><code class="language-plaintext highlighter-rouge">appVersion</code></a> policy with the <a href="https://trunkbaseddevelopment.com/branch-for-release/">release
branch</a> pattern from trunk-based development.
There’s one Git branch per runtime version.</p>

  <p>Suppose <code class="language-plaintext highlighter-rouge">main</code> targets runtime version <code class="language-plaintext highlighter-rouge">1.0.17</code> and a PR lands a breaking native
change:</p>

  <ul>
    <li>The PR with the breaking native change bumps the app version to <code class="language-plaintext highlighter-rouge">1.0.18</code>.</li>
    <li>Right before merging, we branch <code class="language-plaintext highlighter-rouge">1.0.17-ota</code> off of <code class="language-plaintext highlighter-rouge">main</code> and push a final
OTA to <code class="language-plaintext highlighter-rouge">1.0.17</code> users.</li>
    <li>Merge the PR. After the merge, updates published from <code class="language-plaintext highlighter-rouge">main</code> target <code class="language-plaintext highlighter-rouge">1.0.18</code>.</li>
    <li>If there’s a JavaScript bug introduced in <code class="language-plaintext highlighter-rouge">1.0.17</code> we need to mitigate for
users on both versions, we fix it on <code class="language-plaintext highlighter-rouge">main</code>, cherry-pick it onto <code class="language-plaintext highlighter-rouge">1.0.17-ota</code>,
and then push OTAs from both branches.</li>
  </ul>

</details>

<p>After I explained this release management system to agents mid-outage a few
times, it was helpful to just write a doc into the repo.
I’d recommend you do the same!
Everyone’s release process is a different special snowflake, so it’s probably
not just <a href="https://www.newyorker.com/tech/annals-of-technology/chatgpt-is-a-blurry-jpeg-of-the-web">In The Weights</a>.</p>

<h3 id="recursive-self-improvement-postmortems-and-runbooks">Recursive self-improvement: postmortems and runbooks</h3>

<p>I confess: we haven’t done this one yet!
Have you?</p>

<p>A few times now we’ve “fixed” a bug, only to have it crop up again.
The agents fixed some narrower version of the issue or didn’t test the right
thing before declaring victory.</p>

<p>In a repeat outage it’s stressful to watch the agent plod down the same
investigation/discovery path, wasting tokens and time.
I don’t want them to root-cause the same issue twice!</p>

<p>I’m thinking of adding a simple <a href="https://www.lesswrong.com/posts/JBadX7rwdcRFzGuju/recursive-self-improvement">recursive self-improvement</a> loop for the
repo where we have:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docs
├── postmortems
│   └── 2026-08-18-photo-upload-failed-....md
└── runbooks
    └── expo-updates.md
</code></pre></div></div>

<p>I’m not sure what threshold for notability I’d use to warrant a postmortem or
runbook except “when I say so”.
But I’d want these to be primarily <a href="https://x.com/jynniit/status/2089860109707125033">agent-readable</a> (not explicitly
designed to be human-readable) docs.</p>

<h2 id="is-this-cope">Is this cope?</h2>

<p>How much <em>should</em> we change how we build software to adapt to coding agents, and
how much should we just wait for the next model release?</p>

<p>My thought is:</p>

<ul>
  <li>if it’s generically making the agent “code better”, it’s probably gonna be in
the weights.
That’s why I won’t write a generic “on-call” skill which would basically be
“follow the Google SRE book”.</li>
  <li>if it’s about how your specific team/company does things, might as well make
it legible to agents by writing it down</li>
</ul>

<p>I still hold tightly (too tightly?) to some aspects of software development.</p>

<p>Maybe someday soon AI will replace my SRE job and we’ll delete all the docs and
walk into the sunset building freely :)</p>

<h2 id="related-reading">Related reading</h2>

<p>I read these after writing the above post.
These writers are thinking along similar lines!</p>


<a class="rich-link" href="https://every.to/chain-of-thought/compound-engineering-how-every-codes-with-agents" referrerpolicy="no-referrer" target="_blank">
  <span class="rich-link__body">
    <strong class="rich-link__title">Compound Engineering: How Every Codes With Agents </strong>
    
    
      <span class="rich-link__description">A four-step engineering process for software teams that don’t write code</span>
    
    <span class="rich-link__url">https://every.to/chain-of-thought/compound-engineering-how-every-codes-with-agents</span>
  </span>
  
    <img class="rich-link__image" src="https://d24ovhgu8s7341.cloudfront.net/uploads/post/social_media_image/3866/full_page_cover_compoundmagician.png" alt="Compound Engineering: How Every Codes With Agents  preview" referrerpolicy="no-referrer" loading="lazy">
  
</a>



<a class="rich-link" href="https://charity.wtf/p/ai-demands-more-engineering-discipline" referrerpolicy="no-referrer" target="_blank">
  <span class="rich-link__body">
    <strong class="rich-link__title">AI demands more engineering discipline. Not less</strong>
    
    
      <span class="rich-link__description">If you lived through the shift from handcrafted server pets to immutable infrastructure, you should sense something oddly familiar about what's happening now.</span>
    
    <span class="rich-link__url">https://charity.wtf/p/ai-demands-more-engineering-discipline</span>
  </span>
  
    <img class="rich-link__image" src="https://substackcdn.com/image/fetch/$s_!aax3!,w_1200,h_675,c_fill,f_jpg,q_auto:good,fl_progressive:steep,g_auto/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef553825-6e34-4653-ba0d-3e9b416c225f_1774x887.png" alt="AI demands more engineering discipline. Not less preview" referrerpolicy="no-referrer" loading="lazy">
  
</a>




<a class="rich-link" href="https://rootly.com/blog/stop-trying-to-review-ais-code-faster-bet-on-rollbacks-instead" referrerpolicy="no-referrer" target="_blank">
  <span class="rich-link__body">
    <strong class="rich-link__title">Stop trying to review AI's code faster: bet on rollback instead | Rootly</strong>
    
    
    <span class="rich-link__url">https://rootly.com/blog/stop-trying-to-review-ais-code-faster-bet-on-rollbacks-instead</span>
  </span>
  
    <img class="rich-link__image" src="https://cdn.prod.website-files.com/65eead4fb17e4a53aebb2a91/69fa13a3e4c4bb19dbcfbf43_panel-q.avif" alt="Stop trying to review AI's code faster: bet on rollback instead | Rootly preview" referrerpolicy="no-referrer" loading="lazy">
  
</a>



<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:pre-ai">
      <p>Pre-AI.&nbsp;<a href="#fnref:pre-ai" class="reversefootnote" role="doc-backlink">↩</a></p>
    </li>
    <li id="fn:chicken-farm">
      <p>Or <a href="https://conductor.farm">chicken farm</a> on my phone&nbsp;<a href="#fnref:chicken-farm" class="reversefootnote" role="doc-backlink">↩</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Claire Nord</name></author><category term="coding agents" /><category term="site reliability engineering" /><category term="release management" /><category term="software engineering" /><summary type="html"><![CDATA[I miss being an SRE in the Old Days. Here's how we do release management with agents now]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/agent-outage.png" /><media:content medium="image" url="https://clairenord.com/assets/img/agent-outage.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">whoport - who is using my port?</title><link href="https://clairenord.com/whoport.html" rel="alternate" type="text/html" title="whoport - who is using my port?" /><published>2026-08-11T00:00:00+00:00</published><updated>2026-08-11T00:00:00+00:00</updated><id>https://clairenord.com/whoport</id><content type="html" xml:base="https://clairenord.com/whoport.html"><![CDATA[<p>Recently I’ve been running swarms of coding agents on my laptop.
At some point I started to feel like my laptop is haunted, being puppeted by many beings out of my control.</p>

<p>For example, I’ll go to develop on <a href="/jep.html">jep</a>, and run into:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">%</span><span class="w"> </span>npm run dev
<span class="go">
&gt; dev
&gt; react-router dev

Port 3000 is in use, trying another one...
  ➜  Local:   http://localhost:3001/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
</span></code></pre></div></div>

<p>Hey! <strong>Who is using <code class="language-plaintext highlighter-rouge">localhost:3000</code> ?!</strong></p>

<h2 id="how-to-use-whoport">How to use <code class="language-plaintext highlighter-rouge">whoport</code></h2>

<p><code class="language-plaintext highlighter-rouge">whoport</code> is my new <a href="http://www.catb.org/esr/writings/taoup/html/ch13s01.html#id2964843">small, sharp tool</a> to solve this. Pass in a port and it’ll tell you what command (and therefore what path -&gt; project + worktree) is using it:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">%</span><span class="w"> </span>whoport 3000
<span class="go">PORT   PID      COMMAND
3000   61981    node ~/conductor/workspaces/echo/rome-v3/target/debug/api
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">whoport</code> can look up multiple ports at once:</p>

<p><img src="/assets/img/whoport.png" alt="A Solarized Light terminal showing whoport output for six development processes in city-named Conductor worktrees" class="hero-image"></p>

<p>Check it out here on GitHub 🐈‍⬛</p>


<a class="rich-link" href="https://github.com/cmnord/whoport" referrerpolicy="no-referrer" target="_blank">
  <span class="rich-link__body">
    <strong class="rich-link__title">GitHub - cmnord/whoport: Who's using this port?</strong>
    
    
      <span class="rich-link__description">Who's using this port? Contribute to cmnord/whoport development by creating an account on GitHub.</span>
    
    <span class="rich-link__url">https://github.com/cmnord/whoport</span>
  </span>
  
    <img class="rich-link__image" src="https://opengraph.githubassets.com/66ca1e4608eb11163fdc657637a8fabb7ed02ba4a2d0844ef04667aa71f3fdbc/cmnord/whoport" alt="GitHub - cmnord/whoport: Who's using this port? preview" referrerpolicy="no-referrer" loading="lazy">
  
</a>


<p>After you’ve found the process using your port, you can obviously <code class="language-plaintext highlighter-rouge">kill -9</code> it 🔪 or otherwise shuffle ports around.</p>

<h2 id="why-ps--grep-falls-apart-with-coding-agents">Why? <code class="language-plaintext highlighter-rouge">ps</code> + <code class="language-plaintext highlighter-rouge">grep</code> falls apart with coding agents</h2>

<p>My previous tool for this was good ol’ <a href="https://man7.org/linux/man-pages/man1/ps.1.html"><code class="language-plaintext highlighter-rouge">ps</code></a> with some text searches.
<code class="language-plaintext highlighter-rouge">ps</code> (“process status”) lists information about processes running on your
computer.
The typical command is <code class="language-plaintext highlighter-rouge">ps aux</code> to list <strong>a</strong>ll running processes.
I used this in combination with a search tool like <a href="https://man7.org/linux/man-pages/man1/grep.1.html"><code class="language-plaintext highlighter-rouge">grep</code></a> (or <a href="https://github.com/ggreer/the_silver_searcher"><code class="language-plaintext highlighter-rouge">ag</code></a> or, lately,
<a href="https://github.com/burntsushi/ripgrep"><code class="language-plaintext highlighter-rouge">rg</code></a>) like so:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Atuin v18.19.0                                                               &lt;esc&gt;: exit, &lt;tab&gt;: edit, &lt;enter&gt;: run, &lt;ctrl-o&gt;: inspect                                                        history count: 18631
  Search │ Inspect

  3 111ms   3mo ago ps aux | rg 'expo start'
  2 101ms   3mo ago ps aux | rg 'dev:'
  1 128ms   2mo ago ps aux | rg 'expo'
  &gt; 123ms   2mo ago ps aux | rg 'expo '
 [     GLOBAL     ] 'ps aux'
 ps aux | rg 'expo '
</code></pre></div></div>
<p><em class="caption">shoutout to <a href="https://atuin.sh">atuin</a></em></p>

<p><code class="language-plaintext highlighter-rouge">ps aux | rg 'npm '</code> worked when I had a <em>hunch</em> about the name of the command
and I could search them.
But now I have like 20–50 node processes running on my computer at any time…</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">%</span><span class="w"> </span>ps aux | rg <span class="s1">'node'</span> | <span class="nb">wc</span> <span class="nt">-l</span>
<span class="go">      53
</span><span class="gp">%</span><span class="w"> </span>ps aux | rg <span class="s1">'npm '</span> | <span class="nb">wc</span> <span class="nt">-l</span>
<span class="go">      28
</span></code></pre></div></div>
<p><em class="caption">translation: count how many running processes begin with <code class="language-plaintext highlighter-rouge">'node '</code> or <code class="language-plaintext highlighter-rouge">'npm '</code></em></p>

<p>Moreover, the commands agents kick off are completely unreadable…</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">%</span><span class="w"> </span>ps aux | rg <span class="s1">'npm '</span> | <span class="nb">head</span> <span class="nt">-n</span> 2
<span class="go">cnord            21752   0.1  0.4 446355296 149152   ??  S     8:02PM   0:00.22 node /Users/cnord/.nvm/versions/node/v22.22.3/bin/pnpm -C packages/editor exec vitest run --environment jsdom src/note/index.test.ts src/plugins/doc-change-listener.test.ts
</span></code></pre></div></div>
<p>↳ <em>Okay, seems like it’s running Vitest, that’s fine.</em></p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">cnord             6522   0.0  0.0 435341104   5136   ??  S     7:46PM   0:00.01 /usr/bin/open -n -W -o target/debug/example-dev.stdout.log --stderr target/debug/example-dev.stderr.log --env RANDOM_CONFIG_VAR=foo --env VITE_API_URL=https://api.example.com --env YOUR_SECRET_KEY=hunter2 --env YOUR_RANDOM_OAUTH_CLIENT_ID=hunter2 --env PATH=./my/whole/hecking/path/list/.bin:/......... target/debug/Example.app
</span></code></pre></div></div>
<p>↳ <em>What in the world is happening in this 3,000-character command which has my whole env and <code class="language-plaintext highlighter-rouge">PATH</code> in it???</em></p>

<p><em>which of you is hogging my port?</em></p>

<h2 id="whoport-under-the-hood"><code class="language-plaintext highlighter-rouge">whoport</code> under the hood</h2>

<p><code class="language-plaintext highlighter-rouge">whoport</code> glues together ~two other tools: stalwart <code class="language-plaintext highlighter-rouge">ps</code>, plus <a href="https://man7.org/linux/man-pages/man8/lsof.8.html"><code class="language-plaintext highlighter-rouge">lsof</code></a> to list open files.</p>

<p>The core <code class="language-plaintext highlighter-rouge">lsof</code> command is:</p>

<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsof <span class="nt">-t</span> <span class="nt">-nP</span> <span class="nt">-iTCP</span>:<span class="s2">"</span><span class="nv">$port</span><span class="s2">"</span> <span class="nt">-sTCP</span>:LISTEN
</code></pre></div></div>

<p>Brief explanation of the command:</p>

<table>
  <thead>
    <tr>
      <th>flag(s)</th>
      <th>meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-t</code></td>
      <td><strong>t</strong>erse output: PIDs only, no header</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-nP</code></td>
      <td>skip DNS resolution and service name lookups</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-iTCP:$port</code></td>
      <td>only TCP sockets using this port (local or remote)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-sTCP:LISTEN</code></td>
      <td>only TCP sockets in listen state</td>
    </tr>
  </tbody>
</table>

<p>So with just this command we can get a newline-separated list of process IDs (PIDs).</p>

<p><code class="language-plaintext highlighter-rouge">whoport</code> turns this into a comma-separated list to hand to <code class="language-plaintext highlighter-rouge">ps</code>.</p>

<p>Heroic <code class="language-plaintext highlighter-rouge">ps</code> returns, instead filtering by PID instead of search on the commands!</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">%</span><span class="w"> </span>ps <span class="nt">-o</span> <span class="nv">pid</span><span class="o">=</span>61981,command<span class="o">=</span>COMMAND <span class="nt">-p</span> 61981
<span class="go">61981 COMMAND
61981 node ~/conductor/workspaces/echo/rome-v3/target/debug/api
</span></code></pre></div></div>

<p>So now I know that the <code class="language-plaintext highlighter-rouge">rome-v3</code> echo worktree is using the port!
And from there I can <em>*ahem*</em> terminate it or move aside.
<code class="language-plaintext highlighter-rouge">localhost:3001</code> isn’t so bad, anyway.</p>

<h2 id="filling-out-your-tool-belt">Filling out your tool belt</h2>

<p>I keep little scripts like this in my <a href="https://github.com/cmnord/dotfiles">dotfiles</a> repo, which documents my favorite terminal commands and setup across laptops and machines. 🛠️</p>

<p>You should too! Some influential blogs on this topic:</p>
<ul>
  <li><a href="https://zachholman.com/2010/08/dotfiles-are-meant-to-be-forked/">Your Dotfiles Are Meant to be Forked</a>, Zach Holman</li>
  <li><a href="https://anishathalye.com/managing-your-dotfiles/">Managing Your Dotfiles</a>, Anish Athalye</li>
</ul>

<p>Read more about tiny personal programs here:</p>

<ul>
  <li><a href="https://jvns.ca/blog/2022/03/08/tiny-programs/">Some tiny personal programs I’ve written</a>, Julia Evans</li>
</ul>

<p>And to learn more about the command line, <code class="language-plaintext highlighter-rouge">ps</code>, etc., I highly recommend <a href="https://missing.csail.mit.edu/2020/command-line/">The Missing Semester of Your CS Education</a>.</p>

<p>Happy coding :)</p>]]></content><author><name>Claire Nord</name></author><category term="software engineering" /><category term="developer tools" /><category term="shell" /><category term="macOS" /><category term="coding agents" /><category term="projects" /><summary type="html"><![CDATA[Small bash script to identify which coding agent is taking your `localhost:3000`]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/whoport.png" /><media:content medium="image" url="https://clairenord.com/assets/img/whoport.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jep! - play Jeopardy! with friends online</title><link href="https://clairenord.com/jep.html" rel="alternate" type="text/html" title="Jep! - play Jeopardy! with friends online" /><published>2023-07-26T00:00:00+00:00</published><updated>2023-07-26T00:00:00+00:00</updated><id>https://clairenord.com/jep</id><content type="html" xml:base="https://clairenord.com/jep.html"><![CDATA[<p>Play Jeopardy! online with your friends at <a href="https://whatis.club/">Jep!</a>. Choose from past games
or make your own. Just share the link to play with friends.</p>

<p>:earth_americas: Play a game: <a href="https://whatis.club/">https://whatis.club</a></p>

<p>:computer: GitHub repository:</p>


<a class="rich-link" href="https://github.com/cmnord/jep" referrerpolicy="no-referrer" target="_blank">
  <span class="rich-link__body">
    <strong class="rich-link__title">GitHub - cmnord/jep: 🌎 Jep! multiplayer trivia online</strong>
    
    
      <span class="rich-link__description">🌎 Jep! multiplayer trivia online. Contribute to cmnord/jep development by creating an account on GitHub.</span>
    
    <span class="rich-link__url">https://github.com/cmnord/jep</span>
  </span>
  
    <img class="rich-link__image" src="https://opengraph.githubassets.com/5e7d95e001067d9fd831fce0198aacb0b36de2bc7fa919aa796a6b096b7880de/cmnord/jep" alt="GitHub - cmnord/jep: 🌎 Jep! multiplayer trivia online preview" referrerpolicy="no-referrer" loading="lazy">
  
</a>


<p><img src="/assets/img/jep/screenshot.png" alt="A Jep! game board with six categories and two players, Claire and Neil" class="hero-image"></p>

<p>If you’re curious to learn more, this blog post explains further rules and how
it’s built.</p>

<ul class="table-of-contents">
  <li><a href="#what-it-does">What it does</a>
    <ul>
      <li><a href="#real-time-gameplay">Real-time gameplay</a></li>
      <li><a href="#upload-your-own-games">Upload your own games</a></li>
    </ul>
  </li>
  <li><a href="#how-its-built">How it’s built</a>
    <ul>
      <li><a href="#room-logic-state-machine">Room logic (state machine)</a></li>
      <li><a href="#storing-room-state">Storing room state</a></li>
      <li><a href="#timeout-actions--serverless-functions">Timeout actions + serverless functions</a></li>
      <li><a href="#real-time-updates">Real-time updates</a></li>
    </ul>
  </li>
  <li><a href="#designing-jeopardy-without-a-host">Designing Jeopardy! without a host</a>
    <ul>
      <li><a href="#check-your-own-answers">Check your own answers</a></li>
      <li><a href="#say-your-guess-then-check-privately">Say your guess, then check privately</a></li>
      <li><a href="#keeping-up-momentum">Keeping up momentum</a></li>
    </ul>
  </li>
  <li><a href="#conclusion">Conclusion</a></li>
</ul>

<h2 id="what-it-does">What it does</h2>

<h3 id="real-time-gameplay">Real-time gameplay</h3>

<p>When you click on a clue on the board, it pops up on the screen for every player
at the same time.</p>

<figure class="local-video">
  <video class="local-video__player" aria-label="Jep real-time gameplay demonstration" preload="metadata" poster="/assets/img/jep/video-poster.jpg" autoplay="" loop="" muted="" playsinline="">
    <source src="/assets/video/jep.mp4" type="video/mp4">
    <a href="/assets/video/jep.mp4">Watch Jep real-time gameplay demonstration</a>.
  </video>
  
    <figcaption><em>Firefox chooses a clue. Safari then buzzes in first.</em></figcaption>
  
  <p class="local-video__email-fallback"><a href="/assets/video/jep.mp4">Watch Jep real-time gameplay demonstration</a></p>
</figure>

<p>You all get a chance to read the clue, then race to buzz in. The fastest buzzer
gets to answer the clue and gets points if they’re right. If they’re wrong, the
buzzers reopen for all other players.</p>

<h3 id="upload-your-own-games">Upload your own games</h3>

<p>I made a JSON file format so people can upload their own games. The format
allows for an arbitrary number of rounds, categories per round, and clues per
category. I hope this flexibility is fun for constructors!</p>

<p>The <a href="https://whatis.club/upload-help">upload help</a> page describes the format and has a sample file.</p>

<p class="caption"><img src="/assets/img/jep/big-board.png" alt="Biiig board">
<em>Biiig board</em></p>

<p>Games can also be <em>unlisted</em> or <em>private</em> so constructors can play their games or
playtest draft games without sharing them widely.</p>

<p class="caption"><img src="/assets/img/jep/visibility.png" alt="Edit game visibility">
<em>Edit game visibility</em></p>

<h2 id="how-its-built">How it’s built</h2>

<p>Jep! is built in <a href="https://react.dev/">React</a> using the <a href="https://remix.run/">Remix</a> web framework. The
backend is not a persistent server but rather a collection of <a href="https://en.wikipedia.org/wiki/Serverless_computing">serverless
functions</a> that respond to HTTP requests. All persistent data is
stored in a database. The frontend uses <a href="https://en.wikipedia.org/wiki/WebSocket">websockets</a> to subscribe to
real-time updates to the database.</p>

<p><img src="/assets/img/jep/system-diagram.jpg" alt="System diagram"></p>

<h3 id="room-logic-state-machine">Room logic (state machine)</h3>

<p>Choosing a game from the home page creates a new “room”. Every player joins the
room from their own web browser. Each player sees the same initial room state
(round not yet started, all clues are hidden, everyone’s score is 0, etc.).</p>

<p>Players then take actions in the room that modify the state. For example, a
player chooses a clue, then buzzes in to answer.</p>

<p>The room <a href="https://en.wikipedia.org/wiki/Finite-state_machine">state machine</a> is implemented as a <a href="https://react.dev/learn/extracting-state-logic-into-a-reducer">reducer</a>
in React.</p>

<p class="caption"><img src="/assets/img/jep/state-machine.jpg" alt="Simplified room state machine">
<em>Simplified room state machine.</em></p>

<p>Each room state also has a corresponding client-only mini state machine.</p>

<p class="caption"><img src="/assets/img/jep/frontend-state-machine.jpg" alt="Simplified state machine for a client in the &quot;reading clue&quot; room state">
<em>Simplified state machine for a client in the “reading clue” room state.</em></p>

<h3 id="storing-room-state">Storing room state</h3>

<p>Instead of storing the room state itself in one database row, we store the list
of <em>actions</em> that have occurred in the room.</p>

<ul>
  <li><strong>Reads:</strong> Fetch all the actions for the room, then apply each action one by
one to the initial state to get the current state.</li>
  <li><strong>Writes:</strong> When a player takes a new action, the client sends an HTTP POST
request to the server, which appends it to the list of actions in the database.</li>
</ul>

<p>This is called an <a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing">event sourcing</a> architecture. Writes are fast
because they append to the list of actions without worrying about the current
state or any other actions. It also makes it easy to replay the actions to
compute the state at any point in time.</p>

<p>Each client has a local state machine which computes the current state from past
actions.</p>

<table>
  <thead>
    <tr>
      <th><strong>ID</strong></th>
      <th><strong>Room ID</strong></th>
      <th><strong>Action Type</strong></th>
      <th><strong>Action Data</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>803</td>
      <td>join</td>
      <td><code class="language-plaintext highlighter-rouge">{name: player 1, userId: 123}</code></td>
    </tr>
    <tr>
      <td>2</td>
      <td>803</td>
      <td>join</td>
      <td><code class="language-plaintext highlighter-rouge">{name: player 2, userId: 456}</code></td>
    </tr>
    <tr>
      <td>3</td>
      <td>803</td>
      <td>start round</td>
      <td><code class="language-plaintext highlighter-rouge">{round: 0}</code></td>
    </tr>
    <tr>
      <td>3</td>
      <td>803</td>
      <td>choose clue</td>
      <td><code class="language-plaintext highlighter-rouge">{i: 0, j: 0, userId: 123}</code></td>
    </tr>
    <tr>
      <td>4</td>
      <td>803</td>
      <td>buzz in</td>
      <td><code class="language-plaintext highlighter-rouge">{i: 0, j: 0, userId: 456, deltaMs: 351}</code></td>
    </tr>
    <tr>
      <td>5</td>
      <td>803</td>
      <td>buzz in</td>
      <td><code class="language-plaintext highlighter-rouge">{i: 0, j: 0, userId: 123, deltaMs: 489}</code></td>
    </tr>
    <tr>
      <td>6</td>
      <td>803</td>
      <td>check answer</td>
      <td><code class="language-plaintext highlighter-rouge">{i: 0, j: 0, userId: 456, correct: true}</code></td>
    </tr>
  </tbody>
</table>

<p class="caption"><em>Example actions table</em></p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">state</span><span class="p">:</span> <span class="nx">State</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">type</span><span class="p">:</span> <span class="nx">RoomState</span><span class="p">.</span><span class="nx">RevealAnswerToAll</span><span class="p">,</span>
  <span class="na">activeClue</span><span class="p">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
  <span class="na">boardControl</span><span class="p">:</span> <span class="mi">456</span><span class="p">,</span>
  <span class="na">buzzes</span><span class="p">:</span> <span class="p">{</span> <span class="mi">123</span><span class="p">:</span> <span class="mi">489</span><span class="p">,</span> <span class="mi">456</span><span class="p">:</span> <span class="mi">351</span> <span class="p">},</span>
  <span class="na">isAnswered</span><span class="p">:</span> <span class="p">[</span>
    <span class="p">[{</span> <span class="na">isAnswered</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">answeredBy</span><span class="p">:</span> <span class="mi">456</span> <span class="p">}</span> <span class="cm">/* ... */</span><span class="p">],</span>
    <span class="c1">// ...</span>
  <span class="p">],</span>
  <span class="na">players</span><span class="p">:</span> <span class="p">{</span>
    <span class="mi">123</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">player 1</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">score</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="mi">456</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">player 2</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">score</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
    <span class="p">},</span>
  <span class="p">},</span>
  <span class="na">round</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
  <span class="c1">// ... more state</span>
<span class="p">};</span>
</code></pre></div></div>

<p class="caption"><em>State computed from example actions above</em></p>

<h3 id="timeout-actions--serverless-functions">Timeout actions + serverless functions</h3>

<p>Some actions aren’t really player-initiated, but have to be modeled as such
because there’s no persistent server running on the backend. For example, what
if no players buzz in within 5 seconds to answer a clue? There’s no server-side
timer tracking this, so all the players would be stuck waiting for someone to
buzz in.</p>

<p>To fix this, each client submits a “5 seconds elapsed” action after the timeout.
Upon receiving any “5 seconds elapsed” message the state machine advances the
room state to the next step.</p>

<h3 id="real-time-updates">Real-time updates</h3>

<p>If Player 1 chooses a clue, how does Player 2 see that clue pop up on their
screen at the same time? In other words, how do they read one anothers’ writes
in real time without refreshing the page?</p>

<p>The client subscribes to real-time updates from the “actions” table. This opens
a websocket connection to the <a href="https://supabase.com/realtime">Supabase realtime</a> server. When the actions table
receives a new row, the server broadcasts the row to all clients subscribed to
that action’s room ID.</p>

<p>The client then receives that action and applies it to its local state machine.
This way all clients have a consistent view of the room state.</p>

<p class="caption"><img src="/assets/img/jep/supabase-realtime.png" alt="Example use case from the Supabase realtime page">
<em>Example use case from the <a href="https://supabase.com/realtime">Supabase realtime</a> page</em></p>

<h2 id="designing-jeopardy-without-a-host">Designing Jeopardy! without a host</h2>

<p>The biggest difference between the show and Jep! is that there’s <strong>no host</strong> to
communicate what’s happening at any given time, keep up the momentum, and check
answers. The first two can be addressed with good design, but the last one is
trickier.</p>

<h3 id="check-your-own-answers">Check your own answers</h3>

<p>I didn’t want to require one player be the host, or make any player check other
players’ answers. I also didn’t want to try to make a ~virtual host~ for a
couple reasons:</p>

<ol>
  <li>There are often multiple correct answers to a question, like “aubergine” vs
“eggplant” or “Queen Elizabeth” vs “Elizabeth I”. I don’t have an automated way
to check this for all questions.</li>
  <li>I don’t want players to have to type, which means the virtual host would have
to use speech recognition. I find speech recognition annoying and error-prone.</li>
</ol>

<p>Having each player check their own answers keeps the game fast-paced and doesn’t
put the hosting burden on one person in particular.</p>

<p>Groups also develop house rules for which answers are “close enough” to be
correct. Self-checking helps account for more and less strict play styles.
However, it’s not as suited to a competitive tournament setting.</p>

<h3 id="say-your-guess-then-check-privately">Say your guess, then check privately</h3>

<p>We keep players honest by having them say their guess out loud to others before
checking it. This requires players to join a voice call or the same physical
room together.</p>

<p>Here’s an example transcript:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PLAYER 1: Ferns for $600.

**Text: FERNS FIRST EMERGE IN THE FOSSIL RECORD IN THIS PERIOD**

PLAYER 1: [Buzzes in]
PLAYER 1: Hmm... what is the Jurassic period?
PLAYER 1: [Privately sees the answer]
PLAYER 1: Drat!
PLAYER 1: [Marks "incorrect"]

**Buzzers reopen**

PLAYER 2: [Buzzes in]
PLAYER 2: Ah, it's me! What is the Devonian period?
PLAYER 2: [Privately sees the answer]
PLAYER 2: Yep!
PLAYER 2: [Marks "correct"]

**Text: DEVONIAN**
</code></pre></div></div>

<p class="caption"><em>Example game transcript with honest players</em></p>

<p>With this scheme, you can tell later if someone lied. If they said “Jurassic”
was correct, you’d know they lied when the game later showed the answer was
“Devonian”.</p>

<p>This design <em>is</em> clunkier than having a host: it’s hart not to blurt out the
answer as soon as you learn you were wrong! It adds more cognitive burden during
an emotional moment in the game.</p>

<h3 id="keeping-up-momentum">Keeping up momentum</h3>

<p>I tried to make the visuals as clear as possible, but it’s still sometimes
confusing who’s up next to choose the clue or who won the buzz-in. This
confusion slows the game momentum and keeps it from moving as snappily as a
20-minute TV show.</p>

<p>I also love using my phone for everything and tried to make the game
mobile-friendly, but not everything can fit on phone screens smaller than mine.
There’s also a design tension between showing all possible information and
controlling what’s shown to keep the stakes high.</p>

<p>These problems are related! I definitely hit the limits of my UI design skills.
I would love to get specific feedback or designs that would improve this. For
now, I’m happy the game generally works and am ready to move on to other
projects.</p>

<h2 id="conclusion">Conclusion</h2>

<p>A major inspiration for Jep! was <a href="https://github.com/downforacross/downforacross.com">Down for a Cross</a>, a collaborative
crossword website. I wanted to make something similar for Jeopardy! to hopefully
give back to the trivia fan community.</p>

<p>I implemented a minimal set of features from Down for a Cross, plus the
Jeopardy! game logic. Some fun additions could be:</p>

<ul>
  <li>Timing the game with a clock</li>
  <li>An in-browser puzzle creator instead of just JSON files</li>
  <li>Coryat score tracking</li>
  <li>…<a href="https://github.com/cmnord/jep/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement">And more!</a></li>
</ul>

<p>Thank you to all the playtesters who gave great feedback! Your insight into my
fundamental design problems kept me awake at night. Thanks in particular to
Alex, Jynnie, Tommy, and Neil, as well as the NCSJ, JJSR, MJKT+, and GACJI
playtest groups.</p>

<p>I hope people enjoy playing Jep! and I’d love to hear any feedback or ideas for
improvements. Play a game <a href="https://whatis.club/">here</a> and check out the code <a href="https://github.com/cmnord/jep">here</a>!</p>]]></content><author><name>Claire Nord</name></author><category term="software engineering" /><category term="web development" /><category term="system design" /><category term="projects" /><category term="trivia" /><summary type="html"><![CDATA[Announcing Jep! and how I built it]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/jep/screenshot.png" /><media:content medium="image" url="https://clairenord.com/assets/img/jep/screenshot.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Five Types of Sports</title><link href="https://clairenord.com/the-five-types-of-sports.html" rel="alternate" type="text/html" title="The Five Types of Sports" /><published>2022-03-25T00:00:00+00:00</published><updated>2022-03-25T00:00:00+00:00</updated><id>https://clairenord.com/the-five-types-of-sports</id><content type="html" xml:base="https://clairenord.com/the-five-types-of-sports.html"><![CDATA[<p>All sports can be grouped into five categories by their rules. These rules set
constraints which lead to similar emergent behaviors and strategies in each
category.</p>

<p class="caption"><img src="/assets/img/sports.png" alt="Saweetie's Fast (Motion) cover art edited with CV bounding boxes" class="hero-image">
<em>Source: <a href="https://en.wikipedia.org/wiki/Fast_(Motion)">Fast (Motion)</a> album cover + my edits</em></p>

<h2 id="what-is-a-sport">What is a sport?</h2>

<p>For my purposes, a sport is a <strong>physical</strong> competition you can <strong>win or lose</strong>
based on some <strong>skill</strong>.</p>

<p>This means <em>non-physical</em> skilled competitions like chess are not sports under
this definition.</p>

<h3 id="leisure-activities-vs-sports">Leisure activities vs. sports</h3>

<p>Many outdoor leisure activities are not sports. Kayaking alone is not a sport
because you can’t win or lose. You would need to add competitive rules to make
it into a sport, like speed kayaking.</p>

<p>However, the rules must be designed carefully to reward skill more than chance.
Coin tossing would obviously not be a sport because it only depends on chance.
If fishing is made into a sport via a <a href="https://www.cwoutfitting.com/fishing-tournament-rules.htm">fishing tournament</a>, the
rules should control for the unpredictable fish by evaluating fishers in the
same area of a river for the same time period.</p>

<p>Under this definition, I claim that all sports can be categorized as Measure,
Precision, Spectacle, Combat, or Play.</p>

<h2 id="runner-measure">:runner: Measure</h2>

<p>“Measure” sports are ones where the goal is to min/max a certain value. The most
common measure sports are races. You win a measured sport by running the
fastest, lifting the most, throwing the farthest, etc.</p>

<p>The objective is to get the record, and there is no “best” performance or
ceiling other than the current world record.</p>

<p>Examples: <em>track &amp; field</em>, <em>car racing</em>, running, cycling, rowing, bobsledding,
speed-climbing, power lifting</p>

<h2 id="dart-precision">:dart: Precision</h2>

<p>Precision sports are like Measured sports, but with the goal of accuracy. The
goal is to hit a target, not a min/max with no bound. You win a precision sport
by being the most precise with your movement.</p>

<p>Examples: pistol, archery, darts, cornhole, golf</p>

<h2 id="dancer-spectacle">:dancer: Spectacle</h2>

<p>Spectacle sports are subjectively judged and aesthetics matter. They often have
panels of judges.</p>

<p>For example, <a href="https://www.usfigureskating.org/about/scoring-system">figure skating</a> is judged on “interpretation of
music” among other more technical criteria. Almost any physical activity could
be turned into a spectacle sport if a point system is designed around it to
determine who wins and who loses.</p>

<p>This is not to say that all non-Spectacle sports are perfectly objectively
scored. For example, many Play sports have referees who make subjective calls
for fouls. However, individual judges have fairly wide subjective leeway in
Spectacle sports. Would a “perfect” judge with all information always make the
same calls?</p>

<p>Because Spectacle sports require subjective judging, casual enjoyment of the
activity is not a sport. Skating in Central Park with your friends is not a
sport unless you count how many triple lutzes you each do.</p>

<p>Examples: <em>cheerleading</em>, figure skating, gymnastics, artistic (synchronized)
swimming, skateboarding, parkour, surfing</p>

<h2 id="muscle-combat">:muscle: Combat</h2>

<p>Combat sports encompass most martial arts or fighting sports, where you win when
the other person loses. They involve some direct physical strength contest or
grappling, so fencing is a combat sport but tennis is not. These are often 1-v-1
sports, except maybe tug-of-war.</p>

<p>What makes fencing a Combat sport and tennis a Play sport? There’s something
visceral about Combat sports, like they are one layer closer to actual fighting.
Please provide a counterexample if you disagree.</p>

<p>Examples: <em>boxing</em>, fencing, judo, taekwondo, karate, arm wrestling, tug of war</p>

<h2 id="soccer-play">:soccer: Play</h2>

<p>Play is the leftover, catch-all category which encompasses all sports that don’t
fit the other definitions. The top 9 <a href="https://www.worldatlas.com/articles/what-are-the-most-popular-sports-in-the-world.html">most popular sports</a> in the
world are all Play sports, and they are perhaps the most natural expression of
“play” even as kids. You can complete the sentence “I play ___” with any Play
sport.</p>

<p>Play sports are hard to define on their own and not in contrast to the other
four sports categories. Most Play sports have more complex rules than Measure,
Precision, Spectacle, or Combat sports. Play sports are often, but not always
team sports — both singles and doubles tennis are Play sports.</p>

<p>Examples: <em>basketball</em>, <em>football</em>, <em>soccer</em>, tennis, cricket, hockey,
volleyball, ping pong, baseball, rugby</p>

<h2 id="limitations">Limitations</h2>

<p>This blog post was a fun thought exercise for me, but I’m curious what others
think. Would you categorize anything differently? Would you propose a new
category? Do you have a better definition for any category?</p>]]></content><author><name>Claire Nord</name></author><category term="personal essay" /><category term="sports" /><category term="taxonomy" /><summary type="html"><![CDATA[Measure 📏 Precision 🎯 Spectacle 💃 Combat 🤼 Play 🛶]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/sports.png" /><media:content medium="image" url="https://clairenord.com/assets/img/sports.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Tech Deep-Dive: Object Detection Ensembles as Graph Cliques</title><link href="https://clairenord.com/tech-deep-dive-object-detection-ensembles-as-graph-cliques.html" rel="alternate" type="text/html" title="Tech Deep-Dive: Object Detection Ensembles as Graph Cliques" /><published>2019-03-14T00:00:00+00:00</published><updated>2019-03-14T00:00:00+00:00</updated><id>https://clairenord.com/tech-deep-dive--object-detection-ensembles-as-graph-cliques</id><content type="html" xml:base="https://clairenord.com/tech-deep-dive-object-detection-ensembles-as-graph-cliques.html"><![CDATA[<p>Object detection is one important type of computer vision task: given an image, localize and identify the relevant objects in it.</p>

<p class="caption"><img src="/assets/img/obj/cat-dog.jpeg" alt="A sleeping dog and cat enclosed by object-detection boxes labeled DOG 80% and CAT 95%" class="hero-image">
<em>Photo by <a href="https://unsplash.com/photos/6VQlKJp2vpo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Ancaro Project</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></em></p>

<p>Object detection algorithms can solve problems difficult for humans, like identifying vehicle models from satellite images, counting the number of people in a crowd, or rejecting unripe fruits from a conveyor belt of fresh produce. The last example is what’s known as a sporadic visual search problem, where a huge stream of repetitive data contains anomalies only infrequently and sporadically. Humans struggle with these types of problems due to <a href="https://medium.com/synapse-blog/tsa-missed-at-least-three-firearms-in-2018-why-theyll-keep-missing-threats-in-2019-5176ea0773bf">cognitive fatigue.</a> Object detection algorithms, however, <a href="https://medium.com/synapse-blog/ai-outperforms-certified-xray-operators-972932086f2e">excel at this</a>.</p>

<p>A perfect example of the sporadic visual search problem is X-ray baggage screening at museums. At the entrance to a museum, security operators may scan passengers’ bags with X-ray machines to detect threats like knives or handguns. Baggage screeners see thousands of bags a day, but still need to identify the single bag that contains a threat.</p>

<p class="caption"><img src="/assets/img/obj/museum.jpeg" alt="Visitors passing through an X-ray security checkpoint outside a museum">
<em>An X-ray scanner at the entrance to the Louvre museum.</em></p>

<p>At Synapse Technology, we use an object detection model to tackle this problem. A <em>model</em> in this context is an algorithm trained on a large number of labeled examples to solve a task. Let’s train an object detection model, then run it against each X-ray image the bag screener inspects.</p>

<p class="caption"><img src="/assets/img/obj/ez-knife.png" alt="An X-ray of a suitcase with a clearly visible knife detected at 99 percent confidence">
<em>A straightforward example: the knife stands out in this bag.</em></p>

<p>Detection can get much more difficult, though, depending on the bag contents and arrangement.</p>

<p class="caption"><img src="/assets/img/obj/hard-knife-blank.png" alt="A cluttered suitcase X-ray containing a partially obscured knife">
<em>This bag contains a knife. Can you spot it?</em></p>

<p>It’s hard to tell whether this bag contains a threat, even with a very good object detection model. That’s why we use multiple models in <em>ensemble</em> to do our detections. If two models both detect the same object, there’s a much higher chance that something is actually there. Using multiple models can also improve detection rate and lower false alarms by making the whole system more robust towards uncertain/difficult cases. Ensemble detection also allows us to compare different model architectures against each other to continuously improve our product. Finally, it allows us to scale the number of threat classes we support: each model supports some set of threats, and ensemble detection makes it easy to incorporate a new model’s set of threats into the ensemble.</p>

<p>Implementing ensemble detection presents additional challenges beyond single object detection. Here, I’ll describe how we approached these challenges.</p>

<hr>

<h4 id="how-can-a-multi-model-ensemble-be-better-than-a-single-model">How can a multi-model ensemble be better than a single model?</h4>

<p>Suppose we have two object detection models, Model A and Model B. Each of them has detected some objects in the image, and each detection has an associated <em>confidence score.</em></p>

<p class="caption"><img src="/assets/img/obj/hard-knife-label.png" alt="The cluttered suitcase X-ray with two overlapping knife detections at 40 and 50 percent confidence">
<em>The two different models have detected a threat. Note that is the same image as the previous one.</em></p>

<p>Model A (red) and Model B (yellow) have both detected a knife in the top right corner of the image, but neither model has a high enough confidence score alone for us to conclude that there is a threat in the image. If we consider the models as an <em>ensemble</em>, though, we observe that since two of their detections overlap, they probably refer to the same object. Given that they refer to the same object, there is a much greater probability that something is actually there. For example, if the confidence scores are the probability that each detection is a true positive (rather than a false positive), then the probability that the <em>ensemble</em> is a true detection is 0.7, which is greater than either detection alone (see calculations below).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">prob_threat_modelA</span> <span class="o">=</span> <span class="mf">0.4</span>
<span class="n">prob_threat_modelB</span> <span class="o">=</span> <span class="mf">0.5</span>

<span class="n">prob_nonthreat_ensemble</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">prob_threat_modelA</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">prob_threat_modelB</span><span class="p">)</span>
<span class="c1">#                         = (1 - 0.4) * (1 - 0.5)
#                         = 0.6 * 0.5
# prob_nonthreat_ensemble = 0.3
</span>
<span class="n">prob_threat_ensemble</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">prob_nonthreat_ensemble</span><span class="p">)</span>
<span class="c1">#                      = 1 - 0.3
# prob_threat_ensemble = 0.7
</span></code></pre></div></div>

<p>Ensemble detection can improve the outcome of this example, but also even more complex examples. Let’s formalize the problem and address further extensions.</p>

<p><strong>Input:</strong> For a single image, each detection model independently returns a set of <em>inferences</em>, which are composed of a <em>bounding box</em>, a <em>label</em>, and a <em>confidence score</em>. The detection model will only generate one inference for each object it thinks is in the image. In the image above, Model A has returned one inference and Model B has returned one inference. Model A’s inference has the label “knife” and a confidence score of 40%, meaning that it’s 40% sure that the inference is correct.</p>

<p><strong>Output:</strong> After ensemble detection, return a list of inferences, one per perceived threat in the image (no disagreements or redundancies). These inferences must have high confidence scores.</p>

<h4 id="how-can-you-implement-multi-model-ensemble-voting-for-object-detection">How can you implement multi-model ensemble voting for object detection?</h4>

<p>Let’s consider a hypothetical example with the same two models, but more inferences:</p>

<p class="caption"><img src="/assets/img/obj/ex-1.jpeg" alt="Four overlapping knife detections from two object-detection models">
<em>Model A and Model B have detected more objects in this hypothetical image. Some of these perceived objects overlap.</em></p>

<p>In this case, we can’t just group overlapping inferences as one “ground truth object” like in the first example because two of Model B’s inferences overlap. Model B thinks that there are <em>two</em> different overlapping objects in the lower half of the image, but Model A thinks that there is one. How do we group them? What if there were even more inferences than this? Let’s model this as a graph problem.</p>

<h4 id="graph-model-for-inference-cliques">Graph model for inference cliques</h4>

<p>Create a vertex for each inference. Let the vertex’s <em>color</em> indicate the model it came from (e.g. red for Model A, yellow for Model B). Draw an edge between two inferences if their bounding boxes overlap. Weight this edge by how much they overlap, in particular, their <a href="https://en.wikipedia.org/wiki/Jaccard_index"><em>intersection over union</em></a> (IoU) score.</p>

<p class="caption"><img src="/assets/img/obj/ex-2.jpeg" alt="A graph connecting nearby centers of the four overlapping detections">
<em>A graph representation of our inferences, where weighted edges join nodes whose bounding boxes intersect.</em></p>

<p>Now we have a weighted undirected graph. Our goal is to partition each disjoint subgraph into <em>cliques</em> such that no two vertices in a clique share the same color. We also want the cliques to have the maximum sum of edge weights (be as overlapping as possible). We take a greedy approach to this. First, we create a clique for each vertex. Then, we sort the edges by edge weight (IoU score). In descending order of edge weight, merge the two cliques the edges would connect if their sets of vertex colors are disjoint. A pseudocode implementation of this is below.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Create a single-element clique for each vertex
</span><span class="n">vertex_to_clique_map</span> <span class="o">=</span> <span class="p">{</span><span class="n">v</span><span class="p">:</span> <span class="nf">clique</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">graph</span><span class="p">.</span><span class="n">vertices</span><span class="p">}</span>

<span class="n">edges</span> <span class="o">=</span> <span class="p">[]</span>

<span class="k">for</span> <span class="n">every</span> <span class="n">pair</span> <span class="n">of</span> <span class="n">colors</span> <span class="n">color1</span><span class="p">,</span> <span class="n">color2</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">every</span> <span class="n">pair</span> <span class="n">of</span> <span class="n">vertices</span> <span class="k">from</span> <span class="n">color1</span> <span class="ow">and</span> <span class="n">color2</span> <span class="n">v1</span><span class="p">,</span> <span class="n">v2</span><span class="p">:</span>
        <span class="c1"># if v1 and v2's bounding boxes overlap, they have an edge between them
</span>        <span class="k">if</span> <span class="n">v1</span><span class="p">.</span><span class="n">bounding_box</span><span class="p">.</span><span class="nf">overlap</span><span class="p">(</span><span class="n">v2</span><span class="p">.</span><span class="n">bounding_box</span><span class="p">):</span>
            <span class="n">edges</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">edge</span><span class="p">(</span><span class="n">v1</span><span class="p">,</span> <span class="n">v2</span><span class="p">))</span>

<span class="c1"># sort edges by IoU score, descending
</span><span class="n">edges</span> <span class="o">=</span> <span class="nf">sorted</span><span class="p">(</span><span class="n">edges</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">edge</span><span class="p">:</span> <span class="o">-</span><span class="n">edge</span><span class="p">.</span><span class="n">iou_score</span><span class="p">)</span>

<span class="k">for</span> <span class="n">edge</span> <span class="ow">in</span> <span class="n">edges</span><span class="p">:</span>
    <span class="n">clique1</span> <span class="o">=</span> <span class="n">vertex_to_clique_map</span><span class="p">[</span><span class="n">edge</span><span class="p">.</span><span class="n">v1</span><span class="p">]</span>
    <span class="n">clique2</span> <span class="o">=</span> <span class="n">vertex_to_clique_map</span><span class="p">[</span><span class="n">edge</span><span class="p">.</span><span class="n">v2</span><span class="p">]</span>
    <span class="c1"># if no 2 vertices in the cliques share a color, they can merge
</span>    <span class="k">if</span> <span class="n">clique1</span><span class="p">.</span><span class="n">colors</span><span class="p">.</span><span class="nf">isdisjoint</span><span class="p">(</span><span class="n">clique2</span><span class="p">.</span><span class="n">colors</span><span class="p">):</span>
        <span class="n">clique1</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">clique2</span><span class="p">)</span>
        <span class="n">vertex_to_clique_map</span><span class="p">[</span><span class="n">v2</span><span class="p">]</span> <span class="o">=</span> <span class="n">clique1</span>

<span class="c1"># return the remaining cliques
</span><span class="k">return</span> <span class="nf">set</span><span class="p">(</span><span class="n">vertex_to_clique_map</span><span class="p">.</span><span class="nf">values</span><span class="p">())</span>
</code></pre></div></div>

<p class="caption"><img src="/assets/img/obj/ex-3.jpeg" alt="The detection graph grouped into three possible cliques">
<em>We’ve grouped the detections into three cliques. The clique on the left contains two inferences.</em></p>

<h4 id="voting-on-cliques">Voting on cliques</h4>

<p>Now we have cliques of inferences that each refer to one object in the image. Which of these cliques should we accept? The fact that only Model A detected an object near the top of the image (Clique 3) means that Model B did <em>not</em> think there was an object near the top of the image. Here is where we can introduce <em>voting</em> to increase the accuracy of our ensemble detection. A model votes for a clique if one of the inferences in the clique comes from that model, and only accept a clique if majority of voters vote for it. Let’s define a <em>voting threshold</em> as the minimum percentage of models that must vote for a clique for us to accept it.</p>

<p>Suppose we had a voting threshold of 0.5, so both models need to vote for a clique for us to accept it. Then we’d reject Clique 2 and Clique 3 because they each only have one vote, but we’d accept Clique 1 because it has two votes.</p>

<p class="caption"><img src="/assets/img/obj/ex-4.jpeg" alt="The detection graph with the selected maximum clique emphasized">
<em>Results of ensemble detection after voting: only accept Clique 1.</em></p>

<p>After voting, we only accept Clique 1. We finally compute the combined score of Clique 1 as a function of its inferences, and output a representative bounding box for this clique with that combined score as its confidence score.</p>

<h4 id="takeaways">Takeaways</h4>

<p>Through effective grouping and voting schemes, multiple object detection models in ensemble can perform better than one alone. In the context of X-ray baggage screening this means more accurate detections, lower false positive rates, and ultimately a safer world.</p>

<p>We hope other computer vision teams can apply this technique to make more accurate and robust detection systems.</p>

<p class="caption"><img src="/assets/img/obj/detections.gif" alt="Animation of an ensemble model combining detections around a knife in a suitcase X-ray">
<em>Some cool detections we’ve made at Synapse Technology, including handguns, gun parts, ammunition, razor blades, and fixed and folding knives.</em></p>

<p><em>I designed and implemented this system over the course of my one-month internship at <a href="https://www.forbes.com/profile/synapse-technology-corporation/">Synapse Technology</a> (<a href="https://www.businesswire.com/news/home/20200610005917/en/Rapiscan-Continues-Innovation-Leadership-with-Acquisition-of-Advanced-Artificial-Intelligence-AI-Technology">acquired</a>) this January. Thanks to my mentor Steven for thoughtful design documents and whiteboard sessions. Thanks to Sims for encouraging me to share my work, as well as the rest of the engineering team for a fun and productive internship.</em></p>

<p><em>Thanks to Noah Moroze, Simanta Gautam, Bruno B. Ferrari Faviero, Ian Cinnamon, and Sanjeevani Lakshmivarahan for feeback.</em></p>]]></content><author><name>Claire Nord</name></author><category term="software engineering" /><category term="machine learning" /><category term="computer vision" /><category term="algorithms" /><summary type="html"><![CDATA[My intern project for Synapse Technology (acquired) on composing multiple CV models for better output]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/obj/cat-dog.jpeg" /><media:content medium="image" url="https://clairenord.com/assets/img/obj/cat-dog.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to Un-Break Your Jaw</title><link href="https://clairenord.com/how-to-un-break-your-jaw.html" rel="alternate" type="text/html" title="How to Un-Break Your Jaw" /><published>2018-01-09T00:00:00+00:00</published><updated>2018-01-09T00:00:00+00:00</updated><id>https://clairenord.com/how-to-un-break-your-jaw</id><content type="html" xml:base="https://clairenord.com/how-to-un-break-your-jaw.html"><![CDATA[<p><strong>TL;DR: sleep is important</strong></p>

<p>I broke my jaw at the end of August by standing up and fainting—way to go,
right? I got up out of bed in the morning, felt dizzy, blacked out, and fell. I
woke up within a few seconds and noticed that my jaw felt out of place. The left
side felt stuck higher up, so the right side of my bite didn’t close. I also had
a scrape on my chin and my ear was bleeding.</p>

<p>My mom rushed me to the dentist, where they did a <a href="https://en.wikipedia.org/wiki/Panoramic_radiograph">panoramic dental
x-ray</a> to find that my jaw was fractured in two places.</p>

<p class="caption"><img src="/assets/img/jaw/2.jpeg" alt="A panoramic dental X-ray showing the jaw fracture before surgery">
<em>The original panoramic x-ray result. The white line across my lower middle
teeth is my <a href="https://en.wikipedia.org/wiki/Retainer_(orthodontics)#Fixed_retainers">permanent retainer</a>!</em></p>

<p class="caption"><img src="/assets/img/jaw/3.jpeg" alt="The panoramic dental X-ray with the two fracture sites circled in red">
<em>The two fractures: the body fracture on my right and the condylar fracture high
up on my left.</em></p>

<p>The doctors identified two fractures: one along the body of my jaw and one high
up on the left condyle [<a href="https://commons.wikimedia.org/wiki/File:Mandbular_fractures.png">diagram</a>] and recommended I go to
the <a href="https://www.beaumont.org/locations/beaumont-hospital-royal-oak">hospital</a> for surgery. This blog post recounts my experiences
from injury to full recovery, and is basically everything I wish I’d known
before it happened.</p>

<ul class="table-of-contents">
  <li><a href="#the-hospital">The Hospital</a></li>
  <li><a href="#the-surgery">The Surgery</a></li>
  <li><a href="#immediately-post-op">Immediately Post-Op</a></li>
  <li><a href="#1-day-post-op">1 Day Post-Op</a></li>
  <li><a href="#23-days-post-op">2–3 Days Post-Op</a></li>
  <li><a href="#4-days-post-op">4 Days Post-Op</a></li>
  <li><a href="#week-1">Week 1</a></li>
  <li><a href="#week-2">Week 2</a></li>
  <li><a href="#weeks-3--4">Weeks 3 + 4</a></li>
  <li><a href="#weeks-5--6">Weeks 5 + 6</a></li>
  <li><a href="#arch-bar-removal">Arch Bar Removal</a></li>
  <li><a href="#weeks-7">Weeks 7+</a></li>
  <li><a href="#why-did-i-fall">Why did I fall?</a></li>
</ul>

<h3 id="the-hospital">The Hospital</h3>

<p>After I was admitted to the hospital I waited and napped for a long time. It was
overall fine and relaxing, but my IV for fluids was annoying to move around
with. Throughout the day I met with various doctors and nurses: oral &amp;
maxillofacial surgeons for the surgery, and internal medicine doctors to
investigate why I fell.</p>

<p>I could talk alright before the surgery, but couldn’t open my mouth very much,
chew, or brush my teeth. I never felt much pain while waiting — I didn’t really
feel the fracture along the body at all, and the condylar fracture only hurt
when I tried to open my jaw wide. The doctors and nurses were very concerned
about my ear bleeding, but that turned out to just be a surface cut. I had a <a href="https://en.wikipedia.org/wiki/CT_scan#Axial_skeleton_and_extremities">CT
scan</a> at the hospital to better understand the injury and decide which
surgery to do.</p>

<h3 id="the-surgery">The Surgery</h3>

<p>For the fracture on the jaw body, the doctors decided to do an “<a href="https://en.wikipedia.org/wiki/Internal_fixation">internal
fixation</a>”, where they put a small titanium plate across the
break to fixate it. The plate looks like this:</p>

<p><img src="/assets/img/jaw/plate.png" alt="A hand-drawn fixation plate with an arrow labeling its screw holes"></p>

<p>The plate goes from a bit before the corner of my jaw on the right to a little
past the curve at the front of my chin. The plate is 10cm long (about the length
of my index finger), 1.7mm thick, and held on by 7 titanium screws. The plate
had to be so long because the fracture was sort of parallel along the bone. It’s
made to stay in for life.</p>

<p>With the condylar fracture, the head of the condyle actually broke off of the
bone and is “floating” in that area. Because it’s so high up in the joint, the
surgeons decided not to open up the joint area and fixate it with a plate — it’s
an invasive surgery and could have caused problems with articulating my jaw.
Instead, they decided to temporarily lock my jaw shut with <a href="https://surgeryreference.aofoundation.org/cmf/basic-technique/maxillomandibular-fixation-mmf">arch
bars</a> and elastic bands.</p>

<p class="caption"><img src="https://media.aofoundation.org/-/jssmedia/surgery/91/91_x010_i180.png?w=665" alt="Arch bars wired around the upper and lower teeth of a model jaw">
<em>Arch bars (source: <a href="https://media.aofoundation.org/-/jssmedia/surgery/91/91_x010_i180.png?w=665">AO Foundation</a>)</em></p>

<p>Arch bars are like a row of braces above your teeth with little metal hooks like
a coat rack for hooking the elastics. The bars are held on by wire twist-ties
“lassoed” around each tooth. They decided to use elastic bands instead of wires
because my jaw could have locked if the condyle healed without any movement. The
elastics were moved and adjusted as I healed, allowing for more and more guided
movement.</p>

<p>To put in the plate, the surgeons made an incision inside my lip (like if you
pulled down your lower lip all the way, and cut at the line between lip and
gum). They stitched it up with dissolvable sutures.</p>

<p>For the surgery itself, I changed into a hospital gown and socks, peed
extensively so I wouldn’t need to during surgery, and talked with the main <a href="https://doctors.beaumont.org/provider/Aaron+Pokorny/227215">oral
surgeon</a> and anesthesiologist. They gave me the KO medication through
my IV and I was completely out for the 3–3.5hr surgery.</p>

<p class="caption"><img src="/assets/img/jaw/1.jpeg" alt="A postoperative panoramic X-ray showing arch bars and a plate fixed to the lower jaw" class="hero-image">
<em>The plate and arch bars after the surgery.</em></p>

<h3 id="immediately-post-op">Immediately Post-Op</h3>

<p>I was SUPER PUFFY and could not feel most of my lower lip and jaw area, but
still wasn’t really in any pain. I couldn’t move my jaw or open my mouth to
speak. My throat was a bit scratchy because they had to put a tube up my nose
and down my throat for anesthesia. I drank chicken broth and rested for the rest
of that day and night.</p>

<h3 id="1-day-post-op">1 Day Post-Op</h3>

<p>I was still just as puffy the day after the surgery. I kept drinking water and
broth and getting IV fluids. I also showered, which was a bit scary because I
was afraid of falling again (except naked this time), but it went OK. I used
Vaseline to keep my lips from getting chapped for the next month, because I
couldn’t open my mouth to lick my lips.</p>

<p>I was given 5 medications:</p>

<ul>
  <li>Antibiotic (amoxicillin) every 8hrs for a week. Sweet pink liquid.</li>
  <li>Prescription painkiller (hydrocodone) as needed. I only took this once, and
just used ibuprofen from then on, because I wanted to avoid opioids.</li>
  <li>Non-prescription painkiller (children’s ibuprofen) as needed. I took this
every 8hrs with my antibiotic. White/clearish and sweet, but stung my throat a
bit.</li>
  <li>Strong mouthwash (chlorohexidine) rinse after every meal because I couldn’t
open my mouth to brush my teeth. Medical-grade alcohol-free mouthwash.</li>
  <li>Anti-nausea and anti-constipation meds to combat possible side effects from
the hydrocodone. I never used these.</li>
</ul>

<p>At the end of the day I finally had my IV taken out and went home!</p>

<h3 id="23-days-post-op">2–3 Days Post-Op</h3>

<p>For the next few days I just took medicine and rested. I used a bed wedge at
home to rest with my head elevated and ordered one for school too. I could only
drink <em>extremely smooth</em> things because I could only sip through the space
between my upper and lower teeth. Example liquids:</p>

<ul>
  <li>Juice</li>
  <li>Cream of {Chicken, Mushroom, etc.} soups (strained)</li>
  <li>Broths</li>
  <li><a href="https://ensure.com/">Ensure</a>/<a href="https://www.premierprotein.com/">Premier Protein</a></li>
  <li>Smoothies (strained only, but this was still very difficult and didn’t really
work)</li>
</ul>

<p>I flew to Boston for my fall semester of school 3 days after my surgery. The
flight was uneventful — the TSA inspected my ice packs, but I was convincing
enough that I needed them (I was puffy as heck, couldn’t speak, and had a
plastic bag full of medicine!).</p>

<h3 id="4-days-post-op">4 Days Post-Op</h3>

<p>I was fortunate to be able to stay with my boyfriend’s family in the Boston area
for a day before moving back into my dorm. At first I used a text-to-speech app
to talk, but then I started to become able to speak in a garbled voice without
moving my jaw or lips much.</p>

<p>I had a lot of angst over meeting my boyfriend and his family because I was
really embarrassed with how I looked. He, his family, and all my friends were
really kind and supportive, though, and it meant a lot to me.</p>

<p>I was still very puffy, but my chin scab started to heal. The skin on my face
peeled a little (making room for puffier skin), and so did the skin on my lips.
My mouth felt fuzzy and gross from not brushing… pretty much from then until I
could brush my teeth again.</p>

<h3 id="week-1">Week 1</h3>

<p>It was very difficult to talk. My mouth had a bit of a lingering taste of blood,
and my spit was always red/pink. Keeping up with antibiotics and pain meds every
8 hours was annoying, but I’m glad I was never really in pain.</p>

<p>Once I was in Boston, I made weekly appointments with Dr. Somi Kim at
<a href="https://www.massgeneral.org/">Massachusetts General Hospital</a> (MGH). Dr. Kim was
really nice! She adjusted the locations of my elastics so that my bite was back
to where it naturally rests, which was a huge relief. It had been a bit out of
place since the surgery, with my bottom front teeth pushing on my top front
teeth. She also told me not to do any contact sports for 3 months, and that I
was <strong>absolutely not allowed to chew for the next 6 weeks</strong>.</p>

<p>The most surprising thing she said was that it’s possible to remove the plate! I
had expected to have to keep it in forever, but it can be removed after 6
months. It’s still worth a discussion whether the removal is worth it.</p>

<p>As far as swelling, Dr. Kim said that 50% of the swelling goes down in the first
6 weeks, and the next 50% goes away in the next year. For me most of the
swelling went away after 2.5 weeks, though.</p>

<p class="caption"><img src="/assets/img/jaw/hackmit.jpeg" alt="Claire with two fellow HackMIT organizers wearing matching event shirts">
<em>Me (left) struggling to smile normally at <a href="https://hackmit.org">HackMIT</a> 2017 (2.5 weeks
post-op). You can see my arch bars peeking out, and my right side is still a
little puffy.</em></p>

<p>My lower right lip and cheek near the plate were numb for a long time after
surgery because the nerve that passes through that area was
<a href="https://medical-dictionary.thefreedictionary.com/skeletonize">skeletonized</a>. I was still pretty numb even after the swelling
went away. At 3 months post-op I had about 90% of the feeling back, and at 4
months now I have 95%+ back, but I was worried at the beginning because the
numbness receded so slowly.</p>

<h3 id="week-2">Week 2</h3>

<p>During week 2 talking got easier, but I still couldn’t move my jaw at all. The
bruising/swelling started turning yellow. One of my elastics broke at this time,
and I was TERRIFIED of putting it back in. I was afraid of tearing my stitches,
and didn’t know exactly which hooks the elastic was supposed to go on. It took
me like 30 minutes and I almost cried from stress. I got <em>way</em> better at putting
these back on by the end.</p>

<p>At this week’s doctor’s appointment I was put in lighter “guided elastics” that
let me open my mouth. I had an elastic band on each side of my mouth in the
following positions:</p>

<p class="caption"><img src="/assets/img/jaw/elastics.png" alt="A hand-drawn diagram numbering the five elastic positions around the teeth">
<em>I remembered the positions as 5–2–3–4, like room 5-234 for HackMIT meetings!</em></p>

<p>I had to take the elastics out 3 times a day and stretch open my jaws as far as
I could. This sounded scary based on my last elastic replacement experience, but
it was <em>way</em> easier when my stitches were healed (at around 3.5 weeks in!). I
didn’t really get around to doing the jaw exercises 3 times a day, but I did do
2 times (morning and night). I also stopped taking ibuprofen for pain around
this time.</p>

<p class="caption"><img src="/assets/img/jaw/elastics-irl.jpeg" alt="A close-up dental exam showing elastics attached to the arch bars">
<em>Guiding elastics in my mouth IRL.</em></p>

<p>Other miscellaneous notes:</p>

<ul>
  <li>Using a <a href="https://www.waterpik.com/oral-health/products/dental-water-flosser/">waterpik</a> would have been fine as long as I didn’t blast my
stitches. I was ultimately too afraid of this to use it, though.</li>
  <li>Sleeping on my side was fine as long as it didn’t hurt (ordering a bed wedge
was a bit of overkill I guess, haha). I was very concerned about my bite
alignment slipping out of place when I relaxed, but Dr. Kim reassured me it
would go back to normal (and it did). The condyle on the left side is slowly
regrowing to match the right, so over time it’s becoming more and more even.</li>
  <li>I read online that chlorohexidine (the strong mouthwash) could stain your
teeth, but Dr. Kim reassured me that a regular cleaning at the dentist’s would
fix it. I didn’t end up having noticeable stains. I switched to a regular
alcohol-free mouthwash when I ran out of chlorohexidine.</li>
</ul>

<p>I only had trouble sleeping for one night in the whole recovery period, 13 days
post-op. For some reason my jaw just hurt a lot being in the aligned position,
but I was afraid to let it rest in the askew position while I slept in case it
stayed there. I just took some ibuprofen and stressed myself out until I fell
asleep at 4am. My worry ended up being unfounded — my bite got more and more
even, and I had a comfortable and fairly even bite by week 4.</p>

<h3 id="weeks-3--4">Weeks 3 + 4</h3>

<p>By week 3 I looked pretty much normal and got a lot better at speaking without
moving my jaw. I spoke on the mic in the arena at HackMIT without sounding very
distorted, and I didn’t need to preface conversations with new people by
explaining that I broke my jaw. I just looked like a slightly less symmetric
version of myself with weird braces.</p>

<p>Having room to open my mouth also made eating so much easier! My eating pattern
was to take miscellaneous smoothie ingredients in a green box from the dining
hall up to my wing’s main lounge, blend them, and supplement the mix with Ensure
or Premier Protein. I got tired of Ensure’s distinct flavor (vanilla <a href="https://en.wikipedia.org/wiki/Tootsie_Roll#Alternative_flavors">tootsie
rolls</a>) and started drinking <a href="https://www.soylent.com/">Soylent</a>, which I liked a
lot.</p>

<p>Throughout all of this, eating took much, much longer than usual. Drinking food
instead of chewing it probably takes twice as long.</p>

<p>Another breakthrough I had during this time was to use <a href="https://www.wikihow.com/Apply-Dental-Wax-on-Braces">orthodontic
wax</a>! Before I used it I couldn’t really laugh or smile without
being lacerated with all the wires. The wax made my mouth bulkier, but was
definitely worth it.</p>

<p>Maybe my favorite milestone of week 4 was being able to take out my elastics and
brush all of my teeth with a manual toothbrush!! :heart: :heart: It felt SO
GOOD.</p>

<h3 id="weeks-5--6">Weeks 5 + 6</h3>

<p>I only had to wear my elastics at night for these last 2 weeks. I was told to do
jaw exercises more frequently (5x/day for 5min each), but I didn’t remember this
very well and just tried to practice whenever I remembered.</p>

<p>It was a bit tough to remember <strong>not to chew</strong> while my mouth was totally free,
but I stuck with it. I could mush things in my mouth like oatmeal, cream of
wheat, chili, soup, cottage cheese, soft pasta, and scrambled eggs. I got pretty
good at swallowing small soup ingredients like kidney beans or chickpeas without
chewing them. Instead of going up to the main lounge and blending things, I
would drink cups of soup and milkshakes in the dining hall.</p>

<p>I started using my electric toothbrush and flossing too, which felt amazing
:heart:</p>

<h3 id="arch-bar-removal">Arch Bar Removal</h3>

<p>I was so excited to have the arch bars gone!! There were 3 possible anesthesia
methods to remove my arch bars:</p>

<ul>
  <li>Swish <a href="https://en.wikipedia.org/wiki/Lidocaine">lidocaine</a> in my mouth: tastes
bad, but it’s easy. Maybe not as numbing as the other options.</li>
  <li>General anesthetic: going totally under. It’s kind of a big deal and takes a
lot of time for how short the operation is.</li>
  <li>Injections: I’d need 7–8 shots to numb enough of the lip area, so it’s almost
as much work and pain as the removal itself.</li>
</ul>

<p>I decided to go with the lidocaine. I was worried because Dr. Kim said it was
“uncomfortable”, but it was really fine. It took about 10 minutes, and felt
like… getting wires pulled out from between your teeth, which is what it was.
She untwisted the wires, snipped them short, then pulled them around and out. I
grimaced for a few of the pulls, but it was fairly quick and I <em>really</em> wanted
it all gone.</p>

<p class="caption"><img src="/assets/img/jaw/bars-out.jpeg" alt="Claire lifting her lip to show her teeth after the arch bars were removed">
<em>Imprint of the removed arch bar!</em></p>

<h3 id="weeks-7">Weeks 7+</h3>

<p>My first meal after I could chew was a super lame sandwich. :stuck_out_tongue:
Chewing felt weird, but good. Putting my clear retainer back on my top teeth
hurt really bad at first, but it did fit and went back to normal.</p>

<p>Food gets stuck in the lower right side of my mouth sometimes, probably because
the stitches made the pocket a bit smaller. It’s a bit annoying but I think it’s
getting better, or I’m getting used to it. I had to work up to chewing really
crunchy things, but it only took maybe a week.</p>

<p>At my last dentist appointment everything checked out, and I feel 100% normal
now :blush:</p>

<h3 id="why-did-i-fall">Why did I fall?</h3>

<p>Tests in the hospital ruled out any serious brain or heart issues. I still feel
faint sometimes when standing up quickly, but I’m careful now to wait a bit
before getting up, or I just sit down on the floor carefully as I start to feel
it. I feel more dizzy when I’m sleep deprived, so I think I fainted because I
went to bed too late the night before I fell. <strong>Let this be a warning that
getting good sleep is extremely important, and lack of sleep can have drastic
effects on your health and body!</strong> I’m still learning this, and feeling the
plate under my skin is a firm reminder to keep improving and building good
habits.</p>

<p>I’m very thankful to Dr. McLean at the dentist’s office for taking my x-ray on
such short notice, Dr. Pokorney and the Beaumont OMFS staff for doing my surgery
so well, Dr. Kim at MGH for being so patient with all my followup questions, my
boyfriend and his family for hosting me in Boston while I recovered, my friends
for all being so accommodating and understanding, and of course my family for
helping me through everything and keeping me company in the hospital.</p>

<p>When I first got hurt, I worried a lot about what was going to happen and if I
was ever going to be okay again. Reading about other people’s experiences helped
me get a feel for what I should expect, so I hope that someone can benefit from
reading about my experience.</p>

<p><em>Thanks to Noah Moroze and Kimberli Zhong for feedback.</em></p>]]></content><author><name>Claire Nord</name></author><category term="personal essay" /><category term="health" /><category term="injury" /><category term="broken jaw" /><category term="bones" /><category term="recovery" /><summary type="html"><![CDATA[The story of how I broke my jaw, got surgery, and healed (8-week process)]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/jaw/1.jpeg" /><media:content medium="image" url="https://clairenord.com/assets/img/jaw/1.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Get to know HKN: Tutoring</title><link href="https://clairenord.com/get-to-know-hkn-tutoring.html" rel="alternate" type="text/html" title="Get to know HKN: Tutoring" /><published>2018-01-02T00:00:00+00:00</published><updated>2018-01-02T00:00:00+00:00</updated><id>https://clairenord.com/get-to-know-hkn--tutoring</id><content type="html" xml:base="https://clairenord.com/get-to-know-hkn-tutoring.html"><![CDATA[<p><a href="https://hkn.mit.edu/">HKN</a>, the Course 6 honor society at MIT, is often thought of as the club that hosts free food study breaks, the <a href="https://underground-guide.mit.edu">underground guide</a> to Course 6 classes, and the occasional resume review event. However, there’s much more than free boba and Chipotle— one of the most important services HKN offers is the opportunity to tutor and be tutored.</p>

<p class="caption"><img src="/assets/img/tutor.jpeg" alt="Two students working together at laptops during an HKN tutoring session" class="hero-image">
<em>HKN eligible Stef Ren ’19 reviews a student’s resume.</em></p>

<h4 id="what-is-hkn-tutoring">What is HKN Tutoring?</h4>

<p>Tutoring is the second-largest branch of HKN after the Underground Guide, with 83 student tutors in fall 2017. Tutors for HKN submit the classes and number of hours they’re willing to tutor, and they are manually matched with tutees by the HKN tutoring officers. The tutor and tutee typically meet for an hour once a week, discussing anything from pset questions to exam practice problems to topics from lecture in more depth.</p>

<p>The most popular classes for tutoring are large classes like 6.006, 6.046, 6.036, and 6.004. 205 students were matched with tutors this semester, which means over 20% of students in the department are involved in HKN tutoring (<a href="https://registrar.mit.edu/stats-reports/majors-count/2017-2018" title="MIT Registrar’s Office Enrollment Statistics, Fall 2017–2018">source</a>). HKN member Justin Xiao ’17 discusses tutoring statistics in more detail <a href="https://medium.com/mit-hkn/mit-hkn-tutoring-service-a303d6d6bfa5">here</a>.</p>

<h4 id="why-request-a-tutor">Why request a tutor?</h4>

<p>Office hours for these large Course 6 classes tend to be very crowded, which means TAs have to focus on immediate needs like psets or exams without reviewing concepts. Unlike office hours, lectures, or group psetting, tutoring time is solely focused on <em>you</em> and your understanding of the subject. You’re paired with the same tutor all semester, so you can build a personal relationship with them beyond transactional Q&amp;A. Your tutor knows you, and can encourage you and adapt to your learning style.</p>

<p>Tutoring can be especially helpful for classes like 6.036, which switched to a <a href="https://en.wikipedia.org/wiki/Flipped_classroom">flipped classroom</a> style for the first time this semester. HKN eligible Sooraj Boominathan ’19 notes that “[tutoring] is the first time students might be getting the subject explained to them by a real person, and not just a video.”</p>

<h4 id="why-tutor-for-hkn">Why tutor for HKN?</h4>

<blockquote>
  <p>“It was p fun” ~Anthony Liu ’19</p>
</blockquote>

<p>Aside from being paid $15/hour by the EECS department or earning HKN volunteer hours, tutoring is a great chance to sharpen your understanding of past class material, meet new people, learn about other MIT students’ study habits, and give back to the Course 6 community. HKN eligible Anthony Liu ’19 says that tutoring 6.046 helped him keep up with algorithms, which was good for interviewing. “My tutee would usually have some topics in mind and I would explain them, like a super personal recitation”, Liu says. “Before tests my tutee often wanted to do an extra hour, and we’d do more problems. I’d give test taking advice too — it was pretty fun.”</p>

<p>If you’ve considered TAing a class, tutoring is a great lower-commitment way to try it out without being responsible for course management (grading, writing problems, discipline/fairness, etc.). In fact, Sooraj ’19 decided to TA 6.036 because of how much he enjoyed tutoring. “I think I’ve enjoyed teaching/explaining things for a long time now, and I like helping friends out with psets on classes I’ve taken before”, he says. Previous tutoring experience can also strengthen your TA application.</p>

<h4 id="in-summary">In Summary</h4>

<p>Peer teaching is a key part of the MIT undergraduate experience! Take advantage of this opportunity and sign up to tutor or be tutored <a href="https://hkn-tutoring2.mit.edu/"><strong>here</strong></a>. Tutors are in high demand, so be sure to sign up early if you request tutoring for a popular class.</p>

<p>If you want announcements for HKN events, add yourself to hkn-interest@ <a href="https://groups.mit.edu/webmoira/list/hkn-interest">here</a>. If you have ideas for how to improve tutoring or anything else Course 6, contact HKN at <a href="mailto:hkn-officers@mit.edu">hkn-officers@mit.edu</a>, or the tutoring officers in particular at <a href="mailto:hkn-tutoring@mit.edu">hkn-tutoring@mit.edu</a>.</p>

<p><em>Thanks to Nalini Singh and Noah Moroze for feedback.</em></p>]]></content><author><name>Claire Nord</name></author><category term="MIT" /><category term="education" /><category term="tutoring" /><summary type="html"><![CDATA[About MIT HKN's tutoring program]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/tutor.jpeg" /><media:content medium="image" url="https://clairenord.com/assets/img/tutor.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Making Your First Samsung Gear S3 App</title><link href="https://clairenord.com/making-your-first-samsung-gear-s3-app.html" rel="alternate" type="text/html" title="Making Your First Samsung Gear S3 App" /><published>2017-10-31T00:00:00+00:00</published><updated>2017-10-31T00:00:00+00:00</updated><id>https://clairenord.com/making-your-first-samsung-gear-s3-app</id><content type="html" xml:base="https://clairenord.com/making-your-first-samsung-gear-s3-app.html"><![CDATA[<p>When I started my research project in the MIT <a href="https://d-lab.mit.edu/research-about">D-Lab</a><a href="http://www.mobiletechnologylab.org/">Mobile Technology Lab</a> I was excited to build my first smartwatch app using the <a href="https://www.samsung.com/us/explore/gear-s3/">Samsung Gear S3</a>. Despite being super excited about the project, setting up the development environment took me an entire month :anguished: Here’s how to get to the Hello World stage 10x faster than I did.</p>

<p class="caption"><img src="/assets/img/watch/goal.jpeg" alt="A Samsung Gear S3 displaying “Hello D-Lab!” and the time" class="hero-image">
<em>The goal for this tutorial.</em></p>

<p>The Samsung Gear S3 uses an open-source operating system called <a href="https://www.tizen.org/?langswitch=en">Tizen</a> that’s based on the Linux kernel. You can program apps for the watch in C (“Native”) or with HTML/CSS/JS (“Web”). My project needs fast real-time signal processing, so we chose Native.</p>

<p>The source tutorial for this post is “<a href="https://developer.samsung.com/galaxy-watch-tizen/creating-your-first-app/native.html#run"><strong>Creating Your First Tizen Wearable Native Watch Application</strong></a>” by <a href="https://developer.tizen.org/?langswitch=en">Tizen Developers</a>. The info below should fill in all the gaps in the tutorial.</p>

<p><em>This project is up-to-date as of 20 March 2018. It uses Tizen Studio 2.2 and Wearable 3.0.</em></p>

<h3 id="installing-tizen-studio">Installing Tizen Studio</h3>

<ol>
  <li>Make sure you have enough space to download and install the <a href="https://developer.tizen.org/development/tizen-studio/download">Tizen Studio</a> <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDE</a>. It’s ~5GB!</li>
  <li>In the Tizen Studio package manager, install everything for the <strong>3.0 wearable</strong>. This was my first big stumbling block. Also be sure to install the <strong>Samsung Certificate Extension</strong> and the <strong>Samsung Wearable Extension</strong> under “Extras”.</li>
</ol>

<p>Once you’ve installed Tizen Studio, you can go through the “<a href="https://developer.samsung.com/galaxy-watch-tizen/creating-your-first-app/native.html#create">Creating a Project</a>”, “<a href="https://developer.samsung.com/galaxy-watch-tizen/creating-your-first-app/native.html#configuration">Managing the Application Configuration</a>”, and “<a href="https://developer.samsung.com/galaxy-watch-tizen/creating-your-first-app/native.html#build">Building Your Application</a>” sections of the tutorial.</p>

<h3 id="running-the-emulator">Running the Emulator</h3>

<p>The emulator is basically a little watch that runs on your computer. It takes a long time to start, but you can tell it’s successfully starting up by the boot messages at the top of the emulator watch face.</p>

<p class="caption"><img src="/assets/img/watch/emu.png" alt="A Tizen wearable emulator running inside Tizen Studio">
<em>This is a good sign.</em></p>

<p>Start the emulator from the Tizen Emulator Manager window. You know it’s ready when it shows up in the Tizen Studio Connection Explorer window. If you have issues running the emulator on OSX, you may need to install <a href="https://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager-intel-haxm">Intel HAXM</a> and follow <a href="https://stackoverflow.com/questions/26455759/installing-haxm-on-osx-yosemite">these instructions</a> to install it. TL;DR: run <code class="language-plaintext highlighter-rouge">sudo kextload -bundle-id com.intel.kext.intelhaxmand</code> then restart Tizen Studio. After this, you need to set up security certificates.</p>

<h3 id="security-certificate-profiles">Security Certificate Profiles</h3>

<p>This is the mistake that took me the most time to figure out, and it is completely glossed over in the tutorial. Essentially, you need to go to Tools&gt;Certificate Manager and click “<strong>+</strong>” to make new certificate. You have to make a Tizen certificate for the emulator to work, and a <strong>Samsung</strong> certificate for the app to run on the actual watch. For the Tizen certificate, choose “Partner” privilege level.</p>

<p class="caption"><img src="/assets/img/watch/cert.png" alt="Tizen Certificate Manager with the Samsung certificate profile selected">
<em>You need the one on the right too!!</em></p>

<p>It should be fairly straightforward to follow the steps to set up both certificates from there. You can follow the rest of the Tizen Developers tutorial to run the app on the watch emulator with the Tizen certificate profile.</p>

<p class="caption"><img src="/assets/img/watch/huzzah.png" alt="The completed Hello D-Lab watch face running in the emulator">
<em>Huzzah!</em></p>

<h3 id="running-on-the-real-watch">Running on the real watch</h3>

<p>Follow along with the tutorial section “<a href="https://developer.samsung.com/galaxy-watch-tizen/creating-your-first-app/native.html#target">Running on a Target Device</a>”. In particular, be sure to turn on the device WiFi and Bluetooth, connect your computer to the same WiFi network, and make sure both your laptop and device have the correct date and time. Also be sure to check your device IP address each time you connect to WiFi, because it may change.</p>

<p>When I ran <code class="language-plaintext highlighter-rouge">./sdb connect &lt;watch IP&gt;</code> I never actually got a notification on my watch saying “Allow Gear to read log data, copy files,…”. I just got a notification that said “RSA key fingerprint:…” and clicked the check mark. I had to reset my watch once to get a notification to pop up at all.</p>

<p>Like it says in the tutorial, you know the watch is correctly connected when you see it in the Tizen Device Manager window. When I connect via the command line interface it says <code class="language-plaintext highlighter-rouge">device unauthorized. Please approve on your device.</code> each time, but it still works :sweat_smile:</p>

<p><strong>Another thing they don’t mention in the tutorial</strong>: you may need to copy the <code class="language-plaintext highlighter-rouge">device-profile.xml</code> file to the watch. This file is located at <code class="language-plaintext highlighter-rouge">/home/cnord/SamsungCertificate/my\_certificate\_name/device-profile.xml</code> for me and needs to be copied to <code class="language-plaintext highlighter-rouge">home/developer/</code> on the watch. Copy it over to the watch by right clicking on the device name in Tizen Device Manager and selecting “Permit to install applications”.</p>

<p>Now, you should be able to select the Samsung security profile, then continue the tutorial with Run As&gt;Tizen Native Application, select the watch face, and see your beautiful new app!</p>

<p><img src="/assets/img/watch/done.jpeg" alt="A Samsung Gear S3 displaying a thank-you message in front of the app’s source code"></p>

<p>Feel free to email me (cnord@school) if you have any questions, comments, or tips about Samsung Gear development.</p>]]></content><author><name>Claire Nord</name></author><category term="software engineering" /><category term="tizen" /><category term="samsung" /><category term="samsung gear s3" /><category term="smartwatch" /><category term="wearables" /><category term="tutorial" /><summary type="html"><![CDATA[Learn from my mistakes and set up Tizen Studio to make your first Gear app]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://clairenord.com/assets/img/watch/goal.jpeg" /><media:content medium="image" url="https://clairenord.com/assets/img/watch/goal.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>