<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on Saeed Esmaili</title>
    <link>https://saeedesmaili.com/posts/</link>
    <description>Recent content in Posts on Saeed Esmaili</description>
    <image>
      <title>Saeed Esmaili</title>
      <url>https://saeedesmaili.com/saeed-esmaili.png</url>
      <link>https://saeedesmaili.com/saeed-esmaili.png</link>
    </image>
    <generator>Hugo -- 0.146.0</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 18 Feb 2026 12:00:00 +0100</lastBuildDate>
    <atom:link href="https://saeedesmaili.com/posts/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Taming Claude Code: Taking Back Control</title>
      <link>https://saeedesmaili.com/posts/taming-claude-code-taking-back-control/</link>
      <pubDate>Wed, 18 Feb 2026 12:00:00 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/taming-claude-code-taking-back-control/</guid>
      <description>&lt;p&gt;I started using &lt;a href=&#34;https://code.claude.com/docs/en/overview&#34; target=&#34;_blank&#34; &gt;Claude Code&lt;/a&gt;
 in July last year. I was a Cursor user looking for something better, and I was skeptical at first. Claude Code is a terminal-based tool, and I was used to IDEs. With Cursor, reviewing AI-generated changes was straightforward, you see the diff, you accept or reject. How would that work in a terminal?&lt;/p&gt;
&lt;p&gt;It took me a while to adapt. The breakthrough was realizing I could run Claude Code inside VS Code&amp;rsquo;s integrated terminal and use the Git extension to review changes. This became central to my workflow: I don&amp;rsquo;t let AI tools do whatever they want. I stay in the loop. Even when I&amp;rsquo;m vibe-coding a throwaway script, I want to quickly review what the AI is doing.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I started using <a href="https://code.claude.com/docs/en/overview" target="_blank" >Claude Code</a>
 in July last year. I was a Cursor user looking for something better, and I was skeptical at first. Claude Code is a terminal-based tool, and I was used to IDEs. With Cursor, reviewing AI-generated changes was straightforward, you see the diff, you accept or reject. How would that work in a terminal?</p>
<p>It took me a while to adapt. The breakthrough was realizing I could run Claude Code inside VS Code&rsquo;s integrated terminal and use the Git extension to review changes. This became central to my workflow: I don&rsquo;t let AI tools do whatever they want. I stay in the loop. Even when I&rsquo;m vibe-coding a throwaway script, I want to quickly review what the AI is doing.</p>
<p>Once I figured out the workflow, I was pretty happy with Claude Code. It explored codebases more efficiently than Cursor, and I could see the model&rsquo;s thinking traces, the reasoning behind each decision. This was huge for me. I could see <em>how</em> the model interpreted my request, catch wrong assumptions early, and course-correct before wasting time on the wrong path.</p>
<p>Then Claude Code 2.0 happened.</p>
<h2 id="the-breaking-point">The Breaking Point</h2>
<p>Anthropic updated Claude Code to be more accessible to everyone, not just power users. The VS Code extension got prettier, the UI got cleaner, and the thinking traces got hidden.</p>
<p>This was the moment I realized I couldn&rsquo;t continue using Claude Code as-is. The thinking traces weren&rsquo;t just a nice-to-have for me, they were essential. Without them, I had no way of knowing if the model had misunderstood my request until it finished working. Sometimes you ask something ambiguous, and instead of asking for clarification, the model assumes. Those assumptions are obvious in the thinking traces. Without them, you&rsquo;re flying blind.</p>
<p><img alt="Claude Code thinking traces showing self-correction" loading="lazy" src="/images/2026/20260218-claude-code-thinking-traces.png"></p>
<p>In this example, you can see the model debugging a SQL error, catching its own mistakes (&ldquo;Actually wait&rdquo;, &ldquo;Wait, let me count&hellip;&rdquo;), and reconsidering assumptions in real-time. This is exactly what you lose when thinking traces are hidden.</p>
<p>So I did what any stubborn power user would do: I disabled auto-updates and pinned my version to the latest 1.x release.</p>
<h2 id="why-i-customize-claude-code">Why I Customize Claude Code</h2>
<p>Here&rsquo;s my problem with the defaults: Claude Code has access to too many tools. In theory, more tools means more capabilities. In practice, it means more complexity for the model to navigate, more tokens consumed on tool selection, and worse results.</p>
<p>Take <strong>plan mode</strong>. Claude Code has multiple tools for entering plan mode, exiting plan mode, asking questions in plan mode. All of this consumes tokens and adds cognitive load for the model. But plan mode is just&hellip; a markdown file where the model writes without editing your actual code. You can achieve the same thing by telling the model: &ldquo;Don&rsquo;t edit anything. Plan first, ask me questions, save your plan in a markdown file.&rdquo; You don&rsquo;t need dedicated tools for this.</p>
<p>Or <strong>sub-agents</strong>. The main model can spawn another agent to explore the codebase and summarize findings. Sounds useful, but I found it performs worse for my use cases. The sub-agent summarizes what it found, and the main model works with that summary instead of the actual code. It&rsquo;s like playing telephone, context gets lost. I&rsquo;d rather have the main model read the code itself, even if it uses more tokens and hits the context limit faster. At least it&rsquo;s working with the real thing.</p>
<h2 id="my-setup">My Setup</h2>
<p>I&rsquo;ve ended up with a Claude Code config that&rsquo;s leaner, faster, and more predictable. Here&rsquo;s what I changed.</p>
<h3 id="restoring-thinking-traces">Restoring Thinking Traces</h3>
<p>The API still returns thinking traces, Claude Code just hides them in the UI since version 2.0. I found <a href="https://github.com/aleks-apostle/claude-code-patches" target="_blank" >claude-code-patches</a>
, a community project that patches thinking traces back to being fully expanded by default.</p>
<p>First, install a compatible Claude Code version:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install -g @anthropic-ai/claude-code@2.0.62
</span></span></code></pre></div><p>Then apply the patch:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone git@github.com:aleks-apostle/claude-code-patches.git
</span></span><span style="display:flex;"><span>cd claude-code-patches
</span></span><span style="display:flex;"><span>node patch-thinking.js
</span></span></code></pre></div><p>Restart Claude Code, and thinking blocks now display inline.</p>
<p><strong>Why version 2.0.62?</strong> That&rsquo;s the latest version the patch maintainer has officially supported. Other contributors have submitted PRs for newer versions, and the patch is just one JS file, so it&rsquo;s trivial to adapt. But 2.0.62 has everything I need, and I&rsquo;d rather wait for the maintainer to officially update than risk a broken patch.</p>
<p>Recent versions have actually gotten worse for power users. <a href="https://symmetrybreak.ing/blog/claude-code-is-being-dumbed-down/" target="_blank" >Version 2.1.20 started hiding even more information</a>
, showing vague summaries like &ldquo;Read 3 files&rdquo; instead of the actual file paths. When I read about this, I was glad I&rsquo;d pinned my version. If a future Claude Code version introduces a feature I really want, I&rsquo;ll upgrade and use a community patch. Until then, I&rsquo;m staying put.</p>
<h3 id="settings">Settings</h3>
<p>In <code>~/.claude/settings.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;includeCoAuthoredBy&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;env&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;DISABLE_AUTOUPDATER&#34;</span>: <span style="color:#e6db74">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY&#34;</span>: <span style="color:#e6db74">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&#34;</span>: <span style="color:#e6db74">&#34;1&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS&#34;</span>: <span style="color:#e6db74">&#34;50000&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>What each setting does:</p>
<ul>
<li><code>includeCoAuthoredBy: false</code> - Disables the &ldquo;Co-authored-by: Claude&rdquo; line in git commits. I don&rsquo;t ask Claude to commit often, and when I do, I prefer clean commit messages.</li>
<li><code>DISABLE_AUTOUPDATER</code> - Prevents automatic updates. I update manually after reviewing changelogs.</li>
<li><code>CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY</code> - Disables the &ldquo;rate your session&rdquo; popups. They appear frequently and break the flow.</li>
<li><code>CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC</code> - Keeps things fast and minimal.</li>
<li><code>CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS: 50000</code> - The default is 25,000 tokens. When Claude reads files larger than that, it gets truncated and errors out. I keep documentation files in my project directories (Cloudflare Workers docs, API references, etc.) that exceed 25k tokens, and I want Claude to read them fully.</li>
</ul>
<h3 id="disabling-token-heavy-tools">Disabling Token-Heavy Tools</h3>
<p>I use a shell alias that runs Claude Code with max thinking tokens and disables tools I don&rsquo;t want:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># In ~/.zshrc</span>
</span></span><span style="display:flex;"><span>alias claude<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;MAX_THINKING_TOKENS=31999 claude --disallowedTools \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#34;Task,AgentOutputTool,ExitPlanMode,NotebookEdit,AskUserQuestion,SlashCommand,EnterPlanMode&#34;&#39;</span>
</span></span></code></pre></div><p>This disables:</p>
<ul>
<li><strong>Task / AgentOutputTool</strong> - Sub-agents that explore and summarize. I prefer the main model to read code directly.</li>
<li><strong>EnterPlanMode / ExitPlanMode</strong> - Plan mode tools. I just ask the model to plan in a markdown file instead.</li>
<li><strong>NotebookEdit</strong> - Jupyter notebook editing conflicts with VS Code&rsquo;s live rendering. It doesn&rsquo;t work well and wastes tokens. I don&rsquo;t use Claude for notebooks much anyway.</li>
<li><strong>AskUserQuestion</strong> - Part of plan mode. Claude can just ask questions in regular text, and I respond in text. No special tool needed.</li>
<li><strong>SlashCommand</strong> - I&rsquo;ve never used it.</li>
</ul>
<p>The last three are more opinionated. If you use Jupyter notebooks heavily or like slash commands, keep those tools enabled.</p>
<p><strong>Why use an alias instead of settings?</strong> When you specify <code>--disallowedTools</code>, these tools are completely removed from the model&rsquo;s context. The model doesn&rsquo;t even know they exist. This makes the context leaner and, I believe, improves the model&rsquo;s focus.</p>
<h3 id="disabling-auto-compaction">Disabling Auto-Compaction</h3>
<p>You can toggle this via <code>/config</code> in Claude Code, or directly in <code>~/.claude.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;autoCompactEnabled&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This one is important. By default, Claude Code reserves 22.5% of the model&rsquo;s context (45k tokens out of 200k) for auto-compaction. When you approach this threshold, it summarizes the conversation and starts a new context. The summary is lossy, 99% of the time the model in the new session doesn&rsquo;t have enough context about what you&rsquo;ve tried, what worked, what didn&rsquo;t.</p>
<p>After disabling auto-compaction, I rarely hit the context limit anyway. I keep my sessions short by default: start a session, work on a feature or bug, clear and start fresh. This leads to higher quality results.</p>
<p>For those times when I&rsquo;m debugging something nasty and the context grows long, I&rsquo;d rather continue the current session until it hits the actual limit. Here&rsquo;s something nice about Claude Code: when you&rsquo;re at 0% context remaining, it doesn&rsquo;t immediately error out. It starts removing tool call results from the oldest messages (that test run from 30 messages ago, for example). You can often continue for 10-20 more messages before hitting the real API limit.</p>
<p>When I do hit the limit, I use <code>/export</code> to save user messages, assistant messages, and tool call titles to a text file. Then I start a fresh session and reference the transcript. This preserves more context than auto-compaction&rsquo;s summary. I sometimes paste the export to another model (Gemini, for example) to get a second opinion on a tricky problem.</p>
<h3 id="why-i-dont-use-mcps">Why I Don&rsquo;t Use MCPs</h3>
<p><a href="https://modelcontextprotocol.io/docs/getting-started/intro" target="_blank" >MCP</a>
 servers let you connect Claude Code to external services: Google Drive, Jira, Notion, databases, etc. I don&rsquo;t use any of them, except the VS Code MCP that Claude Code adds automatically when running in VS Code&rsquo;s terminal (it provides useful things like IDE diagnostics).</p>
<p>My reasoning is the same as with the built-in tools: each MCP adds tools to the context. Some people add tens or even hundreds of tools via MCPs. The model then has to evaluate all of them for every request. More tools means more token overhead and, I believe, worse decision-making.</p>
<p><img alt="Claude Code context with multiple MCPs enabled" loading="lazy" src="/images/2026/20260218-claude-code-before-with-mcp.png"></p>
<p>For services I actually need, I use CLI tools instead:</p>
<ul>
<li><strong>GitHub</strong> - I mention in my <code>CLAUDE.md</code> that the <code>gh</code> CLI is available and authenticated. Claude knows how to use it.</li>
<li><strong>BigQuery</strong> - Same approach. I have <code>bq</code> CLI authenticated and add minimal instructions to <code>CLAUDE.md</code>.</li>
</ul>
<p>For rarely-used integrations like Google Docs or Jira, I ask myself: what percentage of my Claude Code messages actually need Google Drive access? Maybe 1 in 100? 1 in 1000? For those rare cases, I just:</p>
<ul>
<li>Export the Google Doc as markdown, add it to the project directory, reference it manually</li>
<li>Or ask Claude to write to a markdown file, then copy-paste into Google Docs</li>
</ul>
<p>This takes 30 seconds and doesn&rsquo;t pollute every session with tools I rarely use.</p>
<p><strong>The exception: Skills.</strong> Skills are similar to MCPs but more minimal. The model only sees a one-sentence description of each skill until it&rsquo;s actually needed. When triggered, it dynamically loads the full instructions.</p>
<p>For example, I have an <a href="https://obsidian.md/" target="_blank" >Obsidian</a>
 skill. The description says &ldquo;use when the user mentions Obsidian or daily notes.&rdquo; The full instructions (vault location, how to find daily notes, formatting preferences) only load when relevant. When I&rsquo;m working on code and not mentioning Obsidian, my context stays clean.</p>
<p>For use cases like this, Skills are more efficient than MCPs. I&rsquo;ll continue using them selectively.</p>
<h2 id="the-result">The Result</h2>
<p>My Claude Code setup is leaner: fewer tools in context, no auto-compaction overhead, full visibility into thinking traces. I spend less time wondering what the model is doing and more time actually working. When something goes wrong, I can see exactly where the model&rsquo;s assumptions diverged from mine.</p>
<p>Here&rsquo;s the difference. Running <code>/context</code> in a fresh session:</p>
<p><strong>Before (default settings):</strong>
<img alt="Claude Code context usage with default settings" loading="lazy" src="/images/2026/20260218-claude-code-before.png"></p>
<p><strong>After (my setup):</strong>
<img alt="Claude Code context usage with my settings" loading="lazy" src="/images/2026/20260218-claude-code-after.png"></p>
<p>Is it perfect? No. I&rsquo;m pinned to an older version and relying on a community patch. But the tradeoff is worth it for the control I get back.</p>
<p>Your mileage may vary. If you&rsquo;re happy with the defaults, that&rsquo;s fine. But if you&rsquo;re a power user who wants more control, and who wants to understand what the AI is doing rather than just trusting it, these customizations might help.</p>
<p>The goal isn&rsquo;t to use AI tools less. It&rsquo;s to use them with your eyes open.</p>
<hr>
<p><strong>Update — April 2026</strong></p>
<p>My pinned v2.0.62 eventually stopped working. Every message returned &ldquo;interrupted&rdquo; immediately, without me actually interrupting anything. Turned out Anthropic started sending an <code>effortLevel</code> parameter in API responses that older Claude Code versions don&rsquo;t know how to handle — they just crash. <a href="https://github.com/anthropics/claude-code/issues/52178" target="_blank" >This GitHub issue</a>
 confirmed it.</p>
<p>The main patches repo hasn&rsquo;t been updated, but a contributor named <a href="https://github.com/anthrotype" target="_blank" >anthrotype</a>
 has been keeping the patches current independently. I upgraded to v2.1.112 (latest Claude Code at time of writing is 2.1.118) and applied two patches from their open PRs:</p>
<ol>
<li><strong>Thinking traces</strong> — <a href="https://github.com/aleks-apostle/claude-code-patches/pull/9" target="_blank" >PR #9</a>
. Same as before, restores thinking blocks inline.</li>
<li><strong>Tool visibility</strong> — <a href="https://github.com/aleks-apostle/claude-code-patches/pull/12" target="_blank" >PR #12</a>
. Fixes a newer Anthropic regression that collapses tool call details into vague summaries like &ldquo;Read 3 files&rdquo; instead of showing the actual file paths.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install -g @anthropic-ai/claude-code@2.1.112
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git clone git@github.com:anthrotype/claude-code-patches.git
</span></span><span style="display:flex;"><span>cd claude-code-patches
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git checkout update-v2.0.73
</span></span><span style="display:flex;"><span>node patch-thinking.js
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git checkout update-tool-visibility-patch
</span></span><span style="display:flex;"><span>node patch-tool-visibility.js
</span></span></code></pre></div><p>Restart Claude Code and both patches are active.</p>
<hr>
<p><strong>Update 2 — April 2026</strong></p>
<p>Nope, it&rsquo;s not working. The thinking traces are not displayed. I&rsquo;m switching to <a href="https://pi.dev/" target="_blank" >pi</a>
. Bye bye claude code.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Playing Tennis in the Netherlands: A Guide for Newcomers</title>
      <link>https://saeedesmaili.com/posts/playing-tennis-in-the-netherlands-a-guide-for-newcomers/</link>
      <pubDate>Thu, 08 May 2025 10:57:07 +0200</pubDate>
      <guid>https://saeedesmaili.com/posts/playing-tennis-in-the-netherlands-a-guide-for-newcomers/</guid>
      <description>&lt;p&gt;Welcome to the Netherlands! If you&amp;rsquo;re a newcomer like me and love tennis, or even if you&amp;rsquo;re looking to pick up a racket for the first time, you&amp;rsquo;re in luck. Tennis is popular here, and there are plenty of ways to play. However, figuring out the local tennis scene can be a bit of a puzzle when you&amp;rsquo;re new.&lt;/p&gt;
&lt;p&gt;When I moved here in 2022, one of the very first things I bought was a new tennis racket – even before I found a permanent apartment! I was eager to play, but I quickly realized I had a lot to learn about how tennis works in the Netherlands. I spent time hitting against a wall, researching clubs, and slowly finding my way. This blog post is my way of sharing what I&amp;rsquo;ve learned to help you get on the court faster and with less guesswork.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>Welcome to the Netherlands! If you&rsquo;re a newcomer like me and love tennis, or even if you&rsquo;re looking to pick up a racket for the first time, you&rsquo;re in luck. Tennis is popular here, and there are plenty of ways to play. However, figuring out the local tennis scene can be a bit of a puzzle when you&rsquo;re new.</p>
<p>When I moved here in 2022, one of the very first things I bought was a new tennis racket – even before I found a permanent apartment! I was eager to play, but I quickly realized I had a lot to learn about how tennis works in the Netherlands. I spent time hitting against a wall, researching clubs, and slowly finding my way. This blog post is my way of sharing what I&rsquo;ve learned to help you get on the court faster and with less guesswork.</p>
<h2 id="dutch-tennis-clubs">Dutch Tennis Clubs</h2>
<p>This was a big one for me. Back in my home country, we didn&rsquo;t really have &ldquo;tennis clubs&rdquo; in the same way. You&rsquo;d just rent a court by the hour. In the Netherlands, tennis clubs are a huge part of the scene.</p>
<p><strong>What&rsquo;s a Tennis Club?</strong> Think of it as a community. You pay a yearly membership fee, and that usually gives you:</p>
<ul>
<li><strong>Court Access:</strong> Play on the club&rsquo;s courts for free whenever they&rsquo;re available. Peak hours can be busy, though.</li>
<li><strong>Club Events:</strong> Most clubs have fun social events like &ldquo;toss&rdquo; evenings (more on that later!), ladder competitions, and small club tournaments.</li>
<li><strong>Coaching:</strong> Clubs usually have coaches, and lessons can be cheaper if you&rsquo;re a member because you&rsquo;re not paying extra to rent the court.</li>
<li><strong>A Tennis Community:</strong> You become part of a group of people who love tennis.</li>
</ul>
<p>While there are some places where you can just book a court by the hour, joining a club is often the best and most cost-effective way to play regularly.</p>
<p>It&rsquo;s good to know that <strong>club memberships are typically for the full calendar year.</strong> So, when you register, you become a member until the end of that year, and it usually gets automatically renewed for the next year if you don&rsquo;t cancel it in time. This means you generally can&rsquo;t be a member for just the summer months, for example.</p>
<h3 id="finding-and-joining-a-club">Finding and Joining a Club</h3>
<p>Search online or on Google Maps for clubs in your area. The good news is there are clubs <em>everywhere</em> – big cities, small towns, even villages! In Amsterdam, you can usually find a club within a 30-40 minute commute.</p>
<p>Most clubs have websites (often in Dutch). Look for info on:</p>
<ul>
<li><strong>Membership:</strong> Are they accepting new members? Popular city-center clubs might have waiting lists.</li>
<li><strong>Fees:</strong> How much does it cost per year?</li>
<li><strong>How to Join:</strong> Usually, you fill out an online form and transfer the membership fee to their bank account (IBAN).</li>
</ul>
<ul>
<li><strong>Types of Membership:</strong>
<ul>
<li><strong>Full/Adult Membership:</strong> The standard option with all benefits.</li>
<li><strong>Junior/Senior Memberships:</strong> Often cheaper for younger or older players.</li>
<li><strong>Off-Peak Membership:</strong> Some clubs offer cheaper rates if you only play during less busy times, like weekdays before 5 PM.</li>
</ul>
</li>
</ul>
<h3 id="bardienst-bar-duty">Bardienst (Bar Duty)</h3>
<p>Many Dutch tennis clubs have a bar or café run by the members themselves. This means you&rsquo;ll likely be scheduled for &ldquo;bardienst&rdquo; (bar duty) a few times a year (usually 2-3 times, for 3-4 hours each shift).</p>
<ul>
<li><strong>Is it mandatory?</strong> Usually, yes, to get full club benefits.</li>
<li><strong>What&rsquo;s it like?</strong> It can actually be a fun way to meet other members and serve drinks or snacks.</li>
<li><strong>Can I skip it?</strong> Most clubs let you pay a fee (around €30 per duty) if you can&rsquo;t or don&rsquo;t want to do it.</li>
</ul>
<h3 id="types-of-courts">Types of Courts</h3>
<p>You&rsquo;ll mostly find <strong>clay courts</strong> (often called &ldquo;gravel&rdquo; in Dutch) or <strong>smash courts</strong> (a type of artificial clay). Some clubs also have <strong>artificial grass</strong>, and less commonly, <strong>hard courts</strong> or indoor <strong>carpet courts</strong>.</p>
<h2 id="finding-hitting-partners">Finding Hitting Partners</h2>
<p>Okay, you&rsquo;ve joined a club (or are thinking about it), but who will you play with?</p>
<ul>
<li><strong>Online:</strong> Websites like the <a href="https://www.globaltennisnetwork.com/tennis/city/363-amsterdam-netherlands" target="_blank" >Global Tennis Network</a>
 have pages for Dutch cities where people post looking for partners. I found a couple of my regular hitting buddies this way.</li>
<li><strong>Your Network:</strong> Ask around, colleagues at work, fellow students, or friends might play or know someone who does. Use your company&rsquo;s Slack or a student WhatsApp group.</li>
<li><strong>At the Club:</strong> Visit clubs, watch people play, and if you see someone around your level, don&rsquo;t be shy to ask if they&rsquo;re open to a game.</li>
<li><strong>Club Activities:</strong> &ldquo;Toss&rdquo; evenings and ladder competitions (see below) are fantastic for meeting new players.</li>
<li><strong>Be Open:</strong> I&rsquo;ve even had people stop me on the street when they saw my racket sticking out of my bag and ask to play. We became doubles partners and played tournaments together.</li>
</ul>
<h2 id="finding-a-coach">Finding a Coach</h2>
<p>Want to improve and sharpen your skills?</p>
<ul>
<li><strong>Club Coaches:</strong> If you&rsquo;re a member of a club, definitely ask them first about lessons or if they know coaches who teach there. This is often cheaper because you won&rsquo;t need to pay extra for court rental – just the coach&rsquo;s fee.</li>
<li><strong>Ask Around:</strong> Chat with your tennis friends or hitting partners. They might have had good experiences with a coach and can give you a recommendation.</li>
<li><strong>Online Search:</strong> Some coaches have their own websites with contact details. You can even reach out to people in the tennis community (like me, if you need a suggestion!).</li>
</ul>
<ul>
<li><strong>Private or Group:</strong> You can usually choose between one-on-one lessons or cheaper group sessions.</li>
<li><strong>Cost:</strong> Expect to pay around €50-€60 per hour for the coach&rsquo;s time, plus court fees if applicable.</li>
</ul>
<h2 id="club-activities--official-competitions">Club Activities &amp; Official Competitions</h2>
<p>This is where the Dutch tennis scene really shines.</p>
<h3 id="club-level-events">Club-Level Events</h3>
<ul>
<li><strong>&ldquo;Toss&rdquo; Events:</strong> These are super popular. Usually, one evening a week, members just show up, have a coffee or beer, and get randomly paired up (&ldquo;tossed&rdquo;) for fun singles or doubles matches. It&rsquo;s very social and a great way to meet people.</li>
<li><strong>Ladder Competitions:</strong> A bit more serious than toss, but still unofficial (so it doesn&rsquo;t affect your official rating). You join a ladder in your club, challenge people around your rank, and try to climb up. You usually schedule matches with your opponent when it suits you both. You can often choose how many matches you want to play (e.g., one every two weeks, or more if you&rsquo;re keen!).</li>
</ul>
<h3 id="official-knltb-events-tournaments--competition">Official KNLTB Events (Tournaments &amp; Competition)</h3>
<p>The KNLTB is the Royal Dutch Lawn Tennis Association. Playing in their official events can affect your national tennis rating.</p>
<h4 id="tournaments">Tournaments</h4>
<ul>
<li><strong>How to Join:</strong> You&rsquo;ll need to be a club member or buy a <a href="https://toernooipas.knltb.nl/" target="_blank" >KNLTB tournament pass</a>
 (€25 per year).</li>
<li><strong>Finding Them:</strong> Check the <a href="https://mijnknltb.toernooi.nl/tournaments" target="_blank" >KNLTB tournaments website</a>
. You can filter by location, level, singles/doubles, etc.</li>
<li><strong>When:</strong> Mostly April to August, peaking in May and June, but there are tournaments year-round.</li>
<li><strong>Format:</strong> Often week-long (e.g., Saturday to the following Sunday) knockout events. Sometimes there are one-day tournaments. If there aren&rsquo;t many players in your category, it might be a round-robin (everyone plays each other).</li>
</ul>
<h4 id="competitie-league-competition">Competitie (League Competition)</h4>
<ul>
<li><strong>Team-Based:</strong> You need to be part of a club and join or form a team (men&rsquo;s, women&rsquo;s, or mixed).</li>
<li><strong>How it Works:</strong> Your team plays against other teams, usually on a set day each week for a season. There are different formats (e.g., a certain number of singles and doubles matches per team encounter). There&rsquo;s typically a <strong>spring competition</strong> (around March-May) and a <strong>fall competition</strong> (around September-October). Since it&rsquo;s team-based, you can try to find a team looking for players, and many teams also need substitutes now and then.</li>
<li><strong>Your Rating:</strong> Only the matches <em>you</em> personally play will affect your KNLTB rating, not your team&rsquo;s overall win or loss for the day.</li>
</ul>
<h3 id="your-knltb-rating">Your KNLTB Rating</h3>
<p>When you join the <a href="https://mijnknltb.toernooi.nl/" target="_blank" >KNLTB</a>
 (either through a club or a tournament pass), you&rsquo;ll be asked to self-rate yourself.</p>
<ul>
<li><strong>The Scale:</strong> It&rsquo;s from 1 (pro level) to 10 (complete beginner). This is the <em>opposite</em> of the US system, so a lower number is better.</li>
<li><strong>Starting Point:</strong> If you&rsquo;ve played matches and are comfortable with strokes and serving, you might start at an 8.</li>
<li><strong>How it Changes:</strong> Winning official tournament or competition matches, especially against someone with a better (lower number) rating, will improve your rating (make your number smaller). Losing, especially to someone with a worse (higher number) rating, will make your rating worse (make your number bigger).</li>
<li><strong>Doubles:</strong> Your doubles rating is separate and works similarly, based on the average rating of your team versus your opponents.</li>
</ul>
<p>I started at an 8 for both singles and doubles and have bounced between 9 and 7 as I&rsquo;ve played more.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2025/20250508-tv-linnaeushof-tennis-netherlands-min.jpg#center"/> <figcaption>
            <p>TV Linnaeushof club in Amsterdam</p>
        </figcaption>
</figure>

<h2 id="how-much-does-it-all-cost">How Much Does It All Cost?</h2>
<p>There are universal costs that you pay when you play tennis anywhere (rackets, shoes, balls). But here are some Netherlands-specific ones:</p>
<ul>
<li><strong>Club Membership:</strong> Roughly €100 to €300+ per year, depending on the club&rsquo;s location and facilities. Amsterdam city center clubs tend to be pricier.</li>
<li><strong>Coaching:</strong> Around €50-€60 per hour for the coach, plus court fees if you&rsquo;re not taking lessons at your own club.</li>
<li><strong>Tournament/Competition Fees:</strong> Usually around €15 to enter a tournament. Competition fees are often paid as a team and might be around €10 per player per season.</li>
<li><strong>Court Rental (if not a member):</strong>
<ul>
<li><strong>Outdoor (Summer):</strong> Around €20+ per hour in areas like Amsterdam.</li>
<li><strong>Indoor (Winter):</strong> Much more expensive, as courts are limited. Expect €40-€50+ per hour, especially at peak times.</li>
</ul>
</li>
<li><strong>Bar Duty Buy-Out:</strong> If you opt out of your &ldquo;bardienst,&rdquo; it&rsquo;s usually around €30 per shift. So, if you have three shifts, that&rsquo;s an extra €90.</li>
</ul>
<h2 id="the-tennis-season">The Tennis Season</h2>
<p>You can pretty much play tennis all year round if you don&rsquo;t mind the cost of indoor courts in winter.</p>
<ul>
<li><strong>Outdoor Season:</strong> Generally from mid-March to mid-November (about 8 months). Yes, it rains, and yes, it can be windy. But I&rsquo;ve found that most of the time, the weather is fine for playing. Light rain is often playable, especially on artificial surfaces. Serious wind that truly ruins a game is rare, maybe 10% of the time in my experience. July and August can get hot in the middle of the day.</li>
<li><strong>Indoor Season:</strong> Mid-November to mid-March. You&rsquo;ll need to find and book indoor courts for December, January, and February. Although they can be expensive, you <em>can</em> find indoor courts in winter. It&rsquo;s often easier and cheaper if you&rsquo;re lucky enough to be available to play during off-peak hours. I&rsquo;ve personally played at about 5 different indoor facilities around Amsterdam and one in Haarlem.</li>
</ul>
<h2 id="getting-your-tennis-gear">Getting Your Tennis Gear</h2>
<p>If you&rsquo;re new to tennis or just setting up in the Netherlands, you&rsquo;ll need some gear:</p>
<ul>
<li><strong>Tennis Racket:</strong> Obvious, but important.</li>
<li><strong>Proper Tennis Shoes:</strong> Don&rsquo;t play in running shoes. Tennis shoes are designed for side-to-side movements and will help prevent injuries.</li>
<li><strong>Tennis Balls:</strong> You&rsquo;ll need to buy cans of balls regularly.</li>
<li><strong>Racket Restringing:</strong> If you play often, your strings will eventually break or lose tension.</li>
<li><strong>Tennis Clothes:</strong> Comfortable sportswear.</li>
</ul>
<h3 id="where-to-buy">Where to Buy</h3>
<p>I do most of my tennis-specific shopping online. Some websites I&rsquo;ve used and can recommend include <a href="https://www.tennispro.nl" target="_blank" >tennispro.nl</a>
, <a href="https://www.tennis-point.nl/" target="_blank" >tennis-point.nl</a>
, <a href="https://www.tennisdirect.nl/" target="_blank" >tennisdirect.nl</a>

, <a href="https://www.tenniswarehouse-europe.com/" target="_blank" >tenniswarehouse-europe.com</a>
, <a href="https://www.kctennis.nl" target="_blank" >kctennis.nl</a>
, and <a href="https://www.tennis-voordeel.nl/" target="_blank" >tennis-voordeel.nl</a>
.</p>
<p>Tennis equipment can get expensive, but here are a few tips:</p>
<ul>
<li><strong>Look for Discounts:</strong> Online stores often have sales, especially around Black Friday, Christmas, or during Grand Slam tournaments.</li>
<li><strong>Buy in Bulk:</strong> For items you use frequently like tennis balls, racket strings, or overgrips, buying larger packs (e.g., 12 cans of balls, a pack of 60 overgrips) is usually cheaper in the long run.</li>
<li><strong>Test Rackets Before Buying:</strong> If you&rsquo;re unsure about a new racket model, many online shops offer a racket testing service. For a small fee (around €10-€15), they&rsquo;ll send you a test racket to try for a week or two. This is much cheaper than buying a €200-€300 racket you might not like! You can try a few different models this way before committing.</li>
</ul>
<h2 id="a-few-things-to-know">A Few Things to Know</h2>
<p>Based on my experience, here are a couple of common practices you&rsquo;ll see on and off the court:</p>
<ul>
<li><strong>Sweeping the Court:</strong> This isn&rsquo;t unique to the Netherlands but is very common due to the many clay and smash courts. After you finish playing (a practice session or a match), you need to sweep the court with the large brooms provided. This keeps it in good condition for the next players. In an official match, you&rsquo;ll sweep your half, and your opponent will sweep theirs.</li>
<li><strong>Post-Match Socializing:</strong> Dutch tennis community is generally quite social. It&rsquo;s common for the winning player or team in a tournament event to offer their opponent(s) a drink at the club bar after the match. Many people will hang around and chat for a bit. Competition is even more of a social event. The home team usually provides snacks and will buy drinks (coffee, tea, etc.) for the visiting team. Before, between, and especially after all the matches are done, both teams will often sit together, have refreshments, and chat about tennis and life in general.</li>
</ul>
<p>Playing tennis in the Netherlands has been a fantastic experience for me. It&rsquo;s a great way to stay active, meet new people, and feel more connected to local life. While it might seem a bit different at first, the system is well-organized, and there are tons of opportunities to play at every level.</p>
<p>I hope this guide helps you navigate the Dutch tennis scene. Grab your racket, find a club or a partner, and enjoy the game! Good luck, and maybe I&rsquo;ll see you on the court. Feel free to reach out to me via email if you&rsquo;re an intermediate tennis player in the Netherlands and looking for new people to play with.</p>
<h2 id="more-resources">More Resources</h2>
<ul>
<li>Expat Info Holland: <a href="https://expatinfoholland.nl/help-guides/sports/playing-tennis-in-netherlands/" target="_blank" >Tennis in Netherlands</a>
</li>
</ul>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Building a Personal Content Recommendation System, Part Two: Data Processing and Cleaning</title>
      <link>https://saeedesmaili.com/posts/building-a-personal-content-recommendation-system-data-processing-and-cleaning/</link>
      <pubDate>Wed, 26 Mar 2025 22:32:31 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/building-a-personal-content-recommendation-system-data-processing-and-cleaning/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://saeedesmaili.com/posts/building-a-content-recommendation-system-for-myself-part-one/&#34; target=&#34;_blank&#34; &gt;part one of this blog series&lt;/a&gt;
, I explored the motivation behind developing a personal recommendation system. The main goals are to learn how recommendation systems work and to build a tool that helps me find interesting blog posts and articles from feeds where only 1 in 20 posts might match my content interests.&lt;/p&gt;
&lt;p&gt;If you are interested in the technical implementation, the complete codebase is available in &lt;a href=&#34;https://github.com/saeedesmaili/content-recommendation&#34; target=&#34;_blank&#34; &gt;this github repository&lt;/a&gt;
.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>In <a href="https://saeedesmaili.com/posts/building-a-content-recommendation-system-for-myself-part-one/" target="_blank" >part one of this blog series</a>
, I explored the motivation behind developing a personal recommendation system. The main goals are to learn how recommendation systems work and to build a tool that helps me find interesting blog posts and articles from feeds where only 1 in 20 posts might match my content interests.</p>
<p>If you are interested in the technical implementation, the complete codebase is available in <a href="https://github.com/saeedesmaili/content-recommendation" target="_blank" >this github repository</a>
.</p>
<h2 id="creating-an-articles-dataset">Creating an Articles Dataset</h2>
<h3 id="step-1-initial-list-of-liked-articles">Step 1: Initial List of Liked Articles</h3>
<p>Daily browsing of RSS feeds involves scanning through many articles, where sometimes engaging titles lead me to read their opening paragraphs. Through <a href="https://saeedesmaili.com/posts/my-content-consumption-workflow/" target="_blank" >my established content workflow</a>
, I save interesting items to Pocket using Inoreader&rsquo;s built-in feature.</p>
<p>Initially, I planned to use a list of RSS items that I had clicked on. However, Inoreader doesn&rsquo;t provide an API to access this reading history, which led me to explore other options.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2025/20250326-my-rss-based-content-consumption-workflow.png#center"/> <figcaption>
            <p>My RSS-based Content Consumption Workflow</p>
        </figcaption>
</figure>

<p>Looking further into my workflow, I found a better solution: my archived items in Pocket. While I regularly read through my Pocket items, I never delete them - just archive them. This gave me a valuable collection of reading history.</p>
<p>Using the <a href="https://getpocket.com/developer/docs/v3/retrieve" target="_blank" >Pocket API</a>
, I retrieved around 4,000 URLs, dating back to December 2022. Though some archived items might be less relevant now, the dataset reflects my reading interests well. Later, I could add features like upvoting and downvoting to refine the content selection, but that&rsquo;s beyond the current scope.</p>
<p>Each item in the dataset includes this basic information:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;pocket_item_id&#34;</span>: <span style="color:#ae81ff">5598506</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;given_url&#34;</span>: <span style="color:#e6db74">&#34;https://nat.org/&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;resolved_url&#34;</span>: <span style="color:#e6db74">&#34;http://nat.org/&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;Nat Friedman&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;time_added&#34;</span>: <span style="color:#ae81ff">1736940058</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;word_count&#34;</span>: <span style="color:#ae81ff">451</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;domain&#34;</span>: <span style="color:#e6db74">&#34;nat.org&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="step-2-text-content-of-articles">Step 2: Text Content of Articles</h3>
<p>To build an effective recommendation model, we need the actual content of each URL, not just its metadata, but unfortunately Pocket doesn&rsquo;t provide that. While I love writing scrapers, for this project I want to focus on developing the recommendation model itself.</p>
<p>The <a href="https://jina.ai/reader/" target="_blank" >Jina Reader API</a>
 offers a straightforward solution, converting webpage content into markdown format. Here&rsquo;s an example of what we get (shortened version, full content available <a href="https://r.jina.ai/https://nat.org/" target="_blank" >here</a>
):</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 1240 329"
      >
      <g transform='translate(8,16)'>
<path d='M 212,216 L 220,200' fill='none' stroke='currentColor'></path>
<path d='M 304,208 L 312,192' fill='none' stroke='currentColor'></path>
<path d='M 600,192 L 608,176' fill='none' stroke='currentColor'></path>
<circle cx='0' cy='144' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='160' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='176' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='192' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='208' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='224' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='240' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='0' cy='256' r='6' stroke='currentColor' fill='currentColor'></circle>
<circle cx='312' cy='192' r='6' stroke='currentColor' fill='#fff'></circle>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='0' y='292' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='8' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='8' y='84' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='8' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='8' y='292' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='292' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='24' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='24' y='292' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='32' y='180' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='32' y='196' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='32' y='212' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='32' y='244' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='32' y='260' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='40' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='212' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='40' y='244' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='292' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='48' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='48' y='212' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='48' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='48' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='48' y='292' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='56' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='244' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='56' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='292' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='64' y='244' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='292' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='212' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='72' y='244' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='260' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='72' y='292' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='80' y='244' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='80' y='292' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='88' y='212' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='88' y='260' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='96' y='244' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='260' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='96' y='292' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='104' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='212' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='244' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='260' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='180' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='112' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='112' y='292' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='120' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='120' y='212' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='120' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='120' y='260' fill='currentColor' style='font-size:1em'>B</text>
<text text-anchor='middle' x='120' y='292' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='128' y='180' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='128' y='212' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='128' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='128' y='292' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='196' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='136' y='212' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='136' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='136' y='244' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='260' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='136' y='292' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='144' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='144' y='196' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='144' y='212' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='144' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='144' y='244' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='144' y='292' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='212' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='152' y='228' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='244' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='260' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='152' y='292' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='180' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='160' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='160' y='212' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='160' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='160' y='244' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='260' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='168' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='168' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='168' y='196' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='168' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='168' y='244' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='168' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='292' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='176' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='176' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='184' y='196' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='184' y='212' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='184' y='244' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='192' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='192' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='192' y='180' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='192' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='192' y='212' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='192' y='244' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='192' y='260' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='200' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='200' y='212' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='200' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='200' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='208' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='208' y='164' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='208' y='180' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='208' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='212' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='208' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='216' y='84' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='216' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='216' y='164' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='216' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='216' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='216' y='244' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='216' y='260' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='224' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='224' y='164' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='224' y='180' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='224' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='224' y='212' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='224' y='244' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='224' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='232' y='84' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='232' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='232' y='164' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='232' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='232' y='196' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='232' y='212' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='232' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='240' y='148' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='240' y='164' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='240' y='180' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='240' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='240' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='240' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='240' y='260' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='248' y='84' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='248' y='196' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='248' y='212' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='248' y='244' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='248' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='256' y='148' fill='currentColor' style='font-size:1em'>V</text>
<text text-anchor='middle' x='256' y='164' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='256' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='256' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='256' y='212' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='256' y='244' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='256' y='260' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='264' y='148' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='264' y='164' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='264' y='180' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='264' y='196' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='264' y='212' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='264' y='244' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='272' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='212' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='272' y='244' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='272' y='260' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='280' y='164' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='280' y='196' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='280' y='212' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='280' y='244' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='280' y='260' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='164' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='288' y='180' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='288' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='288' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='260' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='296' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='296' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='296' y='212' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='296' y='244' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='296' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='304' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='304' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='304' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='304' y='244' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='304' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='312' y='164' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='312' y='180' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='312' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='320' y='84' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='320' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='320' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='320' y='244' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='320' y='260' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='328' y='164' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='328' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='328' y='196' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='328' y='212' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='328' y='244' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='328' y='260' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='336' y='164' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='336' y='180' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='336' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='336' y='212' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='336' y='244' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='344' y='196' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='344' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='344' y='244' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='344' y='260' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='352' y='164' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='352' y='180' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='352' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='352' y='212' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='352' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='352' y='260' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='360' y='164' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='360' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='360' y='196' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='360' y='244' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='360' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='368' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='368' y='180' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='368' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='368' y='212' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='368' y='244' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='368' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='376' y='164' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='376' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='376' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='376' y='212' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='376' y='244' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='376' y='260' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='384' y='164' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='384' y='180' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='384' y='196' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='384' y='212' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='384' y='244' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='384' y='260' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='392' y='164' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='392' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='392' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='392' y='212' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='392' y='244' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='392' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='400' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='400' y='196' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='400' y='244' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='400' y='260' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='408' y='164' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='408' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='408' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='408' y='244' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='408' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='416' y='164' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='416' y='180' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='416' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='416' y='212' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='416' y='244' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='416' y='260' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='424' y='164' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='424' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='424' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='424' y='212' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='424' y='244' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='424' y='260' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='432' y='164' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='432' y='180' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='432' y='196' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='432' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='432' y='244' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='432' y='260' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='440' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='440' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='440' y='212' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='440' y='244' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='440' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='448' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='448' y='196' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='448' y='212' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='448' y='244' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='448' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='456' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='456' y='180' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='456' y='196' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='456' y='212' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='456' y='244' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='456' y='260' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='464' y='164' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='464' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='464' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='464' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='464' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='472' y='164' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='472' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='472' y='196' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='472' y='212' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='472' y='244' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='472' y='260' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='480' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='480' y='180' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='480' y='196' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='480' y='212' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='480' y='244' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='480' y='260' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='488' y='164' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='488' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='488' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='488' y='212' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='488' y='244' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='488' y='260' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='496' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='496' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='212' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='496' y='244' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='496' y='260' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='504' y='180' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='504' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='504' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='504' y='260' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='512' y='180' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='512' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='512' y='244' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='512' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='520' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='520' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='520' y='244' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='520' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='528' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='528' y='196' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='528' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='528' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='536' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='536' y='196' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='536' y='244' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='536' y='260' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='544' y='180' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='544' y='196' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='544' y='244' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='544' y='260' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='552' y='180' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='552' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='552' y='244' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='552' y='260' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='560' y='180' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='560' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='560' y='244' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='560' y='260' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='568' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='568' y='196' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='568' y='244' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='568' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='576' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='576' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='576' y='244' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='576' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='584' y='180' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='584' y='196' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='584' y='260' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='592' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='592' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='592' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='600' y='180' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='600' y='260' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='608' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='608' y='260' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='616' y='180' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='616' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='616' y='260' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='624' y='180' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='624' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='624' y='260' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='632' y='180' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='632' y='196' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='640' y='180' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='640' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='648' y='180' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='648' y='196' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='656' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='656' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='664' y='180' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='664' y='196' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='672' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='672' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='680' y='180' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='680' y='196' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='688' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='688' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='696' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='696' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='704' y='180' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='704' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='712' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='712' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='720' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='720' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='728' y='180' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='728' y='196' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='736' y='180' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='736' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='744' y='180' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='744' y='196' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='752' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='752' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='760' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='760' y='196' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='768' y='180' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='768' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='776' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='776' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='784' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='784' y='196' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='792' y='180' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='792' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='800' y='180' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='800' y='196' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='808' y='180' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='808' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='816' y='180' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='816' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='824' y='180' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='824' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='832' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='832' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='840' y='180' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='840' y='196' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='848' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='856' y='180' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='864' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='872' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='880' y='180' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='888' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='896' y='180' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='904' y='180' fill='currentColor' style='font-size:1em'>B</text>
<text text-anchor='middle' x='912' y='180' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='920' y='180' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='928' y='180' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='936' y='180' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='944' y='180' fill='currentColor' style='font-size:1em'>Q</text>
<text text-anchor='middle' x='952' y='180' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='960' y='180' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='968' y='180' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='976' y='180' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='984' y='180' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='992' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1000' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1008' y='180' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='1016' y='180' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='1024' y='180' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1032' y='180' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1040' y='180' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='1048' y='180' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1056' y='180' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='1064' y='180' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1072' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1080' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1088' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1096' y='180' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='1104' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1112' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1120' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1128' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1136' y='180' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='1144' y='180' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1152' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1160' y='180' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1168' y='180' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='1176' y='180' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1184' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1192' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1200' y='180' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='1208' y='180' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='1216' y='180' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='1224' y='180' fill='currentColor' style='font-size:1em'>)</text>
</g>

    </svg>
  
</div>
<p>The converted content is excellent, but it has two main challenges:</p>
<ol>
<li>Markdown formatting adds unnecessary complexity for our use case. Plain text would work better.</li>
<li>Articles vary greatly in length - from short paragraphs to long essays - which could make comparing them via semantic similarity difficult later.</li>
</ol>
<figure class="align-center ">
    <img loading="lazy" src="/images/2025/20250326-distribution-of-documents-lengths-in-tokens.png#center"/> <figcaption>
            <p>Distribution of Documents Lengths (in Tokens)</p>
        </figcaption>
</figure>

<h3 id="step-3-summarizing-the-markdown-content">Step 3: Summarizing the Markdown Content</h3>
<p>To solve both issues at once, I used the <code>gemini-2.0-flash</code> model to create consistent-length summaries of each document. Here&rsquo;s an example summary for <code>https://nat.org/</code>:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 5328 89"
      >
      <g transform='translate(8,16)'>
<circle cx='3496' cy='64' r='6' stroke='currentColor' fill='#fff'></circle>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='192' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='216' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='248' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='248' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='264' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='264' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='280' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='304' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='312' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='320' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='336' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='344' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='352' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='360' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='360' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='368' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='368' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='376' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='384' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='400' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='408' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='416' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='416' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='424' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='424' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='432' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='432' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='440' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='440' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='448' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='448' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='456' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='464' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='464' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='464' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='472' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='472' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='472' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='480' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='488' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='488' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='488' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='496' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='496' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='504' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='504' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='512' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='512' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='512' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='520' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='520' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='528' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='528' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='536' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='536' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='536' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='544' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='544' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='552' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='552' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='552' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='560' y='4' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='560' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='568' y='4' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='568' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='568' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='576' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='576' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='584' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='584' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='584' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='592' y='36' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='592' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='600' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='600' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='600' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='608' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='608' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='608' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='616' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='616' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='616' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='624' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='624' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='624' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='632' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='632' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='640' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='640' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='640' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='648' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='648' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='648' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='656' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='656' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='664' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='664' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='672' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='672' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='672' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='680' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='680' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='680' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='688' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='696' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='696' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='704' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='704' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='704' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='712' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='712' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='720' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='720' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='720' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='728' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='728' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='728' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='736' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='736' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='744' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='744' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='744' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='752' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='752' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='760' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='760' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='760' y='68' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='768' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='768' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='768' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='776' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='784' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='784' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='792' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='792' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='792' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='800' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='800' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='800' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='808' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='808' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='816' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='816' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='816' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='824' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='824' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='824' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='832' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='832' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='832' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='840' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='840' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='840' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='848' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='848' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='848' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='856' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='856' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='864' y='4' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='864' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='864' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='872' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='872' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='880' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='880' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='888' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='888' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='888' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='896' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='896' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='904' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='904' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='904' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='912' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='912' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='920' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='920' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='928' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='928' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='936' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='936' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='944' y='4' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='944' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='944' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='952' y='4' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='952' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='952' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='960' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='960' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='960' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='968' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='968' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='976' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='976' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='976' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='984' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='984' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='984' y='68' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='992' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='992' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1000' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1000' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='1000' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1008' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1008' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1008' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1016' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1016' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1024' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1024' y='36' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1024' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1032' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1032' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1032' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1040' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1040' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1048' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1048' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1056' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='1056' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1056' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1064' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1064' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1072' y='4' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='1072' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='1072' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1080' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1080' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1088' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1088' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1088' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1096' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1096' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1096' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1104' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1104' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1112' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1112' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1112' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1120' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1120' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1120' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='1128' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1136' y='4' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='1136' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1136' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1144' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1144' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1152' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='1152' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1152' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1160' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1160' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1168' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1168' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1168' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='1176' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1176' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1176' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1184' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1184' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='1184' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1192' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='1192' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1200' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1200' y='36' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='1200' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1208' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1216' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1216' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1216' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1224' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1224' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1224' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1232' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1232' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1232' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1240' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1240' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1240' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='1248' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1248' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='1256' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1256' y='68' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='1264' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1264' y='36' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1264' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1272' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1272' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1280' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1280' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1288' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1288' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1288' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='1296' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1296' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1304' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1304' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1304' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='1312' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1312' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1312' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1320' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1320' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1320' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1328' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1328' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1328' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1336' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='1344' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1344' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='1352' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1352' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1352' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1360' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1360' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1360' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1368' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1368' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1376' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1376' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1384' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1384' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1392' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1392' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1392' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1400' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1400' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1400' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1408' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1416' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1416' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1416' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1424' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1424' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1424' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1432' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1432' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1432' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1440' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1440' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1440' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1448' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1456' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1456' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1456' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1464' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1464' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1464' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='1472' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1480' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1480' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1480' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1488' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1488' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1488' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1496' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1496' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1504' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1504' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1512' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1512' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1512' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1520' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1520' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1528' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1528' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1528' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1536' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1536' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1536' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1544' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1552' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1552' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1552' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1560' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1560' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1560' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1568' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1568' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='1568' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1576' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1584' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1584' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1584' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1592' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1592' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1592' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1600' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1600' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1600' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1608' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1608' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1616' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1616' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1616' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1624' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1624' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1624' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1632' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1632' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1632' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1640' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1640' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1648' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1648' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='1648' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1656' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1656' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1664' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1664' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1664' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1672' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1672' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1680' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1680' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1680' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1688' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='1688' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1696' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1696' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1704' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1704' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1704' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1712' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1712' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1712' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1720' y='4' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='1720' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1720' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1728' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1728' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1736' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1744' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1744' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1744' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1752' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1752' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1752' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1760' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1760' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1768' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='1768' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1776' y='4' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='1776' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1776' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1784' y='4' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='1784' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1784' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1792' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1792' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1800' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1800' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='1800' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1808' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='1808' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1808' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1816' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1816' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='1824' y='4' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='1824' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1832' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1832' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1832' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1840' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1840' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1840' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1848' y='4' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='1848' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1856' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1856' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1864' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1864' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1864' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1872' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1872' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='1880' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='1880' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1888' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1888' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1888' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='1896' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1896' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1896' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1904' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='1904' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1904' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1912' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1912' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1920' y='4' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='1920' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1920' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='1928' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='1928' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1928' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1936' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='1936' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='1944' y='4' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='1944' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='1952' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='1952' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1960' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1960' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1960' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1968' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1968' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1968' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1976' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='1976' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1984' y='4' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='1984' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='1992' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='1992' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='1992' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2000' y='4' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='2000' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2000' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2008' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='2008' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2016' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='2016' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2016' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2024' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2024' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='2032' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='2032' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2040' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2040' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2040' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2048' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2048' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2048' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2056' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2056' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2056' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2064' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2064' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2064' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2072' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2072' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2072' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2080' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2088' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2088' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2088' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2096' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='2096' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2096' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2104' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='2104' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2112' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2112' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2120' y='4' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='2120' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2120' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2128' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2128' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2136' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2136' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2136' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2144' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2144' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2144' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2152' y='4' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='2152' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='2152' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2160' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2160' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2168' y='36' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='2168' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2176' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2176' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2176' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2184' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2184' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2192' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2192' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2200' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='2200' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2200' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2208' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2208' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2216' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2216' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2224' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2224' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2224' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2232' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2232' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2240' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2240' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2240' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='2248' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2248' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2248' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2256' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2256' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2256' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2264' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2264' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2264' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='2272' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2272' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2280' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2280' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2288' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2288' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2288' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2296' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2296' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2304' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2304' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2304' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2312' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2312' y='68' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='2320' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2320' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2320' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2328' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2328' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2328' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2336' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2336' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2344' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2344' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2344' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2352' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2352' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='2352' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2360' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2360' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2368' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2368' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2368' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2376' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2376' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2376' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2384' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2384' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2384' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2392' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2392' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2400' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2400' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2400' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2408' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2408' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2408' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2416' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2416' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2416' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2424' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2424' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2432' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2432' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2432' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2440' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2440' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2440' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2448' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2448' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2448' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2456' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2456' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2456' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='2472' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2472' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2472' y='68' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='2480' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2480' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2488' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2488' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2496' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2496' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2496' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2504' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='2504' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2504' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='2512' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2512' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2512' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2520' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2528' y='4' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='2528' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2528' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2536' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2536' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2536' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2544' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2544' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2544' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2552' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2552' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2560' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2560' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2560' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2568' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2568' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2568' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2576' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2576' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2576' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2584' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2584' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2592' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2592' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2600' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2600' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2600' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2608' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2608' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2608' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2616' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2616' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2624' y='4' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='2624' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2632' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2632' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2632' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2640' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2640' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2640' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2648' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='2648' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2656' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2656' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2656' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2664' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2664' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2664' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2672' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2672' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2680' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2680' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2680' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2688' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2688' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2696' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2696' y='36' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2704' y='4' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='2704' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2704' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2712' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2712' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2712' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2720' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2720' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2720' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2728' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2728' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2736' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='2744' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2744' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2744' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2752' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2752' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2752' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2760' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2760' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2768' y='36' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='2768' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2776' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2776' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='2776' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2784' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2784' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2792' y='4' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='2792' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2792' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2800' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2800' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2808' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2808' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2808' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2816' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2816' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2816' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2824' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2824' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='2824' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2832' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2832' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2832' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2840' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2840' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='2840' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2848' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2848' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2856' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2856' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2856' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2864' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2864' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2872' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='2872' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2872' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2880' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2880' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2888' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='2888' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2888' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='2896' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='2896' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2904' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2904' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2904' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2912' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='2912' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='2912' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2920' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='2920' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='2928' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2928' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2936' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2944' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2952' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2952' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='2960' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='2960' y='68' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='2968' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='2968' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='2976' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='2976' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='2984' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='2984' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='2992' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='2992' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='3000' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3000' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='3008' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='3008' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3016' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='3024' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='3032' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3032' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3040' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3040' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3048' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='3056' y='4' fill='currentColor' style='font-size:1em'>B</text>
<text text-anchor='middle' x='3056' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='3064' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3072' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='3072' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='3080' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3088' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='3088' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='3096' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3096' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3104' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3104' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3112' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3112' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='3120' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3128' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='3128' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3136' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3136' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='3144' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3152' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='3152' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='3160' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='3160' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='3168' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='3168' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3176' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='3192' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='3200' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3208' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3216' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='3232' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='3240' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3248' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='3256' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='3272' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='3280' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3288' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='3296' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='3304' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3312' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3320' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3328' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3336' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='3352' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3360' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3368' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3384' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='3392' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3400' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='3408' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3416' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3424' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='3432' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3440' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='3448' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3456' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3464' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='3472' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='3480' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='3504' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='3512' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3520' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3528' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='3536' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='3544' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3552' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='3560' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='3568' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3576' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='3584' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='3600' y='68' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='3608' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3616' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3624' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3632' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='3640' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='3648' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='3656' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='3672' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='3680' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3688' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='3696' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3704' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3712' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='3720' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='3728' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3736' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3744' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='3752' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3760' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='3768' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='3784' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='3792' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='3800' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3808' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3816' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3832' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='3840' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3848' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='3864' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='3872' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3880' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3888' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='3896' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='3904' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='3920' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='3928' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3936' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='3944' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='3960' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='3968' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='3976' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='3984' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='3992' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='4000' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4008' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4016' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4032' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4040' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='4056' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='4064' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4072' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='4080' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4088' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4104' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='4112' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4120' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4136' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='4144' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4152' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='4160' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4168' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4176' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='4184' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='4192' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4200' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4208' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='4216' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4224' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4232' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='4248' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='4256' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4264' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='4272' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4288' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='4296' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4304' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='4312' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4320' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='4328' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4336' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4344' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4352' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='4368' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4376' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4384' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='4400' y='68' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='4416' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='4424' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4432' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4440' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4448' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4456' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='4464' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4480' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='4488' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4496' y='68' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='4504' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4512' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4528' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4536' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4544' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4560' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='4568' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4576' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='4584' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4592' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='4600' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4608' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4624' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4632' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='4648' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='4656' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4664' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4680' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='4688' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4696' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4704' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4720' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='4728' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='4736' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4744' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4760' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='4768' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4784' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='4792' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4800' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='4808' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4816' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='4824' y='68' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='4832' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='4840' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='4856' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='4864' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4872' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='4880' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4888' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='4904' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='4912' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='4920' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='4928' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='4944' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='4952' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='4968' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='4976' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='4984' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='5000' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='5008' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='5016' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='5024' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='5040' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='5048' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='5064' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='5072' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='5080' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='5088' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='5096' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='5104' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='5112' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='5128' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='5136' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='5144' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='5160' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='5168' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='5176' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='5184' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='5192' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='5200' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='5208' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='5216' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='5224' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='5240' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='5248' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='5256' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='5264' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='5272' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='5280' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='5288' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='5296' y='68' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='5304' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='5312' y='68' fill='currentColor' style='font-size:1em'>.</text>
</g>

    </svg>
  
</div>
<p>By having the model to wrap summaries in XML tags (<code>&lt;summary&gt; ... &lt;/summary&gt;</code>), I achieved two things:</p>
<ul>
<li>Clean extraction of just the summary text, in case LLM generates other texts before or after the summary (e.g. if it starts with <code>Sure, I can help you with summarizing the content ...</code>).</li>
<li>Easy identification of broken links and error pages. This helped remove 141 invalid entries, leaving me with 3,642 quality documents.</li>
</ul>
<p>The summaries created a more balanced distribution of text lengths:</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2025/20250326-distribution-of-summary-lengths-in-tokens.png#center"/> <figcaption>
            <p>Distribution of Summary Lengths (in Tokens)</p>
        </figcaption>
</figure>

<h2 id="next-steps">Next Steps</h2>
<p>The final dataset now contains well-organized entries with clean metadata and text summaries:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;pocket_item_id&#34;</span>: <span style="color:#ae81ff">5598506</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;given_url&#34;</span>: <span style="color:#e6db74">&#34;https://nat.org/&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;resolved_url&#34;</span>: <span style="color:#e6db74">&#34;http://nat.org/&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;Nat Friedman&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;time_added&#34;</span>: <span style="color:#ae81ff">1736940058</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;word_count&#34;</span>: <span style="color:#ae81ff">451</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;domain&#34;</span>: <span style="color:#e6db74">&#34;nat.org&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;text&#34;</span>: <span style="color:#e6db74">&#34;Title: Nat Friedman\n\nURL Source: https://nat.org/\n\nMarkdown Content:\n\nI&#39;m an investor, ...&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;summary&#34;</span>: <span style="color:#e6db74">&#34;I&#39;m an investor, entrepreneur, and developer who&#39;s been online since 1991, considering it my true hometown. I went to ...&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Next week, I&rsquo;ll work on creating a user profile by concatenating these text summaries and metadata and converting them into vectors using <a href="https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/" target="_blank" >Embedding models</a>
. I&rsquo;m also researching how modern recommendation systems work with transformers and LLMs, which will help guide this project. After all, learning is the main goal here.</p>
<p>I welcome any thoughts or questions about this series - feel free to reach out!</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Building a Personal Content Recommendation System, Part One: Introduction</title>
      <link>https://saeedesmaili.com/posts/building-a-content-recommendation-system-for-myself-part-one/</link>
      <pubDate>Sun, 16 Mar 2025 15:49:44 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/building-a-content-recommendation-system-for-myself-part-one/</guid>
      <description>&lt;p&gt;Every morning, my RSS reader greets me with hundreds of new posts. Tech blogs, indie developers&amp;rsquo; journals, photography content - they all compete for attention. While &lt;a href=&#34;https://saeedesmaili.com/posts/my-content-consumption-workflow/&#34; target=&#34;_blank&#34; &gt;I&amp;rsquo;ve gotten good at quickly scanning through these feeds&lt;/a&gt;
, I keep wondering about all the great content I might be missing from sources I&amp;rsquo;ve had to ignore simply because their signal-to-noise ratio doesn&amp;rsquo;t justify daily checking.&lt;/p&gt;
&lt;p&gt;On the other hand, the posts that I shortlist from my RSS feeds and read or listen to, end up on a curated repository of articles that have passed my personal quality threshold, so I have access to a valuable collection of content (on &lt;a href=&#34;https://getpocket.com/&#34; target=&#34;_blank&#34; &gt;Pocket&lt;/a&gt;
) that is relevant to my interests. This made me wonder, can I utilize this data, and create a content recommendation system tailored to my preferences? Can I build a system that would review new posts from feeds where only 1 in 20 posts might match my content priorities, and filter those for me?&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>Every morning, my RSS reader greets me with hundreds of new posts. Tech blogs, indie developers&rsquo; journals, photography content - they all compete for attention. While <a href="https://saeedesmaili.com/posts/my-content-consumption-workflow/" target="_blank" >I&rsquo;ve gotten good at quickly scanning through these feeds</a>
, I keep wondering about all the great content I might be missing from sources I&rsquo;ve had to ignore simply because their signal-to-noise ratio doesn&rsquo;t justify daily checking.</p>
<p>On the other hand, the posts that I shortlist from my RSS feeds and read or listen to, end up on a curated repository of articles that have passed my personal quality threshold, so I have access to a valuable collection of content (on <a href="https://getpocket.com/" target="_blank" >Pocket</a>
) that is relevant to my interests. This made me wonder, can I utilize this data, and create a content recommendation system tailored to my preferences? Can I build a system that would review new posts from feeds where only 1 in 20 posts might match my content priorities, and filter those for me?</p>
<p>Over the following weeks, my goal is to build this content recommendation system, and to document my thoughts, process, and lessons learned in a blog post series, of which this is the first one. The rough process in my mind includes analyzing the corpus of previously liked content, building a model of personal interests based on content embeddings, and comparing new content against this model to predict the likelihood of interest. If you want to follow along, follow <a href="https://saeedesmaili.com/rss.xml" target="_blank" >my blog&rsquo;s RSS feed</a>
 or subscribe to my newsletter to get notified about new posts.</p>
<h2 id="the-process">The process</h2>
<p>The heart of this project is experimenting with different recommendation algorithms, but solid groundwork comes first. Before diving into the mechanics of the recommender system, I need to establish a clean, reliable dataset. Here&rsquo;s my initial roadmap:</p>
<ol>
<li>Get a list of the liked articles from Pocket
<ul>
<li>I can use Pocket API to fetch the data and store it in a sqlite db or a csv file.</li>
</ul>
</li>
<li>Extract and clean up the text content from liked articles
<ul>
<li>Using content extraction tools like <a href="https://jina.ai/reader/" target="_blank" >r.jina.ai</a>
 or <a href="https://github.com/mozilla/readability" target="_blank" >readability</a>
, which strip away advertisements and formatting while preserving the core article text.</li>
</ul>
</li>
<li>Summarize the text content into a few paragraphs using a large language model
<ul>
<li>The articles I&rsquo;ve saved on Pocket range from a few short paragraphs to tens of pages. The corpus needs to be normalized somehow, and I can use Gemini flash or a local LLM to summarize each content in 2-5 paragraphs.</li>
<li>The extracted texts and summaries will need to be inspected and cleaned up, as I anticipate some URLs returning 404 or blocking my scraper.</li>
</ul>
</li>
<li>Generate embeddings for each content summary using an embeddings API (Gemini or OpenAI) or a <a href="https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/" target="_blank" >local embedding model with sentence-transformers</a>
</li>
<li>Store the embeddings for comparison with new content
<ul>
<li>Probably in a sqlite db using <a href="https://github.com/asg017/sqlite-vec" target="_blank" >sqlite-vec</a>
</li>
</ul>
</li>
<li>Build the content recommendation system
<ul>
<li>Honestly I&rsquo;m not quite sure how this will be done yet.</li>
</ul>
</li>
<li>Check for new content from desired sources every day
<ul>
<li>Using GitHub Actions or some other sort of cron job.</li>
</ul>
</li>
<li>Extract the text, summarize, and generate embeddings for the new content</li>
<li>Find the ones that are close to my interests and add them to an RSS feed</li>
</ol>
<p>Admittedly, the later stages of building the recommendation system are unclear, but that’s exactly why I’m documenting this process. The immediate focus is clear: preparing a clean, reliable dataset. Meanwhile, I&rsquo;ll be diving deeper into recommendation system architectures to prepare for the more complex challenges ahead.</p>
<h2 id="open-questions">Open questions</h2>
<p>Thinking about the challenges ahead, I&rsquo;m faced with some questions I currently don&rsquo;t have answers to. If you can provide any guidance or feedback, feel free to reach out via <a href="me@saeedesmaili.com" >email</a>
, <a href="https://bsky.app/profile/saeedesmaili.com" target="_blank" >bsky</a>
, or <a href="https://twitter.com/saeedesmaili" target="_blank" >twitter</a>
.</p>
<ul>
<li>How do I build the content recommendation system?! No, but seriously, how do we compare the embeddings of liked content to those of new, unknown content?
<ul>
<li>Calculate the similarity score (cosine similarity) of the average of the liked content with the new content? But I have a diverse set of liked content in the dataset.</li>
<li>Should I cluster the liked content into categories and figure out if the new content is close enough to one of the clusters?</li>
<li>What if a new content is close to a cluster (e.g. <code>Photography</code>) but still not relevant to my interests at all (e.g. I read photography stuff only if they are related to street or landscape photography, or about the Sony camera I own)?</li>
</ul>
</li>
</ul>
<p>Read the part two of this blog series: <a href="https://saeedesmaili.com/posts/building-a-personal-content-recommendation-system-data-processing-and-cleaning/" target="_blank" >Data Processing and Cleaning</a>
</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Ten Favorite Photos I Captured in 2024</title>
      <link>https://saeedesmaili.com/posts/ten-favorite-photos-i-captured-in-2024/</link>
      <pubDate>Sun, 22 Dec 2024 11:02:38 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/ten-favorite-photos-i-captured-in-2024/</guid>
      <description>&lt;p&gt;I purchased a Sony a7ii in 2023, while having zero understanding of the basics of cameras and photography. I recall being particularly confused about the &lt;a href=&#34;https://petapixel.com/exposure-triangle/&#34; target=&#34;_blank&#34; &gt;exposure triangle&lt;/a&gt;
, especially how aperture affects the sharpness and the depth of field of an image. After learning the basics and taking around 3500 photos with that camera, I realized its aging technology was limiting my beginner skills. I had enough experience to be able to research camera models and their features, and I decided to get a Sony a7cii at the beginning of 2024, a decision I&amp;rsquo;m still happy with today.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I purchased a Sony a7ii in 2023, while having zero understanding of the basics of cameras and photography. I recall being particularly confused about the <a href="https://petapixel.com/exposure-triangle/" target="_blank" >exposure triangle</a>
, especially how aperture affects the sharpness and the depth of field of an image. After learning the basics and taking around 3500 photos with that camera, I realized its aging technology was limiting my beginner skills. I had enough experience to be able to research camera models and their features, and I decided to get a Sony a7cii at the beginning of 2024, a decision I&rsquo;m still happy with today.</p>
<p>As I&rsquo;m drafting this post, my camera&rsquo;s shutter count is at 6663, and if I include the period of the year I was still using my old camera, I&rsquo;ve shot 7719 photos in 2024. I&rsquo;ve had many short trips in the Netherlands, and I&rsquo;ve also traveled to Norway, Sweden, Portugal, Belgium, and Turkey, with my camera being my constant companion. When reviewing the photos I took this year, I felt confident enough to select my top 10 favorites to share in this blog. So here they are, ordered by the date taken.</p>
<h2 id="january-2024">January 2024</h2>
<p>At the beginning of the year I came across a photography contest called <a href="https://www.life-framer.com/" target="_blank" >life framer</a>
, and decided to pay for the annual membership and join the contest. I knew I was nowhere close enough to win a photography contest and winning wasn&rsquo;t the point, but the monthly theme of the contest sounded like an appealing challenge to me. It would motivate me to take my camera outside and push myself to capture new moments.</p>
<p>The January theme of the contest centered on night life photography, and with no travel plans ahead, I walked towards the center of Amsterdam with the only camera I owned back then, the a7ii. After a couple of shots, I realized my camera and lens weren&rsquo;t capable of capturing anything compelling in low light situations, and this probably was the time I decided to think about switching to another camera.</p>
<p>While I was disappointingly watching the area around Pathé City, I noticed this bright advertisement panel of Anya Taylor-Joy in front of the tram stop. Oh thank god, something bright enough my poor camera could capture. I took a few uninspiring shots, and then figured this might be the time to try a long exposure shot for the first time in my photography journey.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241221-amsterdam-1.jpg#center"/> <figcaption>
            <p>#1, Amsterdam NL, Jan 2024</p>
        </figcaption>
</figure>

<h2 id="april-2024">April 2024</h2>
<p>I continued the life framer&rsquo;s monthly challenges in February (theme: animals) and March (theme: colors), but looking back at the shots, none are particularly noteworthy. I got my new a7cii camera in April and immediately loved using it. I was more motivated to carry this smaller, lighter, and more performant camera with me, and soon realized I didn&rsquo;t need an external challenge for taking photos anymore.</p>
<p>On a beautiful spring day in April, I noticed this gorgeous sunset from the balcony of my apartment in Diemen and quickly grabbed the camera to photograph the scene. I was lucky to get some birds in frame.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241221-diemen.jpg#center"/> <figcaption>
            <p>#2, Diemen NL, April 2024</p>
        </figcaption>
</figure>

<h2 id="may-2024">May 2024</h2>
<p>I&rsquo;m quite shy about photographing people in general. I love documenting candid moments of life, but I&rsquo;m always hesitant to point my lens towards strangers. As practice for getting over this fear, I grabbed my small but mighty Sony 50mm f/2.5 G lens, walked towards the center of Amsterdam, aiming to capture street life. It was a cloudy gray day and the shots lacked color or light contrast, so for the first time I decided to edit the photos in black and white to magnify the attention on the subjects.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241221-amsterdam-2.jpg#center"/> <figcaption>
            <p>#3, Amsterdam NL, May 2024</p>
        </figcaption>
</figure>

<h2 id="june-2024">June 2024</h2>
<p>We visited Madeira in June and stayed in Porto Moniz for a week. I wasn&rsquo;t prepared for the island&rsquo;s breathtaking beauty, and Madeira definitely is one of the places I&rsquo;ll have to visit more in future. I was hiking a steep uphill route around Porto Moniz, physically exhausted from climbing, turned around to view the landscape behind me, and faced this gorgeous vista.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241221-madeira-1.jpg#center"/> <figcaption>
            <p>#4, Madeira PT, June 2024</p>
        </figcaption>
</figure>

<p>We also had a short visit to Seixal and I took many photos, including this spectacular view from the balcony of a small restaurant where we grabbed lunch.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-madeira-2.jpg#center"/> <figcaption>
            <p>#4 bonus, Madeira PT, June 2024</p>
        </figcaption>
</figure>

<p>I like that I&rsquo;ve included some foreground elements in both of these pictures. They&rsquo;ve elevated the images by providing a nice perspective of the scene, helping the viewer imagine themselves in those places. I&rsquo;ve found myself drawn to this technique throughout the year.</p>
<h2 id="july-2024">July 2024</h2>
<p>To try something completely new, I joined the <a href="https://dutch-shooters.nl/" target="_blank" >Dutch Shooters</a>
 team in July to have a daylong photography trip to the Formula 1 event at Spa-Francorchamps in Belgium. I stayed at a hotel in Eindhoven the night before so I could join them early in the morning. The photography trip was excellent, with many professional and amateur photographers around the circuit. It was the perfect opportunity to practice panning shots and I managed to capture a few images of these super fast cars.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-f1.jpg#center"/> <figcaption>
            <p>bonus, Spa-Francorchamps BE, July 2024</p>
        </figcaption>
</figure>

<h2 id="august-2024">August 2024</h2>
<p>We visited Norway in August, spending the first few days exploring Oslo. I found the <a href="https://en.wikipedia.org/wiki/Oslo_Public_Library" target="_blank" >Oslo Public Library</a>
 to be a fascinating place to shoot, as it contains various types of shapes, lights, colors, people, and even some reflections.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-norway-1.jpg#center"/> <figcaption>
            <p>#5, Oslo NO, August 2024</p>
        </figcaption>
</figure>

<p>There are many of these old phone booths around Oslo converted into small libraries. I came across several while walking around and exploring the city, including this one. I took a few ordinary shots, none of them meeting my satisfaction threshold. Then I saw a gentleman approaching with books in his hands, and I captured this moment. I love how the color of his shirt matches the phone booth.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-norway-2.jpg#center"/> <figcaption>
            <p>#5 bonus, Oslo NO, August 2024</p>
        </figcaption>
</figure>

<p>We left Oslo and stayed in a cabin house around Halden for a few days. The cabin was surrounded by woodland and water, offering an absolutely peaceful experience of nature.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-norway-3.jpg#center"/> <figcaption>
            <p>#5 bonus, Halden NO, August 2024</p>
        </figcaption>
</figure>

<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-norway-4.jpg#center"/> <figcaption>
            <p>#5 bonus, Halden NO, August 2024</p>
        </figcaption>
</figure>

<h2 id="september-2024">September 2024</h2>
<p>Of course I had my camera with me during a work trip to Stockholm in September. One night when I finally found a few hours of alone time, I walked around the city to photograph some night life and got several memorable shots. Reflections always intrigue me, and here I found its combination with the empty section of the advertisement created a perfect frame to capture someone walking by. Several people passed, and this man&rsquo;s height, sweater color, and headphones made for the best composition of the evening.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-sweden-1.jpg#center"/> <figcaption>
            <p>#6, Stockholm SE, September 2024</p>
        </figcaption>
</figure>

<p>It&rsquo;s always challenging to find and compose shots at night, especially with an f/2.8 lens, as there&rsquo;s not enough light reaching the lens to work with. My attention was drawn to the colors and the light in front of this store, and I was fortunate to catch someone crossing, illuminated by the store&rsquo;s lighting.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-sweden-2.jpg#center"/> <figcaption>
            <p>#6 bonus, Stockholm SE, September 2024</p>
        </figcaption>
</figure>

<h2 id="october-2024">October 2024</h2>
<p>Autumn was quickly fading, and I desperately wanted to have a colorful autumn photo in my 2024 portfolio. I brought my camera to tennis practice, so I could go shooting afterward without needing to return home. I photographed some autumn leaves, but nothing spectacular. I lost the daylight and the theme of the day became night life photography.</p>
<p>I walked toward the center of Amsterdam and took many unremarkable shots. On my way to the city center, I came across these green lights around the building of the <a href="https://en.wikipedia.org/wiki/Van_Gogh_Museum" target="_blank" >Van Gogh Museum</a>
. I started photographing the empty but lit walls of the building, and waited for people to pass so I could potentially get a minimalistic silhouette shot, but I ended up with something even more beautiful.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-amsterdam-3.jpg#center"/> <figcaption>
            <p>#7, Amsterdam NL, October 2024</p>
        </figcaption>
</figure>

<p>Since my previous attempt at getting an autumn shot was unsuccessful, I grabbed the camera another day and explored the <a href="https://en.wikipedia.org/wiki/Amsterdamse_Bos" target="_blank" >Amsterdamse Bos</a>
 seeking the yellow and orange colors of nature. I took many shots in the following spot, where the colors on the trees in the background were stunning. I was quite pleased with this image of the family and their dog enjoying their time.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-amsterdam-4.jpg#center"/> <figcaption>
            <p>#8, Amsterdam NL, October 2024</p>
        </figcaption>
</figure>

<h2 id="november-2024">November 2024</h2>
<p>I traveled to Izmir to spend a few days with a friend. I always keep my camera bag on my lap in airplanes, in case an interesting scene appears. This time when the airplane was approaching the Istanbul airport, I captured this beautiful night scene of this megacity, with boats sailing in the water. I almost lost my lens cap while taking this shot, since the airplane landed a few moments after, and I lost control of the items on my lap. Luckily I found it lying on the ground later.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-istanbul.jpg#center"/> <figcaption>
            <p>#9, Istanbul TR, November 2024</p>
        </figcaption>
</figure>

<p>My friend and I stayed in Manisa, a small town near Izmir. We drove to Izmir daily, explored the city, played tennis, and took photos. The first two days, I was amazed by the layers of mountains in the background along the road, but I was driving and there was no safe place to stop for photography. The third day I couldn&rsquo;t resist anymore, so I stopped at a gas station, and quickly took this shot. I&rsquo;m glad that an old red car appeared in the frame as well, adding more visual interest.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-izmir-1.jpg#center"/> <figcaption>
            <p>#10, Izmir TR, November 2024</p>
        </figcaption>
</figure>

<p>I continued practicing street photography and taking photos of strangers in Turkey, hoping to gradually overcome this fear. A few times people noticed I was photographing them, and I worried they&rsquo;d be angry or at least complain, but to my surprise they asked for my Instagram account and requested me to take more photos and share them!</p>
<p>During one of our walks around Izmir, I suggested my friend visit the city&rsquo;s old <a href="https://en.wikipedia.org/wiki/Kemeralt%C4%B1" target="_blank" >Kemeraltı bazaar</a>
 hoping I could get some unique and colorful shots of the chaotic environment, but I couldn&rsquo;t capture anything compelling. When we left the bazaar, people were seated around the Clock Tower of Izmir, and I noticed this lady with a box of red roses in her hand. I waited to see if an interesting moment would unfold, and then took this shot when she handed a rose to another lady, with a bright smile on her face.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20241222-izmir-2.jpg#center"/> <figcaption>
            <p>#10 bonus, Izmir TR, November 2024</p>
        </figcaption>
</figure>

<p>Looking back at these photos, I can see how my journey in photography has evolved throughout 2024. From my hesitant first attempts at long exposures in January to confidently approaching street photography in Turkey, each image represents not just a moment captured, but a step in my growth as a photographer.</p>
<p>I recently purchased a photo printer, and these ten images will be among the first I&rsquo;ll print. There&rsquo;s something special about seeing photographs on paper rather than just on screens, and I&rsquo;m excited to experience these memories in a more tangible form. As I look forward to 2025, I&rsquo;m eager to continue exploring and developing my photographic style, particularly in areas that still challenge me, like street photography.</p>
<p>I&rsquo;d love to hear which of these photos resonates with you and why. Whether you&rsquo;re a fellow photography enthusiast or simply enjoy viewing images, feel free to <a href="mailto:me@saeedesmaili.com" >reach out</a>
 to share your thoughts or discuss photography. You can follow my photography work on <a href="https://www.instagram.com/saeed.esmaili/" target="_blank" >Instagram</a>
, <a href="https://500px.com/p/saeedesmaili?view=photos" target="_blank" >500px</a>
, or <a href="https://www.flickr.com/photos/saeedesmaili/" target="_blank" >flickr</a>
.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Understanding Input Masking in LLM Finetuning</title>
      <link>https://saeedesmaili.com/posts/understanding-input-masking-in-llm-finetuning/</link>
      <pubDate>Sat, 29 Jun 2024 11:31:39 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/understanding-input-masking-in-llm-finetuning/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been using &lt;a href=&#34;https://docs.axolotl.ai/docs/dataset-formats/conversation.html&#34; target=&#34;_blank&#34; &gt;conversational alpaca or sharegpt formats&lt;/a&gt;
 for fine-tuning LLMs with &lt;a href=&#34;https://docs.axolotl.ai/&#34; target=&#34;_blank&#34; &gt;Axolotl&lt;/a&gt;
, but it always felt unnecessary to limit the model on a conversational format when the use-case doesn&amp;rsquo;t require so.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m currently working on a project to classify pull requests in my company&amp;rsquo;s code repositories. The model needs to look at the PR title, description, and code changes, then categorize the PR and explain its reasoning. I thought there must be a way to fine-tune these models with any format I see fitting this specific use-case, and sure there is: &lt;a href=&#34;https://hamel.dev/notes/llm/finetuning/09_template_free.html&#34; target=&#34;_blank&#34; &gt;Template-free Axolotl&lt;/a&gt;
&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I&rsquo;ve been using <a href="https://docs.axolotl.ai/docs/dataset-formats/conversation.html" target="_blank" >conversational alpaca or sharegpt formats</a>
 for fine-tuning LLMs with <a href="https://docs.axolotl.ai/" target="_blank" >Axolotl</a>
, but it always felt unnecessary to limit the model on a conversational format when the use-case doesn&rsquo;t require so.</p>
<p>I&rsquo;m currently working on a project to classify pull requests in my company&rsquo;s code repositories. The model needs to look at the PR title, description, and code changes, then categorize the PR and explain its reasoning. I thought there must be a way to fine-tune these models with any format I see fitting this specific use-case, and sure there is: <a href="https://hamel.dev/notes/llm/finetuning/09_template_free.html" target="_blank" >Template-free Axolotl</a>
</p>
<p>This seemed exactly what I was looking for, but the emphais on &ldquo;masking inputs&rdquo; made me confused:</p>
<blockquote>
<p>If you declare a dataset format such as <code>alpaca</code> or <code>chatml</code>, axolotl knows what is an input (i.e. human) vs. an output (i.e. the assistant) and masks the input labels so that your model can focus on predicting the outputs only. You can construct your prompts without a template by using the <code>input_output</code> format, by setting <code>type: input_output</code> in your configuration file. Unlike <code>type: completion</code>, which is also template-free, <code>type: input_output</code> allows you to mask segments of your text.</p></blockquote>
<p>This was the first time I came across the concept of input masking. It felt counterintuitive at first - why would we want our model to ignore parts of the training data? Isn&rsquo;t all of it important? Wouldn&rsquo;t masking the input prevent the model from learning to respond properly when encountering similar cases during inference time? After going deeper in the Axolotl docs and codes, I&rsquo;ve learned some stuff I&rsquo;d like to share.</p>
<h2 id="what-is-input-masking">What is input masking?</h2>
<p>Input masking is a technique where we intentionally hide parts of the input data from the model during training. It&rsquo;s like covering up certain words in a sentence and asking the model to focus on predicting only the uncovered parts. But why should we do this? There are several facts we need to know:</p>
<ul>
<li>
<p><strong>Distinction between input and output</strong>: In many fine-tuning scenarios, particularly for instruction-following or question-answering tasks, we want the model to learn to generate appropriate responses (outputs) given certain prompts or questions (inputs). By masking the input, we&rsquo;re emphasizing that the model should focus on generating the correct output rather than simply memorizing and repeating the input.</p>
</li>
<li>
<p><strong>Preventing overfitting to prompt templates</strong>: If we train the model on both the input and output, it might start to rely too heavily on specific phrasings or templates in the input. By masking the input, we encourage the model to be more flexible and generalize better to variations in how questions or instructions might be phrased.</p>
</li>
<li>
<p><strong>Focusing on task completion</strong>: The goal is often to have the model learn to perform a task or generate relevant information, not to reproduce the exact input format. Masking helps direct the model&rsquo;s attention to the core task.</p>
</li>
<li>
<p><strong>Efficiency in fine-tuning</strong>: By not training on inputs, we can potentially reduce the amount of computation needed during fine-tuning, as the model doesn&rsquo;t need to adjust its parameters to predict the input tokens.</p>
</li>
<li>
<p><strong>Handling different prompt structures</strong>: This allows for more flexibility in how prompts are structured in our training data, as the model isn&rsquo;t learning to reproduce specific prompt formats.</p>
</li>
</ul>
<h2 id="to-mask-or-not-to-mask">To mask or not to mask</h2>
<p>Based on the above facts, there are scenarios that we might want to use input masking when fine-tuning a language model:</p>
<ul>
<li>Chatbots and conversational AI</li>
<li>Question-answering models</li>
<li>Instruction-following tasks</li>
</ul>
<p>In these cases, we care more about the model generating good responses than repeating exact input phrasings. However, masking isn&rsquo;t always the best choice. We might want to avoid it when:</p>
<ul>
<li>Every detail in the input is crucial for the task</li>
<li>The model needs to understand and reference specific parts of the input</li>
<li>We&rsquo;re working with specialized or technical content where context is key</li>
</ul>
<h2 id="my-use-case-classifying-pull-requests">My use case: classifying pull requests</h2>
<p>As I mentioned previously:</p>
<blockquote>
<p>I&rsquo;m currently working on a project to classify pull requests (PRs) in my company&rsquo;s code repositories. The model needs to look at the PR title, description, and code changes, then categorize the PR and explain its reasoning.</p></blockquote>
<p>For this task, I&rsquo;ve decided not to use input masking. Here&rsquo;s why:</p>
<ul>
<li>
<p><strong>Importance of input details</strong>: In PR classification, every detail in the title, description, and code changes could be crucial for making an accurate classification. By training on the full input, we allow the model to learn important patterns and correlations between specific input features and the resulting classifications.</p>
</li>
<li>
<p><strong>Contextual understanding</strong>: PRs often contain technical jargon, project-specific terms, or code snippets that are important for understanding the nature of the changes. Training on these inputs helps the model build a better contextual understanding of my company&rsquo;s development practices and codebase.</p>
</li>
<li>
<p><strong>Reasoning requirement</strong>: Since my task includes providing reasoning for the classification, the model needs to learn how to reference and analyze specific parts of the input. This is more effectively achieved if the model is trained to process and potentially reproduce relevant parts of the input.</p>
</li>
<li>
<p><strong>Limited output structure</strong>: Unlike more open-ended generation tasks, my output has a specific structure (JSON with classification and reasoning). The risk of the model simply copying large portions of the input into the output is lower, given this constrained format.</p>
</li>
<li>
<p><strong>Potential for implicit feature extraction</strong>: By training on the full input, the model might learn to identify subtle features or patterns in PR descriptions or code changes that are indicative of certain classifications. This implicit feature extraction could lead to more accurate classifications.</p>
</li>
<li>
<p><strong>Handling variations in input</strong>: PRs can vary greatly in length, structure, and content. Training on full inputs helps the model handle this variability more effectively.</p>
</li>
</ul>
<p><a href="mailto:me@saeedesmaili.com" >Let me know</a>
 if I have missed anything or if you know a good learning resource on this topic.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Running Python on a serverless GPU instance for machine learning inference</title>
      <link>https://saeedesmaili.com/posts/running-python-on-a-serverless-gpu-instance/</link>
      <pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/posts/running-python-on-a-serverless-gpu-instance/</guid>
      <description>&lt;p&gt;I was experimenting with some speech-to-text work using &lt;a href=&#34;https://saeedesmaili.com/exploring-openai-whisper-with-non-english-voices/&#34; &gt;OpenAI&amp;rsquo;s Whisper models&lt;/a&gt;
 today, and transcribing a 15-minute audio file with Whisper &lt;code&gt;tiny&lt;/code&gt; model on AWS Lambda (3 vcpu) took 120 seconds. I was curious how faster this could be if I ran the same transcription model on a GPU instance, and with a quick search, &lt;a href=&#34;https://modal.com&#34; target=&#34;_blank&#34; &gt;modal.com&lt;/a&gt;
 seemed like a nice option to spin up a GPU machine, run the code, and shut down the machine, similar to how AWS Lambda works.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I was experimenting with some speech-to-text work using <a href="/exploring-openai-whisper-with-non-english-voices/" >OpenAI&rsquo;s Whisper models</a>
 today, and transcribing a 15-minute audio file with Whisper <code>tiny</code> model on AWS Lambda (3 vcpu) took 120 seconds. I was curious how faster this could be if I ran the same transcription model on a GPU instance, and with a quick search, <a href="https://modal.com" target="_blank" >modal.com</a>
 seemed like a nice option to spin up a GPU machine, run the code, and shut down the machine, similar to how AWS Lambda works.</p>
<p>The cheapest GPU instance on modal.com is Nvidia T4 which costs $0.000164 per second and uses AWS infrastructure behind the scenes. The free starter plan on modal.com includes $30 credit, enough to experiment and try their services. Although the <a href="https://modal.com/docs/reference" target="_blank" >documentation</a>
 is good enough, it took me some time to figure out how to quickly run my code on a T4 GPU without worrying about docker images, so here is a quick guide on how to run your Python code on GPU instances on modal.com from scratch.</p>
<h2 id="preparation">Preparation</h2>
<p>After creating an account on <a href="https://modal.com/" target="_blank" >modal.com</a>
, install their Python library:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install modal
</span></span></code></pre></div><p>and run the <code>setup</code> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>modal setup
</span></span><span style="display:flex;"><span><span style="color:#75715e">## or: python3 -m modal setup</span>
</span></span></code></pre></div><p>This will open a web browser and if you&rsquo;re logged in to your modal account, it will automatically generate a token and save it on your device.</p>
<h2 id="our-code">Our code</h2>
<p>You&rsquo;ll first need to define the environment in which your code will be running. Start <code>my_code.py</code> with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> modal
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>image <span style="color:#f92672">=</span> modal<span style="color:#f92672">.</span>Image<span style="color:#f92672">.</span>debian_slim()
</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> modal<span style="color:#f92672">.</span>App(image<span style="color:#f92672">=</span>image)
</span></span></code></pre></div><p>Then include what you want to run remotely (on a GPU instance in this case) and wrap it with <code>@app.function</code> decorator:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@app.function</span>(gpu<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;t4&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">square</span>(x):
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;This code is running on a remote worker!&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> x<span style="color:#f92672">**</span><span style="color:#ae81ff">2</span>
</span></span></code></pre></div><p>And finally, define an entry point function to call from your local machine when triggering the remote instance:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@app.local_entrypoint</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;the square is&#34;</span>, square<span style="color:#f92672">.</span>remote(<span style="color:#ae81ff">42</span>))
</span></span></code></pre></div><p>So the final state of <code>my_code.py</code> should be:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> modal
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>image <span style="color:#f92672">=</span> modal<span style="color:#f92672">.</span>Image<span style="color:#f92672">.</span>debian_slim()
</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> modal<span style="color:#f92672">.</span>App(image<span style="color:#f92672">=</span>image)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.function</span>(gpu<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;t4&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">run_remote</span>(x):
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;This code is running on a remote worker!&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> x<span style="color:#f92672">**</span><span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.local_entrypoint</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;the square is&#34;</span>, run_remote<span style="color:#f92672">.</span>remote(<span style="color:#ae81ff">42</span>))
</span></span></code></pre></div><p>Note that <code>t4</code> is the cheapest GPU available, and you can use <a href="https://modal.com/docs/reference/modal.gpu" target="_blank" >other GPU instances</a>
 if you&rsquo;d like. To run this dummy <code>run_remote</code> function on a GPU instance, trigger it with the following command on your local terminal:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>modal run my_code.py
</span></span><span style="display:flex;"><span><span style="color:#75715e">## or python3 -m modal run my_code.py</span>
</span></span></code></pre></div><p><img alt="result of running the first gpu instance" loading="lazy" src="/images/2024/20240422-modal-com-gpu-1.png"></p>
<p>Nice! We were able to run a simple function on GPU. You can view the logs by clicking on the URL in the terminal (<code>https://modal.com/&lt;your-username&gt;/apps/ap-&lt;blah-blah-blah&gt;</code>) and monitor your credit usage on <a href="https://modal.com/settings/saeedesmaili/usage" target="_blank" >your billing page</a>
.</p>
<h2 id="more-serious-code">More serious code</h2>
<p>The above sample code can easily be found on the modal&rsquo;s documentation, but stuff gets more complicated if you want to do more serious work. Getting back to my text-to-speech code, I had to add a few steps:</p>
<ul>
<li>Include a local audio file (what I want to transcribe) from my device.</li>
<li>Install Python libraries (<code>openai-whisper</code> and <code>faster-whisper</code>).</li>
<li>Install <code>ffmpeg</code> (it&rsquo;s required for <code>openai-whisper</code> to work).</li>
</ul>
<p>Let&rsquo;s start by refining our image and environment:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>image <span style="color:#f92672">=</span> (
</span></span><span style="display:flex;"><span>    modal<span style="color:#f92672">.</span>Image<span style="color:#f92672">.</span>debian_slim()
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>apt_install(<span style="color:#e6db74">&#34;ffmpeg&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>pip_install(<span style="color:#e6db74">&#34;openai-whisper&#34;</span>, <span style="color:#e6db74">&#34;faster-whisper&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>copy_local_file(
</span></span><span style="display:flex;"><span>        local_path<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/&lt;path-to-our-local-file&gt;/&lt;filename&gt;.m4a&#34;</span>,
</span></span><span style="display:flex;"><span>        remote_path<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;./tmp/&#34;</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> modal<span style="color:#f92672">.</span>App(image<span style="color:#f92672">=</span>image)
</span></span></code></pre></div><p>And update our <code>run_remote</code> function with what we&rsquo;ll need for transcribing an audio file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@app.function</span>(gpu<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;t4&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">run_remote</span>():
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;This code is running on a remote worker!&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">import</span> whisper
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">from</span> faster_whisper <span style="color:#f92672">import</span> WhisperModel
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    file_path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/tmp/&lt;filename&gt;.m4a&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    whisper_model <span style="color:#f92672">=</span> whisper<span style="color:#f92672">.</span>load_model(<span style="color:#e6db74">&#34;tiny.en&#34;</span>)
</span></span><span style="display:flex;"><span>    t1 <span style="color:#f92672">=</span> time<span style="color:#f92672">.</span>perf_counter()
</span></span><span style="display:flex;"><span>    result <span style="color:#f92672">=</span> whisper_model<span style="color:#f92672">.</span>transcribe(file_path)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;time passed with openai-whisper: &#34;</span>, time<span style="color:#f92672">.</span>perf_counter() <span style="color:#f92672">-</span> t1)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    model <span style="color:#f92672">=</span> WhisperModel(<span style="color:#e6db74">&#34;tiny.en&#34;</span>, device<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;cuda&#34;</span>, compute_type<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;float16&#34;</span>)
</span></span><span style="display:flex;"><span>    t1 <span style="color:#f92672">=</span> time<span style="color:#f92672">.</span>perf_counter()
</span></span><span style="display:flex;"><span>    segments, info <span style="color:#f92672">=</span> model<span style="color:#f92672">.</span>transcribe(file_path, beam_size<span style="color:#f92672">=</span><span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">## note: `segments` is a generator, so we&#39;ll need to iterate over it to</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">##       be able to get the transcription.</span>
</span></span><span style="display:flex;"><span>    segments_result <span style="color:#f92672">=</span> list(segments)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;time passed with faster-whisper: &#34;</span>, time<span style="color:#f92672">.</span>perf_counter() <span style="color:#f92672">-</span> t1)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.local_entrypoint</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    run_remote<span style="color:#f92672">.</span>remote()
</span></span></code></pre></div><p>Note that we need to include the <code>import</code>s inside our <code>run_remote</code> function, as that&rsquo;s what actually will be getting executed on the remote instance.</p>
<p>This is all we need, and by running the <code>modal run my_code.py</code> command and waiting for our audio to get transcribed (twice in this case), we should get the following result (of course, the actual values of <code>time passed</code> will be different depending on what GPU instance you use and the length of the file):</p>
<p><img alt="final result of running the transcription code on a gpu instance" loading="lazy" src="/images/2024/20240422-modal-com-gpu-2.png"></p>
<p>We can see that by running this transcription process on GPU, we reduced the time it takes to do the job from 120 seconds to 31 and 14 seconds. A very nice achievement, but we should take the important factor of pricing into account as well (maybe in a future post).</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>If you&rsquo;re getting the following error when running <code>faster-whisper</code> (or any other code) with <code>cuda</code>:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 1104 25"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='456' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='464' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='472' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='480' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='488' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='504' y='4' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='512' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='520' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='528' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='536' y='4' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='544' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='552' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='560' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='568' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='576' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='584' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='592' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='600' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='608' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='616' y='4' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='624' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='640' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='648' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='656' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='664' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='672' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='680' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='696' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='704' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='712' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='720' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='736' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='744' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='752' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='760' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='768' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='776' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='792' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='800' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='808' y='4' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='816' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='824' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='832' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='848' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='856' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='864' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='872' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='880' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='896' y='4' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='904' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='920' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='928' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='936' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='944' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='960' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='968' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='976' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='984' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1000' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1008' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1024' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='1032' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='1040' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1048' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='1056' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='1064' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='1072' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='1080' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='1088' y='4' fill='currentColor' style='font-size:1em'>y</text>
</g>

    </svg>
  
</div>
<p>you can fix it by explicitly setting the path of the <code>cuda</code> installations in your image definition using <code>env</code> (took me some time to figure out this fix):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>image <span style="color:#f92672">=</span> (
</span></span><span style="display:flex;"><span>    modal<span style="color:#f92672">.</span>Image<span style="color:#f92672">.</span>debian_slim(python_version<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;3.11&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>env(
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;LD_LIBRARY_PATH&#34;</span>: <span style="color:#e6db74">&#34;/usr/local/lib/python3.11/site-packages/nvidia/cublas/lib:/usr/local/lib/python3.11/site-packages/nvidia/cudnn/lib&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>apt_install(<span style="color:#e6db74">&#34;ffmpeg&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>pip_install(<span style="color:#e6db74">&#34;openai-whisper&#34;</span>, <span style="color:#e6db74">&#34;faster-whisper&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">.</span>copy_local_file(
</span></span><span style="display:flex;"><span>        local_path<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/&lt;path-to-our-local-file&gt;/&lt;filename&gt;.m4a&#34;</span>,
</span></span><span style="display:flex;"><span>        remote_path<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;./tmp/&#34;</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> modal<span style="color:#f92672">.</span>App(image<span style="color:#f92672">=</span>image)
</span></span></code></pre></div><h2 id="using-jupyter-notebook">Using <code>Jupyter</code> notebook</h2>
<p>If you&rsquo;d like to have a quick exploration using a <code>jupyter</code> notebook, you can run the following command to spin up a GPU instance and experiment with your code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>modal launch jupyter --gpu t4
</span></span></code></pre></div><p>Note that you&rsquo;ll be using your credit for the whole amount of time that this <code>jupyter</code> instance is up, even if you don&rsquo;t run any code. Make sure to stop it by terminating the command on your terminal.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>My RSS-based Content Consumption Workflow</title>
      <link>https://saeedesmaili.com/posts/my-content-consumption-workflow/</link>
      <pubDate>Thu, 22 Feb 2024 20:25:49 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/my-content-consumption-workflow/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;: Here is a simplified diagram of how I keep reading articles, books, and other materials without overwhelming myself or having a never-ending pile of to-read items. In this blog post, I will go into the details of each part of the workflow, the tools I currently use, the alternatives I have tried, why they work for me, and what I can improve in the process. Feel free to use the above table of contents to jump to the section that is more interesting to you.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p><strong>TLDR</strong>: Here is a simplified diagram of how I keep reading articles, books, and other materials without overwhelming myself or having a never-ending pile of to-read items. In this blog post, I will go into the details of each part of the workflow, the tools I currently use, the alternatives I have tried, why they work for me, and what I can improve in the process. Feel free to use the above table of contents to jump to the section that is more interesting to you.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20240222-overall-workflow.png#center"/> <figcaption>
            <p>My Overall Workflow</p>
        </figcaption>
</figure>

<h2 id="why">Why</h2>
<p>I used to visit Twitter frequently during the day, sometimes 2-3 hours each day, to find out what&rsquo;s happening in the world and what cool stuff some cool people are talking about. But over time I realized I was a Twitter addict that consumes hours of trash content on the platform without any meaningful benefits. Twitter&rsquo;s algorithm promotes engagement seekers and influencers over actually useful content, and it&rsquo;s just been worse with Elon. I needed to spend my precious time on more useful and rewarding stuff.</p>
<p>I was also a content hoarder, saving anything I found mildly interesting in Pocket or in the browser bookmarks, and I rarely read anything from the pile of the saved items in my read-it-later apps. I had accumulated more than 1000 articles in my Pocket account over the years and had hundreds of bookmarks on Firefox and Chrome. This content clutter was very depressing to me, as I didn&rsquo;t see myself benefiting from anything that I came across.</p>
<p>Lastly, the fact that I was leaving my content consumption inputs to the hands of Twitter, Google, and other algorithms or chance wasn&rsquo;t appealing to me. I wanted to take over control of what I read, at what pace, and from whom.</p>
<h2 id="rss-feeds--other-content-inputs">RSS feeds &amp; other content inputs</h2>
<p>I have tried a few RSS feed reader apps like Feedly and Readwise Reader in the past, but I have currently settled down on using Inoreader as my RSS reader app.</p>
<h3 id="inoreader-pros">Inoreader: Pros</h3>
<p>Inoreader has many nice functionalities, but for me, the most important features are:</p>
<ul>
<li>Very smooth experience between web, android, and iOS apps (I&rsquo;m mentioning this first, as many other apps I&rsquo;ve tried are flaky)</li>
<li>Mark as read while scrolling (Very important for quickly shortlisting items from the feed. This is probably the main reason I&rsquo;ve been able to replace Inoreader with social media apps.)</li>
<li>Rules to auto-delete duplicated items or if the title contains specific words</li>
<li>Its Firefox and Chrome extensions and Android and iOS apps can easily and quickly find RSS feeds of websites</li>
<li>Subscribe to newsletters (max 20 newsletters, that&rsquo;s why I don&rsquo;t use this feature anymore. I use kill-the-newsletter instead)</li>
<li>Feed generators for websites that don&rsquo;t have an RSS feed (the max 20 feed generations limit made me start using RSS-Bridge instead of this feature)</li>
<li>Customization of sharing options (so I&rsquo;ve Pocket the first option and it allows me to quickly share an item into my Pocket account if needed)</li>
</ul>
<p>I&rsquo;ve customized Inoreader&rsquo;s app to make it more pleasing to my taste:</p>
<ul>
<li>Changed the feed layout to list, to get rid of the article thumbnails.</li>
<li>Sorted the lists chronologically, and &ldquo;oldest first&rdquo;. Firstly, I don&rsquo;t want Inoreader&rsquo;s algorithm to decide what is worth my attention. Second, by reading the oldest first, I make sure that nothing gets buried and forgotten at the end of the list, and also when I view an item in the list, the hype dust around it, whatever it is has settled a bit (note that I&rsquo;m usually reading items from 5-6 days ago, so this is not true about any kind of hype).</li>
<li>Disabled the &ldquo;popularity indicator&rdquo;, to have fewer distractions and be focused on the titles.</li>
<li>Changed the content font to a serif one (just a personal preference for reading content).</li>
</ul>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20240222-inoreader.jpg#center" width="300"/> <figcaption>
            <p>My Inoreader Feed</p>
        </figcaption>
</figure>

<h3 id="inoreader-cons">Inoreader: Cons</h3>
<p>Of course, Inoreader is not perfect:</p>
<ul>
<li>They have increased the subscription price many times in the past few years. They&rsquo;ve used to have a Black Friday discount for everyone for years, but they limited the offer to new users last year and it pissed off the existing users and many complained on Reddit.</li>
<li>Inoreader is a cool product, but they&rsquo;ve been focusing on developing new features and capabilities for the enterprise and business customers in the past two years, and they seem to be on a path to <a href="https://en.wikipedia.org/wiki/Enshittification" target="_blank" >enshitification</a>
.</li>
</ul>
<p>I&rsquo;m considering self-hosting a <a href="https://freshrss.org/" target="_blank" >FreshRSS</a>
 in the future and using it with <a href="https://github.com/seazon/FeedMe" target="_blank" >FeedMe</a>
 app if I could make sure it has all the features I need.</p>
<h3 id="rss-feeds-i-follow-using-inoreader">RSS feeds I follow using Inoreader</h3>
<ul>
<li><strong>Blogs and websites</strong>: I started following a handful of blog feeds around 18 months ago. Over time, when I came across an interesting post from a blog or website, I added its feed to Inoreader, and the total is currently 1659 RSS feeds. I recommend you do the same, but you can get an <a href="https://github.com/outcoldman/hackernews-personal-blogs" target="_blank" >OPML list of blog posts from HackerNews users</a>
, or reach out to me if you&rsquo;re interested in my OPML list.</li>
<li><strong>HackerNews and lobste.rs front page</strong>: Thousands of links are submitted to <a href="https://news.ycombinator.com/" target="_blank" >HackerNews</a>
 every day, and you probably don&rsquo;t have time to even scroll through all of them, and many of them are spam or not interesting. But thanks to the power of the community, the ~100 items that reach its front page every day are mostly interesting. The issue with its front page RSS feed is the many duplicated items in the list (I&rsquo;m not sure what exactly happens when a moderator updates an item&rsquo;s title, but many times I see the same item with different titles in my feed reader). Fortunately, I&rsquo;ve been able to remove duplicates automatically using Inoreader&rsquo;s duplication rule (I&rsquo;ve set it to remove any item with the same URL within 24 hours). <a href="https://lobste.rs/" target="_blank" >lobste.rs</a>
 is a similar but smaller community to HackerNews.</li>
<li><strong>Blogs or websites without an RSS feed</strong>: I use <a href="https://rss-bridge.org/" target="_blank" >RSS-Bridge</a>
 (mostly <a href="https://rss-bridge.org/bridge01/#bridge-CssSelectorBridge" target="_blank" >CSS Selector</a>
 and <a href="https://rss-bridge.org/bridge01/#bridge-XPathBridge" target="_blank" >XPathBridge</a>
) to create RSS feeds for blogs and websites that don&rsquo;t provide a feed (If you&rsquo;re writing on the web and don&rsquo;t provide an RSS feed to your readers, please consider adding one). I used to use Inoreader&rsquo;s feed generator, but the Pro plan has a maximum of 20 feed generations and I quickly reached the limit. Honestly, very strange limit on Inoreader&rsquo;s service.</li>
<li><strong>Email newsletters</strong>: I use <a href="https://kill-the-newsletter.com/" target="_blank" >kill-the-newsletter</a>
 to subscribe to email newsletters. It&rsquo;s a free service that provides you a unique email address and a unique RSS feed, you subscribe to the newsletter using that email address, and whenever a new email arrives, it updates your RSS feed. Very nice. I used to use Inoreader&rsquo;s newsletter feature, but the Pro plan has a max 20 newsletter subscription limit (although you can technically use the same email address in multiple newsletters). Some newsletter platforms like Substack, Ghost, and Buttondown provide an RSS feed on the newsletters&rsquo; archive page and I directly use that feed.</li>
</ul>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20240222-rss-inputs.png#center" width="300"/> <figcaption>
            <p>RSS Inputs</p>
        </figcaption>
</figure>

<h3 id="where-i-dont-use-rss">Where I don&rsquo;t use RSS</h3>
<ul>
<li><strong>News</strong>: I don&rsquo;t proactively follow any news publisher, neither general news media like the New York Times, nor tech media like Verge. Anything important will be shared either on HackerNews, or it will reach me somehow via friends and colleagues.</li>
<li><strong>YouTube</strong>: Ideally I should follow the RSS feed of the YouTube channels that I like, but I currently just browse YouTube&rsquo;s home page and if something sounds interesting, I save it into the watch later playlist (or a few other playlists that I&rsquo;ve created). Nothing fancy here, just a typical YouTube user (who sorts his YouTube watch later playlist on &ldquo;oldest first&rdquo;), so won&rsquo;t go into the details of this.</li>
<li><strong>Twitter</strong>: I deleted my Twitter account in 2021 for reasons that I mentioned at the beginning of this post. Last year I realized there are still a handful of interesting people on the platform, so I created <a href="https://twitter.com/saeedesmaili" target="_blank" >a new Twitter account</a>
 and I currently follow 60 users via a Twitter list that I&rsquo;ve created. Whenever I find someone who tweets interesting and useful stuff (like <a href="https://twitter.com/simonw" target="_blank" >@simonw</a>
 or <a href="https://twitter.com/vboykis" target="_blank" >@vicki</a>
) I add them to my list. Fortunately, Elon hasn&rsquo;t destroyed Twitter&rsquo;s list feature yet (although he surely will), so I can browse through the latest tweets from these people in chronological order without any ads. I do this every morning on Twitter&rsquo;s web app, and if there is something very interesting to read later (an article or paper, or a GitHub repo) I use the same shortlist flow as my RSS feeds, which I explain soon. I tried Inoreader&rsquo;s Twitter integration but it stopped working since Elon killed Twitter&rsquo;s API access. I also tried following my list&rsquo;s RSS feed using <a href="https://github.com/zedeus/nitter" target="_blank" >Nitter</a>
, but apparently, that&rsquo;s also going to stop working soon. The experience of reading a handful of tweets in a list on Twitter&rsquo;s web app is generally ok for now.</li>
<li><strong>Podcasts</strong>: Many (or all?) podcasts have RSS feeds, and I can follow them on Inoreader, and I used to do so, but I came to the conclusion that I&rsquo;m not a podcast person. The ratio of the &ldquo;useful information&rdquo; per &ldquo;time spent&rdquo; is extremely low with podcasts for me, so I&rsquo;ve decided to spend my listening time on audiobooks and articles (using Text-to-Speech, more on this later), or if I&rsquo;m not in the mood and I just want to entertain myself while walking or shopping for groceries, I highly prefer listening to music. Therefore, I&rsquo;m following and listening to zero podcasts now.</li>
</ul>
<h2 id="shortlisting">Shortlisting</h2>
<p>So, overall RSS feeds are the main inputs in my workflow, and as I mentioned before, <strong>I&rsquo;m following 1659 RSS feeds</strong> as of today (most of them rarely post new stuff though), which means <strong>I consume between 200 to 300 items in my Inoreader feed on average every day.</strong> Consumption in this step means scrolling through the feed, which I do throughout the day when waiting for my coffee or tea to be ready, waiting in a doctor&rsquo;s office, commuting via bus and metro, and any other place and time that I&rsquo;d go for scrolling social media apps before.</p>
<p>When scrolling my RSS feed reader, if a title grabs my attention, I open the article (or the discussion, if it&rsquo;s a HackerNews page for example), I quickly skim the content (or comments), if interesting, I shortlist the item by either saving it into my Pocket account or the Inoreader&rsquo;s starred items. Both of these saving options are easily accessible on Inoreader&rsquo;s interface.</p>
<p>Why bother saving in two separate places? Over time I&rsquo;ve realized a few facts about myself and the types of content I consume:</p>
<ol>
<li>I don&rsquo;t like reading long stuff on my phone.</li>
<li>Some content requires deep reading, reviewing, highlighting, or some other activity in parallel to reading (like trying the code in a blog post that introduces a new Python library).</li>
<li>I don&rsquo;t need to &ldquo;read&rdquo; everything, I can enjoy listening to some content.</li>
</ol>
<p>So when I shortlist an item, if it has:</p>
<ul>
<li>many visual elements that help understand the content (e.g. a photography tutorial, a how-to guide on using a Python visualization library, etc), or</li>
<li>many code snippets</li>
</ul>
<p>I save it into Inoreader&rsquo;s starred items, and I get back to these items later on my MacBook (More on this later). Otherwise, I save the item into my Pocket account, which it then gets synced with Omnivore, and <strong>I listen to these items</strong> later while doing chores at home. This part of the workflow is a bit messy, I know but I&rsquo;ve experimented with various tools to create a streamlined process of saving items from feeds and listening to them on a high-quality text-to-speech service, and this is what serves me well at the moment:</p>
<ul>
<li>
<p>Every item that must be listened to gets saved into Pocket. Why Pocket?</p>
<ul>
<li>Firstly, because Pocket is a well-known read-it-later tool that has been integrated into many other tools, including Inoreader, so I can save items from my feed to Pocket with just one tap.</li>
<li>Second, since Pocket is integrated into many tools, and the average lifetime of the productivity and reading tools is shorter than the memory of a fish, I&rsquo;m not sure if I&rsquo;ll be using Omnivore, Readwise, or any other tool next year, but I&rsquo;m fairly certain that Pocket will be around (please Mozilla, don&rsquo;t kill it) and any new tool in this ecosystem will have some sort of Pocket integration.</li>
<li>[update 2024-10-31]: Omnivore recently [accounced](<a href="https://blog.omnivore.app/p/omnivore-is-joining-elevenlabs" target="_blank" >https://blog.omnivore.app/p/omnivore-is-joining-elevenlabs</a>
) they&rsquo;re shutting down the whole project, and it made me remember the above paragraph of my blog post and I was reminded that a battle tested old school tool like Pocket is more reliable than a new tool that we don&rsquo;t know if it will exist in a year.</li>
</ul>
</li>
<li>
<p>I&rsquo;ve written a Python script to get the new items from Pocket and save them into Omnivore every day. The script is on Github and using a <code>schedule: cron</code> job on Github Actions and it&rsquo;s running once every day.</p>
</li>
</ul>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20240222-shortlist.png#center"/> <figcaption>
            <p>Shortlisting</p>
        </figcaption>
</figure>

<h2 id="reading-with-text-to-speech">Reading with text-to-speech</h2>
<p>When I realized that by listening to articles I could finally transform myself from a content hoarder to a content consumer, I looked into affordable but high-quality apps that would provide this feature. I of course started with Pocket&rsquo;s own TTS feature, but it&rsquo;s honestly just trash. I couldn&rsquo;t bear listening to Android&rsquo;s cacophonous default TTS engine voices for more than 30 seconds. I then came across this <a href="https://github.com/jing332/tts-server-android" target="_blank" >tts-server-android</a>
 thing that I think somehow replaces your Android phone&rsquo;s TTS engine with more realistic voices (I honestly don&rsquo;t how it works, and yeah, I was brave enough to install the APK file on my phone). It was ok, but still not at a level that I&rsquo;d enjoy listening to. Inoreader also offers a TTS feature for its Pro users, but it&rsquo;s mostly a joke as it has 200K characters per month (which approximates ~20 articles based on my unscientific measurements).</p>
<p>Later I found out about <a href="https://readwise.io/i/s4759" target="_blank" >Readwise Reader</a>
, which was a new product in beta from the Readwise team. <a href="https://readwise.io/" target="_blank" >Readwise</a>
 originally is a sync-your-highlights app, but they started working on their Reader app which is a &hellip; everything app?! It aims to be your feed reader, read-it-later, e-book reader, PDF reader, and many more. Luckily for me, its TTS feature was the best one I had tried until then, and $8/month was a price that I was fine paying for a good TTS app (Readwise Reader was using Microsoft TTS service, but they have switched to <a href="https://unrealspeech.com/" target="_blank" >Unreal Speech</a>
 recently). Readwise Reader has a nice (one-way) Pocket integration, and it gets the newly saved items from Pocket every time you open the app, no extra action is required.</p>
<p>After using Readwise&rsquo;s semi-human-like TTS feature for a few months (with many glitchy UX behaviors of their Android app), I was introduced to <a href="https://omnivore.app/" target="_blank" >Omnivore</a>
 with <a href="https://www.youtube.com/watch?v=B-G0J2cGhBQ" target="_blank" >this YouTube video</a>
. I started trying out Omnivore&rsquo;s TTS, and wow! The voices were impressively high-quality and the experience was the closest to a human narrator (at 1.5x speed). Omnivore is an <a href="https://github.com/omnivore-app/omnivore" target="_blank" >open-source app</a>
, and upon browsing in their codebase and issues I found out they&rsquo;re using <a href="https://play.ht/" target="_blank" >PlayHT</a>
 as their TTS engine. It&rsquo;s still a question mark to me if they are using a not-very-cheap third-party service, how and why they&rsquo;re offering this for free, and what&rsquo;s the business plan, but I&rsquo;d be happy to pay a reasonable monthly subscription to be able to continue using it. Omnivore is a free and open-source app, so I probably can&rsquo;t complain, but I will anyway:</p>
<ul>
<li>TTS is only available on the iOS app, so as an Android user I need to use my iPad or work phone to listen to my saved articles.</li>
<li>There is no stable sync between Omnivore&rsquo;s own apps. Sometimes I remove or archive an item on the web, but it&rsquo;s still available on the iOS app, even if you refresh the feed, close the app, or get back to it next week. (<a href="https://github.com/omnivore-app/omnivore/issues/2922" target="_blank" >I created an issue</a>
 on Oct 2023, but no responses yet)</li>
<li>It has a Pocket integration (told ya!), but it (used to) have an &ldquo;import&rdquo; button in the <code>settings &gt; integrations</code> page, so you&rsquo;d need to click on this button every few days to make Omnivore to fetch the newly saved items from Pocket. No auto-import, yikes, but it was fine. But why do I say &ldquo;used to&rdquo;? Because they changed they completely removed the &ldquo;import&rdquo; button a few months ago, and you have to disconnect and re-connect your Pocket integration (and login, re-authorize, etc on Pocket&rsquo;s website) each time you want to import the new items! Why?! <a href="https://github.com/omnivore-app/omnivore/issues/3256" target="_blank" >I created an issue</a>
 for this on Dec 2023, but no response from devs yet. This is why I need to write my own script to fetch my saved articles from Pocket and store them in Omnivore, and it&rsquo;s been working mostly fine in the past few months.</li>
</ul>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20240222-reading-via-tts.png#center" width="300"/> <figcaption>
            <p>Reading via TTS on Omnivore</p>
        </figcaption>
</figure>

<h2 id="deep-reading">Deep reading</h2>
<p>Text-to-speech is not suitable for content with many visual elements or code snippets. I need to see the chart when the narrator says &ldquo;&hellip; and here is how my indie app&rsquo;s revenue changed last year&rdquo;, and to read the code when you hear &ldquo;&hellip; but you can implement the same functionality with the following 10 lines of Python&rdquo;. There are also cases that I&rsquo;ll need to browse a couple of web pages (like when I come across a HackerNews thread that many interesting tools have been recommended), and this is much easier on a laptop, compared to a phone.</p>
<p>More importantly, one of the critical benefits of doing all the steps of the workflow I&rsquo;ve described so far is to somehow retain the useful information I consume. What should I do when I find a potentially useful Python library or a tip on how to fine-tune a language model? Just say &ldquo;ok, cool&rdquo; and close the tab? I at least have to take a small note about it and make it easier for myself to find it later if I ever needed it. I have a very simple <a href="https://en.wikipedia.org/wiki/Personal_knowledge_management" target="_blank" >personal knowledge management</a>
 system on <a href="https://obsidian.md/" target="_blank" >Obsidian</a>
. I can easily search for my notes using the <a href="https://github.com/scambier/obsidian-omnisearch" target="_blank" >obsidian-omnisearch</a>
 plugin&rsquo;s fuzzy and weighted search features.</p>
<p>I spend 15-30 minutes a day going through the starred items on Inoreader, sorted on &ldquo;oldest first&rdquo;. Depending on the length and depth of the saved item, I can skim, read, highlight, and take notes of 2-10 articles within this timeframe. Obviously, not everything merits a persistence on my memory, I usually find myself taking note of 15-20% of the items I read from Inoreader starred items, while others get an <code>&quot;ok, cool&quot;, close tab</code>. If an item is very long or very dense (information-wise), I usually convert it to a PDF file and save it in my &ldquo;articles&rdquo; folder on iCloud, so I can read it in another scheduled 15-30 minutes time slot on my iPad. I use an app called <a href="https://apps.apple.com/nl/app/documents-file-manager-vpn/id364901807" target="_blank" >Documents</a>
 but I&rsquo;m in a hunt for a better PDF reader for iPad.</p>
<figure class="align-center ">
    <img loading="lazy" src="/images/2024/20240222-deep-reading.png#center"/> <figcaption>
            <p>Deep Reading and Taking Notes</p>
        </figcaption>
</figure>

<p>A side note worth mentioning is that I currently utilize open tabs of Chrome on my phone in parallel to Inoreader&rsquo;s starred feature. Sometimes I just keep an interesting link open as a tab on my phone, and thanks to Chrome&rsquo;s excellent &ldquo;tabs open on other devices&rdquo; feature, I browse them regularly on MacBook, similar to what I do with starred items on Inoreader.</p>
<h2 id="other-types-of-content">Other types of content</h2>
<ul>
<li><strong>Social media</strong>: I&rsquo;ve already talked extensively about Twitter, and I also spend 5-10 minutes a day browsing Instagram and Reddit, but usually nothing beneficial on either of them.</li>
<li><strong>Paper books</strong>: I unfortunately don&rsquo;t spend enough (or any, to be honest) time on reading books on paper. Although I&rsquo;m covering books by reading ebooks and listening to audiobooks, but I&rsquo;d love to add physical books to my days as well.</li>
<li><strong>E-books</strong>: I used to read e-books on my Kindle, but I find myself using the iPad for this.</li>
<li><strong>Audiobooks</strong>: For the same reason as liking to listen to articles, I sometimes prefer listening to books (obviously when the topic permits, meaning it&rsquo;s not full of code or math formulas). I used <a href="https://www.storytel.com/" target="_blank" >Storytel</a>
 for a few months, I&rsquo;m currently using Spotify for listening to audiobooks.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>I started using RSS and Inoreader less than 18 months ago, and I&rsquo;ve built this workflow over time, based on learnings of what has worked and what hasn&rsquo;t for me. I have experienced many benefits by doing this, including but not limited to:</p>
<ul>
<li>I don&rsquo;t feel cluttered with content. The number of my read-later items on Pocket remains steady at around 30, and take-note items on Inoreader are somewhat steady at around 170 (yeah, I need to speed this up a bit). I have replaced my pile of content with a flow of content, I consume from the pile at the same rate that I store into it.</li>
<li>By regularly exposing myself to high-quality content, I&rsquo;ve been learning many new things consistently. The ratio of the &ldquo;useful information&rdquo; per &ldquo;time spent&rdquo; has been significantly higher for me following this workflow.</li>
<li>I&rsquo;ve been known as a person who shares useful and interesting content with friends and colleagues. I have received much positive feedback at work appreciating me sharing articles and my findings.</li>
</ul>
<p>The workflow will surely change over time, as tools die and new tools emerge, and also as I as a person change. I&rsquo;ll update this post in the future with notes if I make changes to any parts of this workflow. Please reach out to me if you have any questions, or if you have any recommended tools that you think would fit or improve my workflow.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Migrating From Gatsby to Hugo</title>
      <link>https://saeedesmaili.com/posts/migrating-from-gatsby-to-hugo/</link>
      <pubDate>Sat, 17 Feb 2024 11:30:57 +0100</pubDate>
      <guid>https://saeedesmaili.com/posts/migrating-from-gatsby-to-hugo/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been using &lt;a href=&#34;https://github.com/gatsbyjs/gatsby&#34; target=&#34;_blank&#34; &gt;GatsbyJS&lt;/a&gt;
 for publishing my blog posts here, but I wanted to move to another static site generator that is more automation friendly (more on this later). That&amp;rsquo;s why I decided to migrate this blog to &lt;a href=&#34;https://github.com/gohugoio/hugo&#34; target=&#34;_blank&#34; &gt;Hugo&lt;/a&gt;
, which has a very active community and is developed with Go. At first, I was scared of this move, since I don&amp;rsquo;t know how to code in Go, but to my surprise the whole migration process didn&amp;rsquo;t require me to write any Go, and everything is handled via &lt;code&gt;yaml&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, and &lt;code&gt;jinja&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I&rsquo;ve been using <a href="https://github.com/gatsbyjs/gatsby" target="_blank" >GatsbyJS</a>
 for publishing my blog posts here, but I wanted to move to another static site generator that is more automation friendly (more on this later). That&rsquo;s why I decided to migrate this blog to <a href="https://github.com/gohugoio/hugo" target="_blank" >Hugo</a>
, which has a very active community and is developed with Go. At first, I was scared of this move, since I don&rsquo;t know how to code in Go, but to my surprise the whole migration process didn&rsquo;t require me to write any Go, and everything is handled via <code>yaml</code>, <code>html</code>, and <code>jinja</code>.</p>
<p>Here is a summary of the steps required to migrate a blog from Gatsby to Hugo:</p>
<h2 id="create-a-new-hugo-site">Create a new Hugo site</h2>
<p>My blog&rsquo;s code sits in a Github repo and the content is served by Netlify using a direct connection to the repo. Some of my existing blog posts have a decent amount of seach traffic, so I didn&rsquo;t want to mess up the URLs with this migration. I created a new Hugo site on my local machine and deploed it to a new Netlify subdomain, configured a few things to make sure the migration of the existing posts will be smooth, and then updated my domain to serve the new website.</p>
<ul>
<li><a href="https://gohugo.io/installation/" target="_blank" >Install Hugo</a>
:</li>
</ul>
<p>On Mac:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install hugo
</span></span></code></pre></div><p>Note that you don&rsquo;t need to have Go installed for this migration.</p>
<ul>
<li>Create a new Hugo site:</li>
</ul>
<p>Replace <code>mywebsite</code> with name of your website.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>hugo new site mywebsite --format yaml
</span></span></code></pre></div><h2 id="choose-and-add-a-theme">Choose and add a theme</h2>
<p>I decided to use <a href="https://github.com/adityatelange/hugo-PaperMod" target="_blank" >PaperMod</a>
 as my theme, but there are <a href="https://themes.gohugo.io/" target="_blank" >many options</a>
 you can choose from.</p>
<ul>
<li>Run the following two commands inside the folder of your Hugo site:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git submodule add --depth<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
</span></span><span style="display:flex;"><span>git submodule update --init --recursive
</span></span></code></pre></div><ul>
<li>Add following to <code>hugo.yaml</code>:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">theme</span>: [<span style="color:#e6db74">&#34;PaperMod&#34;</span>]
</span></span></code></pre></div><h2 id="move-the-existing-content">Move the existing content</h2>
<p>My existing blog posts on Gatsby had a <code>&lt;domain&gt;/&lt;post-slug&gt;</code> URL format (for example: <code>https://saeedesmaili.com/topic-classification-of-texts-locally/</code>), while Hugo by defauls uses a <code>/posts</code> prefix, so if I just created new posts the URL would be <code>&lt;domain&gt;/posts/&lt;post-slug&gt;</code>. I didn&rsquo;t want to deal with redirecting the old URLs, and decided to use the <code>url</code> frontmatter param in Hugo to enforce the same URL schema for my existing content (but will use the default <code>/posts/&lt;post-slug&gt;</code> for the new content, as you can see in the URL of this blog post).</p>
<ul>
<li>Define a default frontmatter format for Hugo posts:</li>
</ul>
<p>Since I just had a handful of existing blog content, I decided to create a new post content on Hugo and copy paste the markdown from Gatsby for every post. Hugo lets you define a yaml frontmatter template for new content, so you don&rsquo;t need to write <code>title</code>, <code>date</code>, etc for each post you create. To do this, I added the following in a new file called <code>default.md</code> in the <code>archetypes</code> directory:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>title: &#34;{{ replaceRE <span style="color:#e6db74">`^[0-9]{8}-`</span> &#34;&#34; .File.ContentBaseName | humanize | title }}&#34;
</span></span><span style="display:flex;"><span>slug: &#34;{{ replaceRE <span style="color:#e6db74">`^[0-9]{8}-`</span> &#34;&#34; .File.ContentBaseName }}&#34;
</span></span><span style="display:flex;"><span>date: &#34;{{ .Date }}&#34;
</span></span><span style="display:flex;"><span>draft: false
</span></span><span style="display:flex;"><span>---
</span></span></code></pre></div><p>The <code>replaceRE `^[0-9]{8}-` &quot;&quot; .File.ContentBaseName</code> part trims eight digits and a <code>-</code> from the beginning of the post slug. I added this to be able to prepend the post creation date to my markdown file names, but excluding them from post title and slugs. This way the markdown files will be sorted in the directory instead of being all over the place.</p>
<ul>
<li>Then, to create a new Hugo post content:</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>hugo new content posts/20240201-post-title.md
</span></span></code></pre></div><p>This creates a new file called <code>20240201-post-title.md</code> in <code>content/posts/</code> directory with the following content:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-md" data-lang="md"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>title: &#34;Post Title&#34;
</span></span><span style="display:flex;"><span>url: &#34;post-title&#34;
</span></span><span style="display:flex;"><span>date: &#34;2024-02-01T11:30:57+01:00&#34;
</span></span><span style="display:flex;"><span>draft: false
</span></span><span style="display:flex;"><span>---
</span></span></code></pre></div><p>I copy pasted the markdown content of my posts to this file, and since both Gatsby and Hugo use markdown, the post content migration was smooth, expect for the images and local files that I need to move as well.</p>
<ul>
<li>Move images and files:
The images of Gatsby posts reside in the same directory as the post&rsquo;s markdown file:</li>
</ul>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 176 185"
      >
      <g transform='translate(8,16)'>
<path d='M 64,16 L 72,0' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>g</text>
</g>

    </svg>
  
</div>
<p>And Hugo supports this exact file structure as well, but I preferred to opt for storing all the images in one single directory (which I called it <code>images</code>) in the <code>static</code> directory. For each existing post, I moved the image file to the <code>static/images/</code> directory, and prepended the post creation date to the file name. Then in the post content, using the VSCode&rsquo;s search and replace feature, replaced <code>/images/</code> with <code>/static/images/2024/20240201-</code>. Adding the date to the image file name will make it easy to find them in future if needed.</p>
<p>And that&rsquo;s it! All existing content are moved to Hugo and I could deploy to Netlify, but I also made a few other config changes as well.</p>
<h2 id="other-configurations">Other configurations</h2>
<ul>
<li>Rename the RSS file to <code>rss.xml</code>:</li>
</ul>
<p>The RSS file in Gatsby uses <code>rss.xml</code> name, but Hugo uses <code>index.xml</code>. This means people would need to know about this change, and to update the url in their feed reader, and to be honest I&rsquo;m not sure how many people are subscribed to my blogs feed (Are you? Porbably not many), but I wanted to keep the old name and retain the subscribers. So I added the following to <code>hugo.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">outputFormats</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">RSS</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">mediatype</span>: <span style="color:#e6db74">&#34;application/rss&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">baseName</span>: <span style="color:#e6db74">&#34;rss&#34;</span>
</span></span></code></pre></div><ul>
<li>Include the full text of posts in the RSS feed:</li>
</ul>
<p>As I don&rsquo;t want to bother the readers who follow me via RSS, I added the following config to <code>hugo.yaml</code> to include the full text of my posts in the RSS feed:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">params</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ae81ff">...</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ShowFullTextinRSS</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><ul>
<li>Use a serif font instead of the default sans-serif one:</li>
</ul>
<p>I like to use a serif font wherever there is a lengthy content to read, so I added a regular, bold, italic, and bold-italic versions of the <a href="https://www.ibm.com/plex/" target="_blank" >IBMPlexSerif font</a>
 to the <code>static/fonts/</code> directory, then created a file named <code>custom.css</code> in <code>assets/css/extented/</code> with following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>@<span style="color:#66d9ef">font-face</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-family</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;IBM Plex Serif&#34;</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">src</span><span style="color:#f92672">:</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-Regular.woff2&#34;</span><span style="color:#f92672">)</span> <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff2&#34;</span><span style="color:#f92672">),</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-Regular.woff&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff&#34;</span><span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-weight</span><span style="color:#f92672">:</span> <span style="color:#f92672">normal</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-style</span><span style="color:#f92672">:</span> <span style="color:#f92672">normal</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">font-face</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-family</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;IBM Plex Serif&#34;</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">src</span><span style="color:#f92672">:</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-Bold.woff2&#34;</span><span style="color:#f92672">)</span> <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff2&#34;</span><span style="color:#f92672">),</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-Bold.woff&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff&#34;</span><span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-weight</span><span style="color:#f92672">:</span> <span style="color:#f92672">bold</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-style</span><span style="color:#f92672">:</span> <span style="color:#f92672">normal</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">font-face</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-family</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;IBM Plex Serif&#34;</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">src</span><span style="color:#f92672">:</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-Italic.woff2&#34;</span><span style="color:#f92672">)</span> <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff2&#34;</span><span style="color:#f92672">),</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-Italic.woff&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff&#34;</span><span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-weight</span><span style="color:#f92672">:</span> <span style="color:#f92672">normal</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-style</span><span style="color:#f92672">:</span> <span style="color:#f92672">italic</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">font-face</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-family</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;IBM Plex Serif&#34;</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">src</span><span style="color:#f92672">:</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-BoldItalic.woff2&#34;</span><span style="color:#f92672">)</span> <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff2&#34;</span><span style="color:#f92672">),</span> <span style="color:#f92672">url</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;/fonts/IBMPlexSerif-BoldItalic.woff&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">format</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;woff&#34;</span><span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-weight</span><span style="color:#f92672">:</span> <span style="color:#f92672">bold</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">font-style</span><span style="color:#f92672">:</span> <span style="color:#f92672">italic</span><span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">body</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">font-family</span>: <span style="color:#e6db74">&#34;IBM Plex Serif&#34;</span>, <span style="color:#e6db74">&#34;Merriweather&#34;</span>, <span style="color:#e6db74">&#34;Georgia&#34;</span>, <span style="color:#e6db74">&#34;Cambria&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Times New Roman&#34;</span>, <span style="color:#e6db74">&#34;Times&#34;</span>, <span style="color:#e6db74">&#34;serif&#34;</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li>Add analytics script:</li>
</ul>
<p>I use <a href="https://www.simpleanalytics.com/?referral=wopip" target="_blank" >SimpleAnalytics</a>
 to gather some basic data around which contents of this blog are more useful for people. I created a file called <code>extend_head.html</code> in <code>layouts/partials/</code> directory and added their javascript tag in the file.</p>
<h2 id="deploy">Deploy!</h2>
<p>I then created a new website on Netlify, connected the Hugo site&rsquo;s repo from Github, and after playing around with the website on Netlify&rsquo;s subdomain and making sure everything works fine, I removed my domain from the existing Gatsby website on Netlify, and added it to the new Hugo one. And with that, the website was live with Hugo!</p>
<p>PS. One of the main reasons for doing all these and migrating to Hugo was to be able to have a new <a href="/notes/" >notes</a>
 section on this blog, and publish my brief notes on what I read and discover every week, in a relatively automatic way (let me know if you&rsquo;re interested in a write up on how I automated sharing these notes with <a href="https://readwise.io/i/s4759" target="_blank" >Readwise</a>
, <a href="https://obsidian.md/" target="_blank" >Obsidian</a>
, <a href="https://www.alfredapp.com/" target="_blank" >Alfred</a>
, and Hugo). These notes are not displayed on this blog&rsquo;s home page, but will be accessable on the <a href="/notes/" ><code>/notes</code></a>
 section and this section has its own <a href="https://saeedesmaili.com/notes/rss.xml" target="_blank" >RSS feed</a>
, so make sure to check it out and subscribe to the feed if you&rsquo;re interested.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hand-drawn xkcd style charts with matplotlib</title>
      <link>https://saeedesmaili.com/hand-drawn-style-charts-in/</link>
      <pubDate>Sat, 09 Dec 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/hand-drawn-style-charts-in/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m a big fan of unique charting styles and I avoid using the default matplotlib style whenever possible, as I find it boring and soleless. This preference is not limited to charts, and I also like the hand-drawn styles for fonts and diagrams (&lt;a href=&#34;https://excalidraw.com/&#34; target=&#34;_blank&#34; &gt;excalidraws&lt;/a&gt;
 is a fantastic tool that I use frequently). The hand-drawn style is especially useful when presenting a proof of concept idea.&lt;/p&gt;
&lt;p&gt;Something very interesting that I&amp;rsquo;ve recently stumbled upon is an &lt;a href=&#34;https://xkcd.com/&#34; target=&#34;_blank&#34; &gt;xkcd&lt;/a&gt;
 chart style for &lt;a href=&#34;https://matplotlib.org/stable/gallery/showcase/xkcd.html&#34; target=&#34;_blank&#34; &gt;matplotlib&lt;/a&gt;
. This is a fantastic style that I can see myself using frequently going forward. &lt;strong&gt;All you need to do is add &lt;code&gt;plt.xkcd()&lt;/code&gt; to your existing matplotlib code.&lt;/strong&gt;&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I&rsquo;m a big fan of unique charting styles and I avoid using the default matplotlib style whenever possible, as I find it boring and soleless. This preference is not limited to charts, and I also like the hand-drawn styles for fonts and diagrams (<a href="https://excalidraw.com/" target="_blank" >excalidraws</a>
 is a fantastic tool that I use frequently). The hand-drawn style is especially useful when presenting a proof of concept idea.</p>
<p>Something very interesting that I&rsquo;ve recently stumbled upon is an <a href="https://xkcd.com/" target="_blank" >xkcd</a>
 chart style for <a href="https://matplotlib.org/stable/gallery/showcase/xkcd.html" target="_blank" >matplotlib</a>
. This is a fantastic style that I can see myself using frequently going forward. <strong>All you need to do is add <code>plt.xkcd()</code> to your existing matplotlib code.</strong></p>
<p>I played around with this style and generated a few examples. Note that currently there is <a href="https://github.com/matplotlib/matplotlib/issues/27350" target="_blank" >an issue</a>
 that causes matplotlib to print a hundred lines of warnings when using the xkcd style, but you can get rid of them by disabling <code>matplotlib.font_manager</code> warnings.</p>
<h3 id="examples">Examples</h3>
<p>Let&rsquo;s first see what our chart would look like out of the box if we used the default matplotlib style:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> matplotlib.pyplot <span style="color:#66d9ef">as</span> plt
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> numpy <span style="color:#66d9ef">as</span> np
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> logging
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>logging<span style="color:#f92672">.</span>getLogger(<span style="color:#e6db74">&#34;matplotlib.font_manager&#34;</span>)<span style="color:#f92672">.</span>disabled <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## some dummy data</span>
</span></span><span style="display:flex;"><span>months <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;Jan&#34;</span>, <span style="color:#e6db74">&#34;Feb&#34;</span>, <span style="color:#e6db74">&#34;Mar&#34;</span>, <span style="color:#e6db74">&#34;Apr&#34;</span>, <span style="color:#e6db74">&#34;May&#34;</span>, <span style="color:#e6db74">&#34;Jun&#34;</span>, <span style="color:#e6db74">&#34;Jul&#34;</span>, <span style="color:#e6db74">&#34;Aug&#34;</span>, <span style="color:#e6db74">&#34;Sep&#34;</span>, <span style="color:#e6db74">&#34;Oct&#34;</span>, <span style="color:#e6db74">&#34;Nov&#34;</span>, <span style="color:#e6db74">&#34;Dec&#34;</span>
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>sales <span style="color:#f92672">=</span> [<span style="color:#ae81ff">12</span>, <span style="color:#ae81ff">15</span>, <span style="color:#ae81ff">18</span>, <span style="color:#ae81ff">22</span>, <span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">24</span>, <span style="color:#ae81ff">26</span>, <span style="color:#ae81ff">25</span>, <span style="color:#ae81ff">23</span>, <span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">18</span>, <span style="color:#ae81ff">16</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## our plot</span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>plot(months, sales, marker<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;o&#34;</span>, linestyle<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>title(<span style="color:#e6db74">&#34;Monthly Sales Performance&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>ylabel(<span style="color:#e6db74">&#34;Sales (in $1000s)&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>annotate(
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;THE DAY WE CHANGED</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">HOW WE RECOMMEND</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">NEW PRODUCTS&#34;</span>,
</span></span><span style="display:flex;"><span>        xy<span style="color:#f92672">=</span>(<span style="color:#e6db74">&#34;May&#34;</span>, <span style="color:#ae81ff">20</span>),
</span></span><span style="display:flex;"><span>        arrowprops<span style="color:#f92672">=</span>dict(arrowstyle<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-&gt;&#34;</span>),
</span></span><span style="display:flex;"><span>        xytext<span style="color:#f92672">=</span>(<span style="color:#e6db74">&#34;May&#34;</span>, <span style="color:#ae81ff">15</span>),
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span></code></pre></div><p><img alt="default matplotlib style" loading="lazy" src="/images/2023/20231209-default-matplotlib-style.png"></p>
<p>What if we use the xkcd style?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">## our plot</span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xkcd() <span style="color:#75715e">## just add this line</span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>plot(months, sales, marker<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;o&#34;</span>, linestyle<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>title(<span style="color:#e6db74">&#34;Monthly Sales Performance&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>ylabel(<span style="color:#e6db74">&#34;Sales (in $1000s)&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>annotate(
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;THE DAY WE CHANGED</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">HOW WE RECOMMEND</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">NEW PRODUCTS&#34;</span>,
</span></span><span style="display:flex;"><span>        xy<span style="color:#f92672">=</span>(<span style="color:#e6db74">&#34;May&#34;</span>, <span style="color:#ae81ff">20</span>),
</span></span><span style="display:flex;"><span>        arrowprops<span style="color:#f92672">=</span>dict(arrowstyle<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-&gt;&#34;</span>),
</span></span><span style="display:flex;"><span>        xytext<span style="color:#f92672">=</span>(<span style="color:#e6db74">&#34;May&#34;</span>, <span style="color:#ae81ff">15</span>),
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span></code></pre></div><p><img alt="xkcd matplotlib style" loading="lazy" src="/images/2023/20231209-xkcd-matplotlib-style.png"></p>
<p>Much better in my opinion. Let&rsquo;s see a few other examples:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> random
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>random<span style="color:#f92672">.</span>seed(<span style="color:#ae81ff">10</span>)
</span></span><span style="display:flex;"><span>exam_scores <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>    random<span style="color:#f92672">.</span>normalvariate(<span style="color:#ae81ff">70</span>,<span style="color:#ae81ff">10</span>,)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">100</span>)
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xkcd() <span style="color:#75715e">## default vs xkcd</span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>hist(exam_scores, bins<span style="color:#f92672">=</span><span style="color:#ae81ff">20</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>title(<span style="color:#e6db74">&#34;Exam Scores Distribution&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xlabel(<span style="color:#e6db74">&#34;Scores&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>ylabel(<span style="color:#e6db74">&#34;Frequency&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>annotate(
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;OUR BEST</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">STUDENT&#34;</span>,
</span></span><span style="display:flex;"><span>    xy<span style="color:#f92672">=</span>(<span style="color:#ae81ff">97</span>, <span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span>    arrowprops<span style="color:#f92672">=</span>dict(arrowstyle<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;-&gt;&#34;</span>),
</span></span><span style="display:flex;"><span>    xytext<span style="color:#f92672">=</span>(<span style="color:#ae81ff">87</span>, <span style="color:#ae81ff">8</span>),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span></code></pre></div><p><img alt="default matplotlib histogram" loading="lazy" src="/images/2023/20231209-default-matplotlib-histogram.png">
<img alt="xkcd matplotlib histogram" loading="lazy" src="/images/2023/20231209-xkcd-matplotlib-histogram.png"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>hours_studied <span style="color:#f92672">=</span> [random<span style="color:#f92672">.</span>uniform(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">10</span>) <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">50</span>)]
</span></span><span style="display:flex;"><span>exam_scores <span style="color:#f92672">=</span> [<span style="color:#ae81ff">60</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">*</span> hours <span style="color:#f92672">+</span> random<span style="color:#f92672">.</span>normalvariate(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">5</span>) <span style="color:#66d9ef">for</span> hours <span style="color:#f92672">in</span> hours_studied]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xkcd() <span style="color:#75715e">## default vs xkcd</span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>scatter(hours_studied, exam_scores, color<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;green&#34;</span>, alpha<span style="color:#f92672">=</span><span style="color:#ae81ff">0.7</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>title(<span style="color:#e6db74">&#34;Scatterplot of Hours Studied vs Exam Scores&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xlabel(<span style="color:#e6db74">&#34;Hours Studied&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>ylabel(<span style="color:#e6db74">&#34;Exam Scores&#34;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span></code></pre></div><p><img alt="default matplotlib scatterplot" loading="lazy" src="/images/2023/20231209-default-matplotlib-scatterplot.png">
<img alt="xkcd matplotlib scatterplot" loading="lazy" src="/images/2023/20231209-xkcd-matplotlib-scatterplot.png"></p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Why you should try Alfred</title>
      <link>https://saeedesmaili.com/why-you-should-try-alfred/</link>
      <pubDate>Thu, 26 Oct 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/why-you-should-try-alfred/</guid>
      <description>&lt;p&gt;Alfred has definitely boosted my productivity since I started using it almost a year ago. Sharing some fragments of my experience using Alfred with a few friends and colleagues made me realize I need to write down a summary of what I find helpful on Alfred, so I can share it with people in future when needed.&lt;/p&gt;
&lt;h2 id=&#34;whats-alfred&#34;&gt;What’s Alfred&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&#34;https://www.alfredapp.com/&#34; target=&#34;_blank&#34; &gt;Alfred&lt;/a&gt;
 is an app for macOS which boosts your efficiency with hotkeys, keywords, text expansion and more. Search your Mac and the web, and be more productive with custom actions to control your Mac.&lt;/em&gt;&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>Alfred has definitely boosted my productivity since I started using it almost a year ago. Sharing some fragments of my experience using Alfred with a few friends and colleagues made me realize I need to write down a summary of what I find helpful on Alfred, so I can share it with people in future when needed.</p>
<h2 id="whats-alfred">What’s Alfred</h2>
<p><em><a href="https://www.alfredapp.com/" target="_blank" >Alfred</a>
 is an app for macOS which boosts your efficiency with hotkeys, keywords, text expansion and more. Search your Mac and the web, and be more productive with custom actions to control your Mac.</em></p>
<p>You can replace it with Spotlight on macOS (by setting the cmd+space hotkey for Alfred and disabling Spotlight). Let’s look at some of Alfred&rsquo;s features. Most of the features mentioned below require the <a href="https://www.alfredapp.com/powerpack/" target="_blank" >paid version of the app</a>
.</p>
<h2 id="clipboard-manager">Clipboard manager</h2>
<p><em>Configured from Features &gt; Clipboard History</em></p>
<p>I&rsquo;ve set the Viewer Hotkey to <code>cmd+shift+V</code>:
<img alt="clipboard-manager" loading="lazy" src="/images/2023/20231026-clipboard-manager.png"></p>
<p>You can access the history of copied items with the same hotkey anywhere on your Mac:
<img alt="clipboard-manager" loading="lazy" src="/images/2023/20231026-clipboard-manager2.png"></p>
<p>As you can see in the above screenshot, it can keep the copied images, shows where each item is copied from (Chrome, Firefox, Slack, etc), at what time each item is copied, and you can paste one of the items with <code>cmd+1</code>, <code>cmd+2</code>, etc, or browse in the list with up and down arrow keys.</p>
<h2 id="text-snippets">Text snippets</h2>
<p><em>Configured from Features &gt; Snippets</em></p>
<p>Are there specific texts that you use over and over? Like a code snippet, an email or web address, your home address, etc? Using the Snippets feature of Alfred, you can set a specific keyword for a text snippet, and when you type that keyword anywhere in your mac, it will automatically be replaced with the text snippet. In the example below, I have created an snippet for <code>:bqt</code> keyword, and when I type this keyword somewhere, it gets replaced with a code snippet for querying BigQuery:
<img alt="snippets" loading="lazy" src="/images/2023/20231026-snippets.png"></p>
<p>You can organize snippets in collections as you can see in the screenshot above.</p>
<h2 id="web-search">Web search</h2>
<p><em>Configured from Features &gt; Web Search</em></p>
<p>Is there a website where you find yourself using its search functionality a lot? Like searching for a book on Goodreads or Storytel, locations on Google Maps, or word definitions on dictionary or thesaurus? You can set custom actions to search on any website. For example, I have one set for archive.org, so when I encounter a paywalled article, I copy the url and open Alfred, then use <code>archive &lt;cmd+v to paste the url&gt;</code> and a new tab on my browser opens with the archived version of the url. I also have custom keywords for searching in Goodreads, Storytel, Spotify web, dictionary, thesaurus, pypi, devdocs, etc.</p>
<p>To set a new one you just need to know where in the URL the target website replaces the searched keyword:
<img alt="web-search" loading="lazy" src="/images/2023/20231026-web-search.png"></p>
<h2 id="file-search-and-file-actions">File search and file actions</h2>
<p><em>Configured from Features &gt; File Search and Features &gt; Default Results</em></p>
<p>Like Spotlight, you can search for files and applications available on the Mac with Alfred. But Alfred enables many customizations on which directories and file types the search should be limited to. In addition, after finding a file with Alfred, you can use the right arrow key on the keyboard to perform various actions on the file without opening it. In the example below, I can easily rename, delete, copy, move, get info, or perform many more actions (not all of the visible in the screenshot) on the PDF file I’ve searched for with Alfred.
<img alt="file-actions" loading="lazy" src="/images/2023/20231026-file-actions.png"></p>
<h2 id="workflows">Workflows</h2>
<p><em>Configured from Workflows</em></p>
<p>This one is the most dangerous rabbit hole on Alfred! Basically the workflows in Alfred allows you to create your own simple or complex &ldquo;workflows&rdquo; for specific actions.
<img alt="workflows" loading="lazy" src="/images/2023/20231026-workflows.png"></p>
<p>But thanks to the amazing community of Alfred users, you almost don’t need to worry about the complexity of creating a workflow, because there is a workflow already created for almost every task. You just download a workflow from <a href="https://alfred.app/" target="_blank" >Alfred’s workflow gallery</a>
 (or anywhere else that the creator has shared it), open it, enable it, and use it without ever looking at the workflow editor.</p>
<p>A few workflows I use almost every day:</p>
<ul>
<li><a href="https://alfred.app/workflows/jygh/recent-files/" target="_blank" >Recent Files</a>
 (e.g. when I download a file and need to perform an action immediately)</li>
<li><a href="https://github.com/zeitlings/alfred-workflows#162-alfred-ocr" target="_blank" >OCR</a>
 (when I need to copy a text from an image or an app which doesn’t allow selecting texts)</li>
<li><a href="https://alfred.app/workflows/logicxd/vscodediff/" target="_blank" >VSCodeDiff</a>
 (to see the difference between two copied texts)</li>
<li><a href="https://github.com/zeitlings/alfred-workflows#111-bluetooth-device-battery" target="_blank" >Bluetooth Device Battery</a>
 (to see if my headsets and trackpad need to be charged)</li>
<li><a href="https://alfred.app/workflows/alfredapp/system-settings/" target="_blank" >System Settings</a>
 (to open the macOS settings in the page I want to do something on)</li>
</ul>
<p>A few others that I use often but not every day:</p>
<ul>
<li><a href="https://github.com/zeitlings/alfred-quill/" target="_blank" >Quil</a>
 (to perform actions on text, like changing the whole text to lowercase, uppercase, snake case, title case, or counting the number of words in a text)</li>
<li><a href="https://alfred.app/workflows/alfredapp/tinypng/" target="_blank" >TinyPNG</a>
 (to compress an image file)</li>
<li><a href="https://alfred.app/workflows/vitor/temporary-email/" target="_blank" >Temporary Email</a>
 (to create a temporary email address to sign up to a garbage site that I have to for some reason, but I also need to click on the email they send)</li>
<li><a href="https://alfred.app/workflows/chrislemke/chatfred/" target="_blank" >ChatFred</a>
 (haven’t used it myself yet, since I’m using other ways to interact with ChatGPT API, but this is one of the easiest ways to ask a quick question from ChatGPT without opening anything)</li>
<li><a href="https://github.com/vitorgalvao/alfred-workflows/tree/master/StrongPassword" target="_blank" >StrongPassword</a>
 (to generate a random sequence of characters to use as password or anything similar)</li>
<li><a href="https://alfred.app/workflows/chrisgrieser/shimmering-obsidian/" target="_blank" >Shimmering Obsidian</a>
 (to open a note on <a href="https://obsidian.md/" target="_blank" >Obsidian</a>
. The search works surprisingly well, much better than Obsidian&rsquo;s own search)</li>
</ul>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Getting started with developing browser extensions</title>
      <link>https://saeedesmaili.com/getting-started-with-developing-browser-extensions/</link>
      <pubDate>Mon, 25 Sep 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/getting-started-with-developing-browser-extensions/</guid>
      <description>&lt;p&gt;For a personal project, I was looking for some guidance on how to develop a simple browser extension, but the information on this topic was so fragmanted and difficult to grasp. I then came accross a book named &lt;a href=&#34;https://link.springer.com/book/10.1007/978-1-4842-8725-5&#34; target=&#34;_blank&#34; &gt;Building Browser Extensions&lt;/a&gt;
 which is focused on the same topic and I found it useful for getting an overall understanding of how to develop a simple extension, but it&amp;rsquo;s quite long, so I started summarizing the important parts for myself. What follows is just my notes and summaries from this book.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>For a personal project, I was looking for some guidance on how to develop a simple browser extension, but the information on this topic was so fragmanted and difficult to grasp. I then came accross a book named <a href="https://link.springer.com/book/10.1007/978-1-4842-8725-5" target="_blank" >Building Browser Extensions</a>
 which is focused on the same topic and I found it useful for getting an overall understanding of how to develop a simple extension, but it&rsquo;s quite long, so I started summarizing the important parts for myself. What follows is just my notes and summaries from this book.</p>
<h2 id="fundamentals">Fundamentals</h2>
<h3 id="manifest">Manifest</h3>
<p>The manifest is the rulebook for the browser extension. Every extension is required to provide this manifest as JSON data in a <code>manifest.json</code> file. Some examples of what is contained in the manifest:</p>
<ul>
<li>Public information such as the extension name, description, semantic version, icons, and author.</li>
<li>Pointers to entrypoint files for background scripts, popup pages, options pages, devtools pages, and content scripts.</li>
<li>Requirements and configurations the extension needs to properly operate, such as permissions, content security policies, cross-origin policies, and minimum browser versions.</li>
<li>Pattern-match rule sets for managing network requests, enabled domains, and resources the extension wishes to inject into the page via content script.</li>
<li>Miscellaneous extension-specific options like enabling offline and incognito and keyboard shortcuts.</li>
</ul>
<p>A sample <code>manifest.josn</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;Extension Name&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Browser extension created from scratch&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;1.0&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;manifest_version&#34;</span>: <span style="color:#ae81ff">3</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;icons&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;16&#34;</span>: <span style="color:#e6db74">&#34;images/icon-16.png&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;32&#34;</span>: <span style="color:#e6db74">&#34;images/icon-32.png&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;48&#34;</span>: <span style="color:#e6db74">&#34;images/icon-48.png&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;128&#34;</span>: <span style="color:#e6db74">&#34;images/icon-128.png&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;background&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;service_worker&#34;</span>: <span style="color:#e6db74">&#34;scripts/background.js&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;action&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;default_popup&#34;</span>: <span style="color:#e6db74">&#34;popup/index.html&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;options_page&#34;</span>: <span style="color:#e6db74">&#34;options/options.html&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;content_scripts&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;js&#34;</span>: [<span style="color:#e6db74">&#34;scripts/content.js&#34;</span>],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;matches&#34;</span>: [<span style="color:#e6db74">&#34;*://example.com/*&#34;</span>],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;run_at&#34;</span>: <span style="color:#e6db74">&#34;document_end&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;permissions&#34;</span>: [<span style="color:#e6db74">&#34;activeTab&#34;</span>, <span style="color:#e6db74">&#34;scripting&#34;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="background-scripts">Background scripts</h3>
<p>The primary purpose of background scripts is to handle browser events. These events might be extension lifecycle events such as an install or uninstall, or they might be browser events like navigating to a web page or adding a new bookmark.</p>
<p>They can access the WebExtensions APIs and therefore are capable of performing actions such as exchanging messages with other parts of the extension, exchanging messages with other extensions, or programmatically injecting content scripts into a page.</p>
<p>To inspect the background service worker, use the <code>service workder</code> link in the extension page in the browser&rsquo;s settings. This will open a new inspection window.</p>
<p>If the <code>action</code> parameter in the <code>manifest.json</code> file left empty, action will be handled by background script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#75715e">// manifest.json
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;action&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {}<span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;background&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;service_worker&#34;</span>: <span style="color:#e6db74">&#34;background.js&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">// background.js
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">action</span>.<span style="color:#a6e22e">onClicked</span>.<span style="color:#a6e22e">addListener</span>(() =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#34;Clicked toolbar icon!&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h3 id="popup-page">Popup page</h3>
<p>The popup page is a native browser container that browser extensions can use to display a custom user interface. The popup page behaves as a dialog box that “pops” over the web page when the user clicks the extension toolbar button. The popup page will always appear directly below the toolbar. Because it can be quickly accessed and can show over any web page, the popup page typically contains content that users need easy access to.</p>
<p>Popup pages are rendered just like regular web pages, but their dialog-like nature means they are disposable: the popup will be freshly rendered each time the popup is opened, and unloaded when the popup is closed. Like background scripts, popup pages can access the WebExtensions API, meaning they have the same set of capabilities.</p>
<p>The popup page cannot be opened programmatically, it must be triggered by a toolbar click or similar privileged browser action.</p>
<p>To open a popup page when user clicks on the extension icon in toolbar, add the following to <code>manifest.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#e6db74">&#34;action&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;default_popup&#34;</span>: <span style="color:#e6db74">&#34;popup/popup.html&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="options-page">Options page</h3>
<p>The options page is a native browser container that browser extensions can use to display a custom user interface. The options page behaves as a standalone web page that opens when the user clicks “Options” in the extension toolbar context menu.</p>
<p>Like the popup page, this is a fully featured web page with access to the WebExtensions API, meaning you are capable of using it as a full web application.</p>
<p>To add an options page in <code>manifest.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#e6db74">&#34;options_page&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;options/options.html&#34;</span>
</span></span></code></pre></div><p>Unlike the popup, the options page <em>can</em> be opened programmatically, but content scripts can&rsquo;t open an option page. The following actions will open the options page:</p>
<ul>
<li>Right clicking the toolbar icon button and selecting <em>Options</em></li>
<li>Calling <code>chrome.runtime.openOptionsPage()</code></li>
</ul>
<h3 id="content-scripts">Content scripts</h3>
<p>The term <em>content script</em> broadly refers to any content that is injected into a web page. JavaScript can either be injected declaratively in the manifest, or programmatically from an extension page or background script via the WebExtensions API. This content can be JavaScript, CSS, or both.</p>
<p>Content scripts are fully capable of reading and writing the page, enabling things like in-page widgets or full integration with the web page. Content scripts have limited access to the WebExtensions API, so they are incapable of many actions that are possible in the popup page, options page, or background script. They can, however, exchanges messages with other extension elements like background scripts. Therefore, content scripts can still indirectly use the WebExtensions API by delegating actions to a background script.</p>
<h2 id="development">Development</h2>
<h3 id="installation-and-reload">Installation and reload</h3>
<p>To install an extension, open the <code>extensions</code> page in the browser and use <code>Load unpacked</code> option. After making changes to the extension scripts, you should hit the <code>reload</code> icon in the extensions page for the changes to be used.</p>
<p><img alt="load unpacked" loading="lazy" src="/images/2023/20230925-developing-browser-extensions.avif"></p>
<h3 id="keyboard-shortcuts">Keyboard shortcuts</h3>
<p><code>commands</code> is used to add keyboard shortcut support in an extension:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#75715e">// manifest.json
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#34;commands&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> { 
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;foobar&#34;</span>: { 
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;suggested_key&#34;</span>: { 
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;default&#34;</span>: <span style="color:#e6db74">&#34;Ctrl+Shift+J&#34;</span>, 
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;mac&#34;</span>: <span style="color:#e6db74">&#34;MacCtrl+Shift+J&#34;</span> 
</span></span><span style="display:flex;"><span>  }, 
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;Perform foobar action&#34;</span> 
</span></span><span style="display:flex;"><span>  } 
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="programatic-injection-of-content-scripts">Programatic injection of content scripts</h3>
<p>The manifest is not the only way to inject content scripts. It is also possible to programmatically inject JavaScript and CSS into the page using the <code>chrome.scripting</code> API.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">// background.js
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">action</span>.<span style="color:#a6e22e">onClicked</span>.<span style="color:#a6e22e">addListener</span>((<span style="color:#a6e22e">tab</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">target</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tabId</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">tab</span>.<span style="color:#a6e22e">id</span>,
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">scripting</span>.<span style="color:#a6e22e">executeScript</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">target</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">func</span><span style="color:#f92672">:</span> () =&gt; {
</span></span><span style="display:flex;"><span>      document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">innerHTML</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`Hello, world!`</span>;
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// or: files: [&#34;content-script.js&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  });
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">scripting</span>.<span style="color:#a6e22e">insertCSS</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">target</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">css</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`body { background-color: red !important; }`</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// or: files: [&#34;content-script.css&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="webextensions-api">WebExtensions API</h2>
<h3 id="permissions">Permissions</h3>
<p>Inspect current permissions and add or remove permissions. More: <a href="https://developer.chrome.com/docs/extensions/reference/permissions/" target="_blank" >chrome.permissions</a>
</p>
<h3 id="messaging">Messaging</h3>
<p>Send messages between parts of the extension, or between the extension and an outside entity. More: <a href="https://developer.chrome.com/docs/extensions/mv3/messaging/" target="_blank" >Chrome Extensions Message passing</a>
, <a href="https://developer.chrome.com/docs/extensions/reference/runtime/" target="_blank" >chrome.runtime</a>
, <a href="https://developer.chrome.com/docs/extensions/reference/tabs/" target="_blank" >chrome.tabs</a>
</p>
<h3 id="storage">Storage</h3>
<p>A simple but powerful key/value storage. More: <a href="https://developer.chrome.com/docs/extensions/reference/storage/" target="_blank" >chrome.storage</a>
</p>
<ul>
<li><code>storage.local</code> stores values only on the local browser</li>
<li><code>storage.sync</code> stores values that are shared between the authenticated browser session</li>
<li><code>storage.session</code> stores values in memory that will be discarded when the browser closes</li>
<li><code>storage.managed</code> is a read-only store intended for enterprise users</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">storage</span>.<span style="color:#a6e22e">onChanged</span>.<span style="color:#a6e22e">addListener</span>(<span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">storage</span>.<span style="color:#a6e22e">local</span>.<span style="color:#a6e22e">set</span>({ <span style="color:#a6e22e">foo</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;tmp&#34;</span> });
</span></span><span style="display:flex;"><span><span style="color:#75715e">// { foo: { newValue &#34;tmp&#34; } }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">storage</span>.<span style="color:#a6e22e">local</span>.<span style="color:#a6e22e">set</span>({ <span style="color:#a6e22e">foo</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;bar&#34;</span>, <span style="color:#a6e22e">baz</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;qux&#34;</span> });
</span></span><span style="display:flex;"><span><span style="color:#75715e">// {
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   foo: { oldValue: &#34;tmp&#34;, newValue &#34;bar&#34; }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   baz: { newValue &#34;qux&#34; }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// }
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">chrome</span>.<span style="color:#a6e22e">storage</span>.<span style="color:#a6e22e">local</span>.<span style="color:#a6e22e">get</span>([<span style="color:#e6db74">&#34;foo&#34;</span>, <span style="color:#e6db74">&#34;baz&#34;</span>]));
</span></span><span style="display:flex;"><span><span style="color:#75715e">// { foo: &#34;bar&#34;, baz: &#34;qux&#34; }
</span></span></span></code></pre></div><h3 id="authentication">Authentication</h3>
<p>Manage authentication state and OAuth flows. More: <a href="https://developer.chrome.com/docs/extensions/reference/identity/" target="_blank" >chrome.identity</a>
</p>
<h3 id="network-requests">Network requests</h3>
<p>More: <a href="https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/" target="_blank" >chrome.declarativeNetRequest</a>
, <a href="https://developer.chrome.com/docs/extensions/reference/webRequest/" target="_blank" >chrome.webRequest</a>
, <a href="https://developer.chrome.com/docs/extensions/reference/webNavigation/" target="_blank" >chrome.webNavigation</a>
</p>
<ul>
<li><code>declarativeNetRequest</code> is used to declaratively instruct the browser to manage page traffic according to a set of rules defined by the extension.</li>
<li><code>webRequest</code> is used to imperatively control page traffic with JavaScript handlers.</li>
<li><code>webNavigation</code> is used to inspect tab-level navigation events.</li>
</ul>
<h3 id="action">Action</h3>
<p>Control the extension toolbar icon, including appearance, title text, badge content, badge color, popup page, and click handlers. More: <a href="https://developer.chrome.com/docs/extensions/reference/action/" target="_blank" >chrome.action</a>
</p>
<h3 id="notifications">Notifications</h3>
<p>Display rich notifications to the user using the host operating system’s notification mechanism. More: <a href="https://developer.chrome.com/docs/extensions/reference/notifications/" target="_blank" >chrome.notifications</a>
</p>
<h3 id="context-menu">Context menu</h3>
<p>Add interactive options to right click and context menus throughout the browser. More: <a href="https://developer.chrome.com/docs/extensions/reference/contextMenus/" target="_blank" >chrome.contextMenus</a>
</p>
<h2 id="authentication-1">Authentication</h2>
<h3 id="content-script-spoofing">Content script spoofing</h3>
<p>Network requests from a content script are treated the same as network requests from a host page script. Therefore, if a website uses HttpOnly cookie authentication, a content script will be able to piggyback on that authenticated state and send requests that will automatically include those cookies.</p>
<h3 id="cookie-authentication">Cookie authentication</h3>
<p>Technically possible, but not recommended.</p>
<h3 id="jwt-authentication">JWT authentication</h3>
<p>The ideal scenario in browser extensions. Popups, options pages, and background scripts can all directly authenticate with a server and share the authentication token between extension components without issue.</p>
<h3 id="oauth-and-openid">OAuth and OpenID</h3>
<p>With the <code>chrome.identity</code> API, browser extensions have first-class support for OAuth and OpenID.
There are two ways of using OAuth in an extension:</p>
<ul>
<li><code>chrome.identity.getAuthToken()</code> allows you to natively authenticate. This method allows you to skip providing a redirect URL and making the authorization token request. Instead, you simply provide the required values inside the OAuth2 manifest property and call this method. The browser will kick open the OAuth dialog and the method’s callback will be passed the OAuth token. This is by far the simplest way of implementing OAuth in an extension, but it is <em>only available inside Google Chrome</em>. This requires a “Chrome app” client ID.</li>
<li><code>chrome.identity.launchWebAuthFlow()</code> is the more generalized method for using OAuth2. It is cross-browser (works on Firefox, Edge, etc.) and cross-platform (supports OAuth2 with Facebook, Github, etc.). It is much more labor intensive, as it requires you to manually implement each of the OAuth2 steps.</li>
</ul>
<p>To solve the OAuth redirect URL problem, the browser supports a special URL (accessible with <code>chrome.identity.getRedirectURL()</code> method) that will direct the auth flow back to the extension.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Topic Classification of Texts Locally Using BERTopic</title>
      <link>https://saeedesmaili.com/topic-classification-of-texts-locally/</link>
      <pubDate>Tue, 12 Sep 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/topic-classification-of-texts-locally/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been recently working on survey response data that in addition to aggregatable question types like Likert-scale and multiple-choice questions, includes optional free-text questions. Although we are lucky that thousands of the respondents spend time elaborating on questions and leaving comprehensive free-text responses, getting insights from these text responses is challenging.&lt;/p&gt;
&lt;p&gt;While investigating how to enrich this text data with proper metadata related to their topics, I came across &lt;a href=&#34;https://maartengr.github.io/BERTopic&#34; target=&#34;_blank&#34; &gt;BERTopic&lt;/a&gt;
 which introduces itself as a topic modeling technique to create clusters allowing for easily interpretable topics. In this post, I&amp;rsquo;ll explore BERTopic and will go through an example to explain what adjustments worked for me.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I&rsquo;ve been recently working on survey response data that in addition to aggregatable question types like Likert-scale and multiple-choice questions, includes optional free-text questions. Although we are lucky that thousands of the respondents spend time elaborating on questions and leaving comprehensive free-text responses, getting insights from these text responses is challenging.</p>
<p>While investigating how to enrich this text data with proper metadata related to their topics, I came across <a href="https://maartengr.github.io/BERTopic" target="_blank" >BERTopic</a>
 which introduces itself as a topic modeling technique to create clusters allowing for easily interpretable topics. In this post, I&rsquo;ll explore BERTopic and will go through an example to explain what adjustments worked for me.</p>
<h2 id="bertopic">BERTopic</h2>
<p>BERTopic leverages a few popular NLP and clustering algorithms and libraries to classify raw texts into meaningful topics. It reshapes the texts in a sequence of steps, including embedding, dimensionality reduction, clustering, tokenization, and weighting words (<a href="https://maartengr.github.io/BERTopic/algorithm/algorithm.html" target="_blank" >explained in more detail in its docs</a>
):</p>
<p><img alt="BERTopic steps" loading="lazy" src="/images/2023/20230912-bertopic-steps.png"></p>
<p>BERTopic is fundamentally built upon <a href="https://en.wikipedia.org/wiki/BERT_%28language_model%29" target="_blank" >BERT</a>
 embeddings, one of the leading NLP techniques. While traditional topic modeling techniques, like <a href="https://en.wikipedia.org/wiki/Latent_Dirichlet_allocation" target="_blank" >LDA</a>
 rely on probabilistic graphical models to identify topics, BERTopic uses the rich, contextual embeddings from BERT to cluster similar texts. This offers two main advantages:</p>
<ol>
<li><strong>Contextual Understanding</strong>: BERT&rsquo;s deep understanding of context means that words with multiple meanings can be more appropriately clustered based on their actual usage in texts, leading to more accurate topic allocations.</li>
<li><strong>Flexibility</strong>: BERTopic is not just confined to English. Thanks to the multilingual capabilities of BERT, it can be adapted for several languages without significant changes in the approach.</li>
</ol>
<p>The choice of BERTopic over traditional methods largely depends on the specific requirements of a project, but its modern and deep learning-based approach can often yield more nuanced and contextually relevant results.</p>
<h2 id="installing-the-library-and-preparing-the-sample-data">Installing the library and preparing the sample data</h2>
<p>Start by installing the required Python libraries and importing them:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install bertopic sentence-transformers pandas scikit-learn
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> bertopic <span style="color:#f92672">import</span> BERTopic
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> sentence_transformers <span style="color:#f92672">import</span> SentenceTransformer
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> sklearn.feature_extraction.text <span style="color:#f92672">import</span> CountVectorizer
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pickle
</span></span></code></pre></div><p>I will use the <a href="https://github.com/sealuzh/user_quality/raw/master/csv_files/reviews.csv" target="_blank" ><code>app reviews</code></a>
 dataset from <a href="https://github.com/sealuzh/user_quality" target="_blank" >Android Apps and User Feedback project</a>
 published by researchers at the University of Zurich. This dataset consists of 288K reviews extracted from Google Play, but as it makes more sense to limit this exercise to one single app and see the topics of reviews submitted for it, I have picked Telegram:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>df_orig <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>read_csv(<span style="color:#e6db74">&#34;reviews.csv&#34;</span>)
</span></span><span style="display:flex;"><span>df_orig<span style="color:#f92672">.</span>head()
</span></span></code></pre></div><p><img alt="app reviews dataset" loading="lazy" src="/images/2023/20230912-app-reviews-dataset.png"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">## filtering data on Telegram</span>
</span></span><span style="display:flex;"><span>df <span style="color:#f92672">=</span> df_orig[df_orig[<span style="color:#e6db74">&#34;package_name&#34;</span>] <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;org.telegram.messenger&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Getting rid of reviews like &#39;nice&#39;, &#39;good&#39;, etc by keeping only the reviews with at least 10 words</span>
</span></span><span style="display:flex;"><span>df[<span style="color:#e6db74">&#34;n_words&#34;</span>] <span style="color:#f92672">=</span> df[<span style="color:#e6db74">&#34;review&#34;</span>]<span style="color:#f92672">.</span>apply(<span style="color:#66d9ef">lambda</span> x: len(x<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34; &#34;</span>)))
</span></span><span style="display:flex;"><span>df <span style="color:#f92672">=</span> df[df[<span style="color:#e6db74">&#34;n_words&#34;</span>] <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">10</span>]
</span></span></code></pre></div><h2 id="generating-and-storing-embeddings">Generating and storing Embeddings</h2>
<p>The first step in clustering our text data is to convert them to some sort of numerical representation. This can be done using publicly available models, and BERTopic uses the famous <a href="https://www.sbert.net/" target="_blank" >sentence-transformers</a>
 library in this step of the process. If you&rsquo;re interested in knowing more about how <code>sentence-transformers</code> works, check out their docs or take a look at <a href="https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/" target="_blank" >my blog post</a>
 which is a brief explanation of how to use this library for embedding texts locally.</p>
<p>We don&rsquo;t necessarily need to import <code>sentence-transformers</code> and do the embeddings step outside the topic generation step as it&rsquo;s managed by the BERTopic library behind the scenes. But since this is a computationally costly process, we will generate the embeddings once and will store them in our machine&rsquo;s memory or disk, and pass it to our topic generation model as many times as we want. This will enable us to experiment with various hyperparameters of BERTopic quickly and conveniently.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>docs <span style="color:#f92672">=</span> df[<span style="color:#e6db74">&#34;review&#34;</span>]<span style="color:#f92672">.</span>values<span style="color:#f92672">.</span>tolist()
</span></span><span style="display:flex;"><span>print(len(docs))
</span></span><span style="display:flex;"><span>docs[:<span style="color:#ae81ff">5</span>]
</span></span></code></pre></div><p><img alt="telegram reviews sample" loading="lazy" src="/images/2023/20230912-telegram-reviews-sample.png"></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">## Pre-calculating embeddings</span>
</span></span><span style="display:flex;"><span>embedding_model <span style="color:#f92672">=</span> SentenceTransformer(<span style="color:#e6db74">&#34;all-MiniLM-L6-v2&#34;</span>)
</span></span><span style="display:flex;"><span>embeddings <span style="color:#f92672">=</span> embedding_model<span style="color:#f92672">.</span>encode(docs, show_progress_bar<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Storing embeddings on the disk</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;embeddings.pkl&#34;</span>, <span style="color:#e6db74">&#34;wb&#34;</span>) <span style="color:#66d9ef">as</span> f:
</span></span><span style="display:flex;"><span>    pickle<span style="color:#f92672">.</span>dump(embeddings, f)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## We can load the stored embeddings later on if needed</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#34;embeddings.pkl&#34;</span>, <span style="color:#e6db74">&#34;rb&#34;</span>) <span style="color:#66d9ef">as</span> f:
</span></span><span style="display:flex;"><span>    embeddings <span style="color:#f92672">=</span> pickle<span style="color:#f92672">.</span>load(f)
</span></span></code></pre></div><h2 id="classify-reviews-into-topics">Classify reviews into topics</h2>
<p>We have another optional but beneficial step before generating topics. In my first attempt to classify texts into topics using BERTopic, meaningless words like &ldquo;in, an, at, for, etc&rdquo; had a significant presence in topic representations and some of the final categories had no semantic meaning. We can ask BERTopic to skip these words (which are called <code>stopwords</code> in the NLP world) by creating a vectorizer model and passing it to our topic generation model:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>vectorizer_model <span style="color:#f92672">=</span> CountVectorizer(stop_words<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;english&#34;</span>, ngram_range<span style="color:#f92672">=</span>(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>))
</span></span></code></pre></div><p>Use the following instead if you want to add some custom stopwords to be skipped:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> nltk.corpus <span style="color:#f92672">import</span> stopwords
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>default_stopwords <span style="color:#f92672">=</span> stopwords<span style="color:#f92672">.</span>words(<span style="color:#e6db74">&#34;english&#34;</span>)
</span></span><span style="display:flex;"><span>custom_stopwords <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;my&#34;</span>, <span style="color:#e6db74">&#34;custom&#34;</span>, <span style="color:#e6db74">&#34;list&#34;</span>, <span style="color:#e6db74">&#34;of&#34;</span>, <span style="color:#e6db74">&#34;words&#34;</span>]
</span></span><span style="display:flex;"><span>stopwords_all <span style="color:#f92672">=</span> default_stopwords <span style="color:#f92672">+</span> custom_stopwords
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>vectorizer_model <span style="color:#f92672">=</span> CountVectorizer(stop_words<span style="color:#f92672">=</span>stopwords_all, ngram_range<span style="color:#f92672">=</span>(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span>))
</span></span></code></pre></div><p>Here I&rsquo;m using the default value for <code>ngram_range</code> (so it can also be dropped from the above code). <code>ngram_range</code> is used to determine the range of n-grams extracted by our topic generation model. In simple terms, if we have so many words with two or more words (like <code>New York</code>, <code>tech debt</code>, etc) and we want to capture them accordingly, <code>ngram_range</code> should probably be set to <code>(1, 2)</code> for example, but I experimented with this and final results always seemed more useful with <code>ngram_range=(1, 1)</code>.</p>
<p>Now let&rsquo;s classify the texts into topics:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>topic_model <span style="color:#f92672">=</span> BERTopic(
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Pipeline models</span>
</span></span><span style="display:flex;"><span>  embedding_model<span style="color:#f92672">=</span>embedding_model,
</span></span><span style="display:flex;"><span>  vectorizer_model<span style="color:#f92672">=</span>vectorizer_model,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Hyperparameters</span>
</span></span><span style="display:flex;"><span>  top_n_words<span style="color:#f92672">=</span><span style="color:#ae81ff">10</span>,
</span></span><span style="display:flex;"><span>  verbose<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>topics, probs <span style="color:#f92672">=</span> topic_model<span style="color:#f92672">.</span>fit_transform(documents<span style="color:#f92672">=</span>docs, embeddings<span style="color:#f92672">=</span>embeddings)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>topic_model<span style="color:#f92672">.</span>get_topic_info()
</span></span></code></pre></div><p><img alt="bertopic output" loading="lazy" src="/images/2023/20230912-bertopic-output.png"></p>
<p>So we have 100 topics, but let&rsquo;s see what pieces of information we have for each topic:</p>
<ul>
<li><code>Topic</code>: A numerical ID for each topic. As you see in the picture above, the numbers start from <code>-1</code>. The texts in the <code>-1</code> topic are not related to each other in a meaningful way, and the only reason they are in the same bucket is that BERTopic hasn&rsquo;t been able to find proper topics for them.</li>
<li><code>Count</code>: Number of texts (rows in the dataframe) that are classified into this topic.</li>
<li><code>Name</code>: Numerical ID of the topic combined with the four most occurring words in this topic.</li>
<li><code>Representation</code>: These are the words that occur most often in the texts classified in this topic. The number of words in this list is determined by the <code>top_n_words</code> parameter above and it has a default value of <code>10</code>. It is recommended to keep it low, otherwise you&rsquo;ll get so few items classified in each topic and the number of outliers will increase.</li>
<li><code>Representative_Docs</code>: List of the texts that are classified into this topic.</li>
</ul>
<p>To get a more practical view of how each text has been classified, we can use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>topic_model<span style="color:#f92672">.</span>get_document_info(docs)
</span></span></code></pre></div><p><img alt="bertopic output" loading="lazy" src="/images/2023/20230912-bertopic-output-docs.png"></p>
<p>And what are the representations (top words) of our main topics?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">5</span>):
</span></span><span style="display:flex;"><span>    print(topic_model<span style="color:#f92672">.</span>get_topic(i), <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><p><img alt="bertopic output" loading="lazy" src="/images/2023/20230912-bertopic-output-top.png"></p>
<p>Finally, we can save our topic model to re-use later without computing them from scratch:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>topic_model<span style="color:#f92672">.</span>save(<span style="color:#e6db74">&#34;topic-model&#34;</span>, serialization<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;safetensors&#34;</span>, save_ctfidf<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, save_embedding_model<span style="color:#f92672">=</span>embedding_model)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Later load it with:</span>
</span></span><span style="display:flex;"><span>BERTopic<span style="color:#f92672">.</span>load(<span style="color:#e6db74">&#34;topic-model&#34;</span>)
</span></span></code></pre></div><h2 id="reducing-outliers">Reducing outliers</h2>
<p>As we saw earlier, a significant number of texts have been classified as outliers (which means they don&rsquo;t have any topics assigned to them). Upon manually examining a few of these outliers, they seemed to belong to one of the topics if we don&rsquo;t want to be very strict about our criteria. BERTopic provides a simple way for reducing these outliers and re-assigning them to our existing topics:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>new_topics <span style="color:#f92672">=</span> topic_model<span style="color:#f92672">.</span>reduce_outliers(documents<span style="color:#f92672">=</span>docs, topics<span style="color:#f92672">=</span>topics, strategy<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;embeddings&#34;</span>, embeddings<span style="color:#f92672">=</span>embeddings)
</span></span><span style="display:flex;"><span>topic_model<span style="color:#f92672">.</span>update_topics(docs<span style="color:#f92672">=</span>docs, topics<span style="color:#f92672">=</span>new_topics, vectorizer_model<span style="color:#f92672">=</span>vectorizer_model)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>topic_model<span style="color:#f92672">.</span>get_topic_info()
</span></span></code></pre></div><p><img alt="bertopic output" loading="lazy" src="/images/2023/20230912-bertopic-output-reduced-outliers.png"></p>
<p>There is no <code>-1</code> topic anymore and all of the texts belong to one of the topic classifications now. Here I&rsquo;ve used the <code>embeddings</code> strategy, but there are other strategies explained in <a href="https://maartengr.github.io/BERTopic/getting_started/outlier_reduction/outlier_reduction.html" target="_blank" >BERTopic&rsquo;s documentation</a>
.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In wrapping up, topic modeling is an incredibly powerful tool for extracting insights from large volumes of text data. With advancements in natural language processing, tools like BERTopic are making this task not only more accurate but also more interpretable. As we&rsquo;ve walked through in this post, setting up and using BERTopic is relatively straightforward, and its adaptability means it can be fine-tuned for various datasets and requirements. If you&rsquo;re dealing with a sizeable text dataset and are keen to unearth the hidden topics within, BERTopic is undoubtedly worth a try.</p>
<p>There are a bunch of hyperparameters that can be tweaked to improve the results, but in the end, it mainly depends on the use cases and what we&rsquo;re trying to achieve. I&rsquo;ll be experimenting more with BERTopic in the future, so <a href="mailto:me@saeedesmaili.com" >let me know</a>
 if you have a question you want to be answered about this approach for classifying texts.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Text Chunking and Headings Grouping: A Guide to Parsing Documents with Pandoc and Python</title>
      <link>https://saeedesmaili.com/text-chunking-headings-grouping-parsing-documents-with-pandoc/</link>
      <pubDate>Sat, 08 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/text-chunking-headings-grouping-parsing-documents-with-pandoc/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://saeedesmaili.com/demystifying-text-data-with-the-unstructured-python-library/&#34; target=&#34;_blank&#34; &gt;my previous blog post&lt;/a&gt;
 I explored using the &lt;code&gt;unstructured&lt;/code&gt; python library for loading and parsing documents. As I mentioned in the post, although &lt;code&gt;unstructured&lt;/code&gt; seems a very useful library, it has a few issues. Since I&amp;rsquo;m planning to do a semantic search on the paragraphs and feed the relevant ones to a large language model, the library&amp;rsquo;s inability to reliably identify headings and paragraphs was a big problem for me.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>In <a href="https://saeedesmaili.com/demystifying-text-data-with-the-unstructured-python-library/" target="_blank" >my previous blog post</a>
 I explored using the <code>unstructured</code> python library for loading and parsing documents. As I mentioned in the post, although <code>unstructured</code> seems a very useful library, it has a few issues. Since I&rsquo;m planning to do a semantic search on the paragraphs and feed the relevant ones to a large language model, the library&rsquo;s inability to reliably identify headings and paragraphs was a big problem for me.</p>
<p>I then found a way to use <code>pandoc</code> with python, and this led me to spend a few hours trying it out to see if it improve my document parsing process and provide me more reliable results. And it did! In this post, I&rsquo;ll walk you through how I used <code>pandoc</code> to load <code>docx</code> and <code>md</code> documents, create a table of content for each document, split the documents to paragraphs, and include the parent heading of each paragraph in the chunks, all using python.</p>
<h2 id="getting-started">Getting started</h2>
<p>First of all, you need to make sure you have <a href="https://pandoc.org/installing.html" target="_blank" >pandoc installed</a>
 on your machine. I will bring <code>pandoc</code> to python using <a href="https://github.com/amoffat/sh" target="_blank" >sh</a>
, but this can be achieved with <a href="https://github.com/JessicaTegner/pypandoc" target="_blank" >pypandoc</a>
 as well. To follow along with me, you can download these three sample <code>docx</code> files: <a href="/files/20230708-sample-doc-with-title.docx" >1</a>
, <a href="/files/20230708-sample-doc-without-title.docx" >2</a>
, <a href="/files/20230708-sample-doc-no-title-and-heading.docx" >3</a>
, and put them in the <code>/corpus</code> directory.</p>
<p>To read a document and get a plain text output we can use this simple line of code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> sh <span style="color:#f92672">import</span> pandoc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;corpus/sample-doc-without-title.docx&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>doc <span style="color:#f92672">=</span> str(pandoc(path, <span style="color:#e6db74">&#34;-t&#34;</span>, <span style="color:#e6db74">&#34;plain&#34;</span>, <span style="color:#e6db74">&#34;--toc&#34;</span>, <span style="color:#e6db74">&#34;--standalone&#34;</span>))
</span></span><span style="display:flex;"><span>print(doc)
</span></span></code></pre></div><p><img alt="pandoc plain text" loading="lazy" src="/images/2023/20230708-pandoc1.png">
<code>path</code> is the document we want to load and parse (it can be a <code>docx</code>, <code>md</code>, or so many other formats that <code>pandoc</code> supports). Using <code>-t</code> and <code>plain</code> we ask <code>pandoc</code> to convert the document to a plaintext format, and <code>--toc</code> and <code>--standalone</code> flags are needed to generate the nice table of content at the beginning of the output.</p>
<p>This is already a very nice and usable output, but I want to split the text into paragraphs and prepend the parent heading to each paragraph as <code>Heading [SEP] paragraph text</code>, so the important content of headings isn&rsquo;t lost when I use semantic search.</p>
<h2 id="parsing-the-headings">Parsing the headings</h2>
<p>I experimented with various <code>docx</code> and <code>md</code> files to find out the different types of TOC and text I could possibly get, and the main three types are:</p>
<ul>
<li><a href="/files/20230708-sample-doc-with-title.docx" >Docs with title and headings</a>
</li>
</ul>
<p>plaintext output when printed:</p>
<p><img alt="pandoc plain text with title" loading="lazy" src="/images/2023/20230708-pandoc2.png"></p>
<p>raw plaintext:</p>
<blockquote>
<p>Sample doc with title\n\n\n- Introduction\n - A child heading of introduction\n- Summary\n\nIntroduction\n\nLorem Ipsum is simply dummy text of the printing and typesetting\nindustry. Lorem Ipsum has been the industry&rsquo;s standard dummy text ever\nsince the 1500s, when an unknown printer took a galley of type and\nscrambled it to make a type specimen book. It has survived not only five\ncenturies, but also the leap into electronic typesetting, remaining\nessentially unchanged. It was popularised in the 1960s with the release\nof Letraset sheets containing Lorem Ipsum passages, and more recently\nwith desktop publishing software like Aldus PageMaker including versions\nof Lorem Ipsum.\n</p></blockquote>
<ul>
<li><a href="/files/20230708-sample-doc-without-title.docx" >Docs without title but with headings</a>
</li>
</ul>
<p>plaintext output when printed:</p>
<p><img alt="pandoc plain text without title" loading="lazy" src="/images/2023/20230708-pandoc3.png"></p>
<p>raw plaintext:</p>
<blockquote>
<ul>
<li>Introduction\n - A child heading of introduction\n- Summary\n\nIntroduction\n\nLorem Ipsum is simply dummy text of the printing and typesetting\nindustry. Lorem Ipsum has been the industry&rsquo;s standard dummy text ever\nsince the 1500s, when an unknown printer took a galley of type and\nscrambled it to make a type specimen book. It has survived not only five\ncenturies, but also the leap into electronic typesetting, remaining\nessentially unchanged. It was popularised in the 1960s with the release\nof Letraset sheets containing Lorem Ipsum passages, and more recently\nwith desktop publishing software like Aldus PageMaker including versions\nof Lorem Ipsum.\n</li>
</ul></blockquote>
<ul>
<li><a href="/files/20230708-sample-doc-no-title-and-heading.docx" >Docs without any title or headings</a>
</li>
</ul>
<p>plaintext output when printed:</p>
<p><img alt="pandoc plain text without title and heading" loading="lazy" src="/images/2023/20230708-pandoc4.png"></p>
<p>raw plaintext:</p>
<blockquote>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting\nindustry. Lorem Ipsum has been the industry&rsquo;s standard dummy text ever\nsince the 1500s, when an unknown printer took a galley of type and\nscrambled it to make a type specimen book. It has survived not only five\ncenturies, but also the leap into electronic typesetting, remaining\nessentially unchanged. It was popularised in the 1960s with the release\n</p></blockquote>
<p>This python function will identify the type of a document:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> re
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">identify_doc_type</span>(doc):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    categorizes a plaintext doc based on the format of the toc.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> re<span style="color:#f92672">.</span>search(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;.*\n\n\n-\s</span><span style="color:#e6db74">{3}</span><span style="color:#e6db74">.*&#39;</span>, doc):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;TOC_WITH_TITLE&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> re<span style="color:#f92672">.</span>search(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;-\s</span><span style="color:#e6db74">{3}</span><span style="color:#e6db74">.*\n\n.*&#39;</span>, doc):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;TOC_WITHOUT_TITLE&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;NO_TOC_TITLE&#34;</span>
</span></span></code></pre></div><p>And it can be used for splitting the table of content from the text when reading the doc:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">read_doc</span>(path):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    reads a text file and returns toc and full text.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    doc <span style="color:#f92672">=</span> str(pandoc(path, <span style="color:#e6db74">&#34;-t&#34;</span>, <span style="color:#e6db74">&#34;plain&#34;</span>, <span style="color:#e6db74">&#34;--toc&#34;</span>, <span style="color:#e6db74">&#34;--standalone&#34;</span>))
</span></span><span style="display:flex;"><span>    doc_type <span style="color:#f92672">=</span> identify_doc_type(doc)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> doc_type <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;TOC_WITH_TITLE&#34;</span>:
</span></span><span style="display:flex;"><span>        doc <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">&#39;.*</span><span style="color:#ae81ff">\n\n\n</span><span style="color:#e6db74">-&#39;</span>, <span style="color:#e6db74">&#39;-&#39;</span>, doc)
</span></span><span style="display:flex;"><span>        toc, text <span style="color:#f92672">=</span> doc<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\n\n</span><span style="color:#e6db74">&#39;</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> doc_type <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;TOC_WITHOUT_TITLE&#34;</span>:
</span></span><span style="display:flex;"><span>        toc, text <span style="color:#f92672">=</span> doc<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\n\n</span><span style="color:#e6db74">&#39;</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        toc, text <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>, doc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> toc, text
</span></span></code></pre></div><p>And it&rsquo;s always nice to clean up the paragraphs to remove unnecessary new line characters, images, etc:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">cleanup_plaintext</span>(text):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    gets the full text of a document and returns cleaned-up text.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Remove images</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> text<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;[image]&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> text<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;[]&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Replace single \n with space (if the next char is not \n or -)</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">&#39;(?&lt;!</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">)</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">(?!(</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">|-))&#39;</span>, <span style="color:#e6db74">&#39; &#39;</span>, text)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Replace any sequence of two or more newlines with \n\n</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">{2,}&#39;</span>, <span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\n\n</span><span style="color:#e6db74">&#39;</span>, text)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Replace multiple spaces with single space</span>
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">&#39;(?&lt;!</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">) +&#39;</span>, <span style="color:#e6db74">&#39; &#39;</span>, text)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> text
</span></span></code></pre></div><p>Now that we know what are the headings of our document, we can split the text into paragraphs while prepending the parent headings:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">split_text</span>(toc, text):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    gets the toc and cleaned text, and returns chunks of texts:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    [&#34;Heading [SEP] Text&#34;, ]
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    headings <span style="color:#f92672">=</span> [line<span style="color:#f92672">.</span>strip(<span style="color:#e6db74">&#39;- </span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#39;</span>) <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> toc<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#39;</span>)]
</span></span><span style="display:flex;"><span>    paragraphs <span style="color:#f92672">=</span> text<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    current_heading <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    list_group <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    text_chunks <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> para <span style="color:#f92672">in</span> paragraphs:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># use the new heading if we&#39;ve moved to a new heading section</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> len(headings) <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">and</span> para <span style="color:#f92672">==</span> headings[<span style="color:#ae81ff">0</span>]:
</span></span><span style="display:flex;"><span>            current_heading <span style="color:#f92672">=</span> headings[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>            headings<span style="color:#f92672">.</span>pop(<span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># group bullet points as a single chunk of text</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> para<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#34;- &#34;</span>):
</span></span><span style="display:flex;"><span>            list_group <span style="color:#f92672">+=</span> para <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; &#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> list_group <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span>:
</span></span><span style="display:flex;"><span>            para <span style="color:#f92672">=</span> list_group
</span></span><span style="display:flex;"><span>            list_group <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># if we&#39;re at the beginning of a document and</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># we haven&#39;t seen any headings yet</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> current_heading <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span>:
</span></span><span style="display:flex;"><span>            text_chunks<span style="color:#f92672">.</span>append(para<span style="color:#f92672">.</span>strip())
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            text_chunks<span style="color:#f92672">.</span>append(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>current_heading<span style="color:#e6db74">}</span><span style="color:#e6db74"> [SEP] </span><span style="color:#e6db74">{</span>para<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#f92672">.</span>strip())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> text_chunks
</span></span></code></pre></div><p>Note that this will group bullet point lists in a single paragraph, as their content most probably is related to each other.</p>
<p>Let&rsquo;s finalize this process and get the text chunks for all the documents in a directory and store them in a <code>pandas</code> dataframe:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>DataFrame()
</span></span><span style="display:flex;"><span>root_dir <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;corpus&#39;</span>
</span></span><span style="display:flex;"><span>allowed_filetypes <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;.md&#39;</span>, <span style="color:#e6db74">&#39;.docx&#39;</span>, <span style="color:#e6db74">&#39;.pdf&#39;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> directory, subdirectories, files <span style="color:#f92672">in</span> os<span style="color:#f92672">.</span>walk(root_dir):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> file <span style="color:#f92672">in</span> files:
</span></span><span style="display:flex;"><span>        filename, filetype <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>splitext(file)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> filetype <span style="color:#f92672">in</span> allowed_filetypes:
</span></span><span style="display:flex;"><span>            full_path <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>join(directory, file)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            toc, text <span style="color:#f92672">=</span> read_doc(full_path)
</span></span><span style="display:flex;"><span>            text_cleaned <span style="color:#f92672">=</span> cleanup_plaintext(text)
</span></span><span style="display:flex;"><span>            text_chunks <span style="color:#f92672">=</span> split_text(toc, text_cleaned)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            df_new <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>DataFrame(text_chunks, columns<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;text&#34;</span>])
</span></span><span style="display:flex;"><span>            df_new[[<span style="color:#e6db74">&#34;directory&#34;</span>, <span style="color:#e6db74">&#34;filename&#34;</span>, <span style="color:#e6db74">&#34;filetype&#34;</span>]] <span style="color:#f92672">=</span> directory, filename, filetype
</span></span><span style="display:flex;"><span>            df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>concat([df, df_new])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df<span style="color:#f92672">.</span>reset_index(drop<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, inplace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p>The result is a nice dataframe with texts ready to be converted to vectors using <a href="https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/" target="_blank" >an embedding model</a>
:</p>
<p><img alt="pandoc converted to pandas" loading="lazy" src="/images/2023/20230708-pandoc-to-pandas.png"></p>
<h2 id="are-we-done">Are we done?</h2>
<p>Our cleaning and splitting process still can be improved, as there are a few issues with the current approach:</p>
<ul>
<li>We should parse or remove footnotes and their references in the plaintext output. They appear in the text as <code>[N]</code> and at the end of the text as <code>[N] some footnote text</code>).</li>
<li>We can preserve even more heading information in the final text chunks by including all the parent headings (e.g. <code>Heading 1 [SEP] Heading 2 [SEP] Paragraph text</code>).</li>
<li>We can include filenames in the final text chunks, as they also contain useful information.</li>
<li>The code blocks (in <code>md</code> files for example) are grouped as normal text paragraphs. This is a good enough approach for my documents, but it&rsquo;s not ideal.</li>
<li>If our documents include tables, our cleanup process considers them as normal text paragraphs. So tables end up as chunks with so many dashed lines and a more robust parsing approach is needed to preserve their information.</li>
<li>We&rsquo;re not considering the length of each chunk when splitting the text. Given that each embedding model has a specific max token size, we should add another step to split the chunks if they are larger than a specific size.</li>
</ul>
<p>Please <a href="mailto:me@saeedesmaili.com" >reach out</a>
 if you have suggestions for any of these problems or any improvement ideas for my parsing functions.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Demystifying Text Data with the unstructured Python Library (&#43;alternatives)</title>
      <link>https://saeedesmaili.com/demystifying-text-data-with-the-unstructured-python-library/</link>
      <pubDate>Wed, 05 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/demystifying-text-data-with-the-unstructured-python-library/</guid>
      <description>&lt;p&gt;In the world of data, textual data stands out as being particularly complex. It doesn&amp;rsquo;t fall into neat rows and columns like numerical data does. As a side project, I&amp;rsquo;m in the process of developing my own personal AI assistant. The objective is to use the data within my notes and documents to answer my questions. The important benefit is all data processing will occure locally on my computer, ensuring that no documents are uploaded to the cloud, and my documents will remain private.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>In the world of data, textual data stands out as being particularly complex. It doesn&rsquo;t fall into neat rows and columns like numerical data does. As a side project, I&rsquo;m in the process of developing my own personal AI assistant. The objective is to use the data within my notes and documents to answer my questions. The important benefit is all data processing will occure locally on my computer, ensuring that no documents are uploaded to the cloud, and my documents will remain private.</p>
<p>To handle such unstructured data, I&rsquo;ve found the <a href="https://unstructured-io.github.io/unstructured/index.html" target="_blank" ><code>unstructured</code></a>
 Python library to be extremely useful. It&rsquo;s a flexible tool that works with various document formats, including Markdown, , XML, and HTML documents.</p>
<h2 id="starting-with-unstructured">Starting with <code>unstructured</code></h2>
<p>You can easily install the library by:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install unstructured
</span></span></code></pre></div><h2 id="loading-and-partitioning-a-document">Loading and partitioning a document</h2>
<p>The first thing you&rsquo;ll want to do with your document is split it up into smaller parts or sections. This process, called partitioning, makes it easier to categorize and extract text.</p>
<p>Here&rsquo;s how you do it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> unstructured.partition.auto <span style="color:#f92672">import</span> partition
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>elements <span style="color:#f92672">=</span> partition(filename<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;example-docs/note.md&#34;</span>)
</span></span></code></pre></div><p>example-docs/note.md:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 216 57"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='192' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>.</text>
</g>

    </svg>
  
</div>
<p>When we partition a document, the output is a list of document <code>Element</code> objects. These element objects represent different components of the source document. The <code>unstructured</code> library supports various element types including <code>Title</code>, <code>NarrativeText</code>, and <code>ListItem</code>. To access the element type you can use the <code>category</code> method:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> element <span style="color:#f92672">in</span> elements:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">{</span>element<span style="color:#f92672">.</span>category<span style="color:#e6db74">}</span><span style="color:#e6db74">:&#34;</span>)
</span></span><span style="display:flex;"><span>    print(element)
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><p>Output:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 216 105"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='8' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='8' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'>.</text>
</g>

    </svg>
  
</div>
<p>The list of document elements can be converted to a list of dictionaries using the <code>convert_to_dict</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> unstructured.staging.base <span style="color:#f92672">import</span> convert_to_dict
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>dict_data <span style="color:#f92672">=</span> convert_to_dict(elements)
</span></span></code></pre></div><p>Output:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 416 329"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='8' y='164' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='100' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='196' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='212' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='228' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='244' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='260' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='308' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='24' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='24' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='24' y='116' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='24' y='132' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='24' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='24' y='196' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='24' y='212' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='24' y='228' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='24' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='24' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='24' y='276' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='24' y='292' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='24' y='308' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='32' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='32' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='32' y='212' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='244' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='276' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='32' y='292' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='32' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='40' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='212' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='40' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='276' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='40' y='292' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='308' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='48' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='48' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='244' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='48' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='48' y='276' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='48' y='292' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='48' y='308' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='56' y='164' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='196' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='212' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='56' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='260' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='292' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='308' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='64' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='244' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='64' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='276' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='292' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='64' y='308' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='212' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='72' y='244' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='72' y='276' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='292' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='212' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='80' y='244' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='80' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='276' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='80' y='292' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='80' y='308' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='212' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='244' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='260' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='88' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='292' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='88' y='308' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='244' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='96' y='260' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='96' y='276' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='96' y='292' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='96' y='308' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='104' y='196' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='104' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='104' y='244' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='104' y='276' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='104' y='292' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='308' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='180' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='112' y='212' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='112' y='244' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='112' y='260' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='112' y='292' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='20' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='120' y='180' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='120' y='196' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='120' y='212' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='260' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='276' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='292' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='120' y='308' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='212' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='128' y='244' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='128' y='260' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='128' y='276' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='292' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='128' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='136' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='228' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='136' y='244' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='136' y='260' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='136' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='308' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='144' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='212' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='144' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='260' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='144' y='276' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='144' y='292' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='144' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>}</text>
<text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='152' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='228' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='152' y='244' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='152' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='276' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='292' fill='currentColor' style='font-size:1em'>}</text>
<text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='160' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='160' y='196' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='160' y='212' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='160' y='244' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='160' y='260' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='276' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='160' y='308' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='168' y='84' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='180' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='168' y='196' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='168' y='212' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='168' y='244' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='168' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='168' y='276' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='168' y='308' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='176' y='116' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='176' y='212' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='176' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='244' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='176' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='176' y='276' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='116' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='196' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='184' y='228' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='184' y='244' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='184' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='276' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='184' y='308' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='192' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='192' y='116' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='192' y='164' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='192' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='192' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='192' y='260' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='192' y='276' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='200' y='116' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'>}</text>
<text text-anchor='middle' x='200' y='164' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='200' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='200' y='244' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='200' y='260' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='200' y='276' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='200' y='308' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='84' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='208' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='208' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='276' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='208' y='308' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='216' y='84' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='216' y='116' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='216' y='196' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='216' y='244' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='216' y='260' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='216' y='276' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='216' y='308' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='224' y='84' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='224' y='116' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='224' y='244' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='224' y='260' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='224' y='276' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='224' y='308' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='232' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='232' y='116' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='232' y='244' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='232' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='232' y='276' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='232' y='308' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='240' y='84' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='240' y='116' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='240' y='244' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='240' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='240' y='276' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='240' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='248' y='84' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='248' y='244' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='248' y='260' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='256' y='244' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='256' y='260' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='256' y='308' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='244' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='264' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='272' y='244' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='272' y='260' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='272' y='308' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='280' y='244' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='280' y='260' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='280' y='308' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='244' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='288' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='308' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='296' y='244' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='296' y='260' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='296' y='308' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='304' y='244' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='304' y='260' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='304' y='308' fill='currentColor' style='font-size:1em'>}</text>
<text text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='312' y='244' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='312' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='312' y='308' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='320' y='84' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='320' y='244' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='320' y='260' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='328' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='328' y='100' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='328' y='244' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='328' y='260' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='336' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='336' y='100' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='336' y='244' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='336' y='260' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='344' y='84' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='344' y='244' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='352' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='352' y='244' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='360' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='360' y='244' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='368' y='84' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='368' y='244' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='376' y='84' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='376' y='244' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='384' y='84' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='384' y='244' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='392' y='84' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='392' y='244' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='400' y='84' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='400' y='244' fill='currentColor' style='font-size:1em'>,</text>
</g>

    </svg>
  
</div>
<p>But since I want to store the chunks of texts in a database and do some exploratory analysis with the data, I used <code>convert_to_dataframe</code> function to convert the text elements into pandas dataframe:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> unstructured.staging.base <span style="color:#f92672">import</span> convert_to_dataframe
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df <span style="color:#f92672">=</span> convert_to_dataframe(elements)
</span></span></code></pre></div><p><img alt="unstructured to pandas dataframe" loading="lazy" src="/images/2023/20230705-unstructured-dataframe.png"></p>
<h2 id="gettng-the-metadata">Gettng the metadata</h2>
<p>One neat feature of the <code>unstructured</code> library is how it keeps track of various metadata about the elements it extracts from documents. For example, you might want to know which elements come from which page number. You can extract the metadata for a given document element like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>doc_metadata <span style="color:#f92672">=</span> elements[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>metadata<span style="color:#f92672">.</span>to_dict()
</span></span><span style="display:flex;"><span>print(doc_metadata)
</span></span></code></pre></div><p>Output:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 568 25"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='456' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='464' y='4' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='472' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='480' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='488' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='496' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='504' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='512' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='520' y='4' fill='currentColor' style='font-size:1em'>'</text>
<text text-anchor='middle' x='528' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='544' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='552' y='4' fill='currentColor' style='font-size:1em'>}</text>
</g>

    </svg>
  
</div>
<p>All document types return the following metadata fields when the information is available from the source file: <code>filename</code>, <code>file_directory</code>, <code>date</code>, <code>filetype</code>, and<code>page_number</code>.</p>
<h2 id="preparing-for-transformers">Preparing for Transformers</h2>
<p>When you&rsquo;re ready to feed your text into a transformer model for further processing, you can use the <code>stage_for_transformers</code> function. This function prepares your text elements by splitting them into chunks that fit into the model&rsquo;s attention window.</p>
<p>In the following example, I&rsquo;m using a library called <code>SentenceTransformers</code> (I&rsquo;ve written more about using this library in <a href="https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/" target="_blank" >my previous blog post</a>
):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> sentence_transformers <span style="color:#f92672">import</span> SentenceTransformer
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> unstructured.staging.huggingface <span style="color:#f92672">import</span> stage_for_transformers
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>model <span style="color:#f92672">=</span> SentenceTransformer(<span style="color:#e6db74">&#34;all-MiniLM-L6-v2&#34;</span>)
</span></span><span style="display:flex;"><span>chunked_elements <span style="color:#f92672">=</span> stage_for_transformers(elements, model<span style="color:#f92672">.</span>tokenizer)
</span></span></code></pre></div><p>And now I can load all the notes in a specific directory, so I can convert them to embedding vectors later:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>all_elements <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>root_dir <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;/corpus&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> directory, subdirectories, files <span style="color:#f92672">in</span> os<span style="color:#f92672">.</span>walk(root_dir):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> file <span style="color:#f92672">in</span> files:
</span></span><span style="display:flex;"><span>        full_path <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>join(directory, file)
</span></span><span style="display:flex;"><span>        all_elements <span style="color:#f92672">+=</span> partition(filename<span style="color:#f92672">=</span>full_path)
</span></span></code></pre></div><h2 id="limitations-of-unstructured">Limitations of <code>unstructured</code></h2>
<p>This library has some issues and limitations as well.</p>
<ul>
<li>When loading and parsing <code>docx</code> files, it can&rsquo;t properly recognize bullet points as <code>ListItem</code> and most of the times labels them as <code>NarrativeText</code> or <code>Title</code>. This makes the <code>Title</code> recognition unreliable as well, since when you look into the output, you can&rsquo;t tell for sure if each <code>Title</code> is actually a title or a list item labeled incorrectly as a <code>Title</code>. (<a href="https://github.com/Unstructured-IO/unstructured/issues/768" target="_blank" >issue on github</a>
)</li>
<li>When working with large documents, there isn&rsquo;t any way to know what are the parents of each paragraph or title. This could be a very useful feature to have, specially when feeding back the data to an LLM. (<a href="https://github.com/Unstructured-IO/unstructured/issues/889" target="_blank" >issue on github</a>
)</li>
</ul>
<h2 id="alternatives">Alternatives</h2>
<p>After playing with <code>unstructured</code> I tried to see if there are better alternatives for reading documents with python. Although I will need to load documents with various formats, I narrowed down my search to first find alternatives for reading <code>docx</code> files first (as this it the format you get when downloading a large folder of documents from Google Drive). Here are what I found:</p>
<h3 id="python-docx"><code>python-docx</code></h3>
<ul>
<li>It seems powerful, but it&rsquo;s complicated to work with.</li>
<li>I tried loading and parsing a few <code>docx</code> files. The biggest issue I experienced was with loading any text that includes hyperlinks. For some unknown reason, the texts of hyperlinks are returned empty in the final output. This makes it unusable for my purpose, since the link texts provide valuable information in the text.</li>
<li>Pro: It is able to provide the heading level info for titles (as <code>Heading 1</code>, <code>Heading 2</code>, etc).</li>
</ul>
<h3 id="docx2txt"><code>docx2txt</code></h3>
<ul>
<li>It uses <code>python-docx</code> under the hood.</li>
<li>Only returns a giant full text string of the loaded document. This would require me to split my documents to meaningful chunks, which is not a trivial task to do.</li>
<li>Pro: It doesn&rsquo;t have any problems with hyperlinks and the output text is readable and useful.</li>
<li>Pro: It is also very easy to use.</li>
</ul>
<h3 id="simplify_docx"><code>simplify_docx</code></h3>
<ul>
<li>It works on top of <code>python-docx</code>.</li>
<li>This library basically converts the complicated output of <code>python-docx</code> to a more easy to use json output.</li>
<li>It also have the same issue with hyperlinks and returns empty texts when there is a link in the paragraph.</li>
</ul>
<p>So I will continue using <code>unstructured</code> for now. It&rsquo;s worth mentioning that, yes, this could be accomplished more easily using <a href="https://python.langchain.com/docs/get_started/introduction" target="_blank" >LangChain</a>
 or other similar tools. However, part of my motivation in building this personal AI assistant is the learning journey. By using <code>unstructured</code> to load documents and other similar tools for embeddings and so on, I&rsquo;m gaining a deeper understanding of the underlying processes, rather than using a one-stop-shop solution like <code>LangChain</code>.</p>
<p>I&rsquo;ll be sharing more about the progress I&rsquo;m making in building this personal AI assistant in future posts, so stay tuned.</p>
<p>Update 2023-07-07:</p>
<h2 id="using-pandoc">Using <code>pandoc</code></h2>
<p><a href="https://news.ycombinator.com/item?id=36626533" target="_blank" >Raphael</a>
 suggested using <code>pandoc</code> with to extract the texts from documents. I wasn&rsquo;t familiar with zpandacz and tried playing with it and tweaked Raphael&rsquo;s suggestion and was able to get plaintext from <code>docx</code> files using:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> sh <span style="color:#f92672">import</span> pandoc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>plaintext_output <span style="color:#f92672">=</span> str(pandoc(<span style="color:#e6db74">&#34;path_to_docx_file.docx&#34;</span>, to<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;plain&#34;</span>))
</span></span></code></pre></div><p>This returns a very clean plaintext from any document, so you can give it a markdown or any other text file and get the same plain text. I just need to remove th footnote references (currently shown as <code>[1]</code>, <code>[2]</code>, etc in the plaintext output) to get an actual plaintext, but I think this is as good as we can get with extracting text from <code>docx</code> and markdown files.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Generating text embeddings locally using sentence-transformers</title>
      <link>https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/</link>
      <pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/how-to-use-sentencetransformers-to-generate-text-embeddings-locally/</guid>
      <description>&lt;p&gt;Recently, I&amp;rsquo;ve been working on a side project where I use OpenAI&amp;rsquo;s &lt;code&gt;text-embedding-ada-002&lt;/code&gt; model to generate vector embeddings for text snippets. While this model is inexpensive, the cost can add up when dealing with thousands or millions of text snippets. Therefore, I decided to explore alternatives, particularly those that would allow me to run similar models locally instead of relying on OpenAI&amp;rsquo;s API. In this post, I&amp;rsquo;ll share my experience using the &lt;code&gt;sentence-transformers&lt;/code&gt; library for this purpose and discuss the pros and cons.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>Recently, I&rsquo;ve been working on a side project where I use OpenAI&rsquo;s <code>text-embedding-ada-002</code> model to generate vector embeddings for text snippets. While this model is inexpensive, the cost can add up when dealing with thousands or millions of text snippets. Therefore, I decided to explore alternatives, particularly those that would allow me to run similar models locally instead of relying on OpenAI&rsquo;s API. In this post, I&rsquo;ll share my experience using the <code>sentence-transformers</code> library for this purpose and discuss the pros and cons.</p>
<h2 id="choosing-the-right-model">Choosing the Right Model</h2>
<p>I was mostly intrested in using light-weight models, since I&rsquo;m planning to run this on my own laptop or some cheap VPS. After looking at the available models in <code>sentence-transformers</code>, I chose <code>all-MiniLM-L6-v2</code> model. It&rsquo;s a small BERT-based model which is a MiniLM model fine tuned on a large dataset of over 1 billion training pairs. It maps sentences and paragraphs to a 384 dimensional dense vector space and can be used for tasks like clustering or semantic search. A few other models I considered were <code>all-mpnet-base-v2</code>, <code>e5-base-v2</code>, and <code>hkunlp/instructor-xl</code> but they are larger and heavier to run.</p>
<h2 id="text-length-matters">Text Length Matters</h2>
<p>It&rsquo;s crucial to note that for Transformer models like BERT, runtime and memory requirements increase quadratically with input length. So there&rsquo;s a limit on the length of inputs these models can handle. Any text that exceeds the specific limit of the model gets truncated to the first N word pieces.</p>
<h2 id="using-sentence-transformers">Using <code>sentence-transformers</code></h2>
<p>If you have already installed the library (<code>pip install sentence-transformers</code>), here&rsquo;s how you can load a sentence-transformer model and adjust the maximum sequence length:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> sentence_transformers <span style="color:#f92672">import</span> SentenceTransformer, util
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>model <span style="color:#f92672">=</span> SentenceTransformer(<span style="color:#e6db74">&#34;all-MiniLM-L6-v2&#34;</span>)
</span></span><span style="display:flex;"><span>print(model<span style="color:#f92672">.</span>max_seq_length)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>model<span style="color:#f92672">.</span>max_seq_length <span style="color:#f92672">=</span> <span style="color:#ae81ff">256</span>
</span></span></code></pre></div><h3 id="generating-embeddings">Generating embeddings</h3>
<p>Generating embeddings for sentences is quite straightforward:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># Our sentences we like to encode</span>
</span></span><span style="display:flex;"><span>sentences <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;this is the first sample sentence&#34;</span>, <span style="color:#e6db74">&#34;this is the second sample sentence&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Sentences are encoded by calling model.encode()</span>
</span></span><span style="display:flex;"><span>embeddings <span style="color:#f92672">=</span> model<span style="color:#f92672">.</span>encode(sentences, normalize_embeddings<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span></code></pre></div><p>Setting <code>normalize_embeddings</code> to <code>True</code> ensures the returned vectors have a length of 1. This allows you to use the faster dot-product instead of cosine similarity.</p>
<h3 id="searching-for-similar-sentences">Searching for Similar Sentences</h3>
<p>Here&rsquo;s how you can identify the most similar sentences to your query within a corpus:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> torch
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Corpus with example sentences</span>
</span></span><span style="display:flex;"><span>corpus <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;A man is eating food.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;A man is eating a piece of bread.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;The girl is carrying a baby.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;A man is riding a horse.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;A woman is playing violin.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;Two men pushed carts through the woods.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;A man is riding a white horse on an enclosed ground.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;A monkey is playing drums.&#39;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#39;A cheetah is running behind its prey.&#39;</span>
</span></span><span style="display:flex;"><span>          ]
</span></span><span style="display:flex;"><span>corpus_embeddings <span style="color:#f92672">=</span> model<span style="color:#f92672">.</span>encode(corpus, normalize_embeddings<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>query <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;What is the man eating?&#34;</span>
</span></span><span style="display:flex;"><span>query_embedding <span style="color:#f92672">=</span> model<span style="color:#f92672">.</span>encode(query, normalize_embeddings<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Since the embeddings are normalized, we can use dot_score to find the highest 5 scores</span>
</span></span><span style="display:flex;"><span>dot_scores <span style="color:#f92672">=</span> util<span style="color:#f92672">.</span>dot_score(query_embedding, corpus_embeddings)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>top_results <span style="color:#f92672">=</span> torch<span style="color:#f92672">.</span>topk(dot_scores, k<span style="color:#f92672">=</span><span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> score, idx <span style="color:#f92672">in</span> zip(top_results[<span style="color:#ae81ff">0</span>], top_results[<span style="color:#ae81ff">1</span>]):
</span></span><span style="display:flex;"><span>    print(corpus[idx], <span style="color:#e6db74">&#34;(Score: </span><span style="color:#e6db74">{:.4f}</span><span style="color:#e6db74">)&#34;</span><span style="color:#f92672">.</span>format(score))
</span></span></code></pre></div><p>It will give you the following:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 552 89"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='192' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='216' y='20' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='224' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='232' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='240' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='248' y='36' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='264' y='36' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='264' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='264' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='272' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='272' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='280' y='36' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='288' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='296' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='304' y='36' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='304' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='36' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='312' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='320' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='328' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='336' y='20' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='336' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='344' y='20' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='344' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='352' y='20' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='360' y='20' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='360' y='52' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='368' y='20' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='368' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='368' y='68' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='376' y='20' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='376' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='384' y='20' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='384' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='392' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='392' y='68' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='400' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='408' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='408' y='68' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='416' y='68' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='424' y='52' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='432' y='52' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='440' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='448' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='456' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='464' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='472' y='52' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='488' y='52' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='496' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='504' y='52' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='512' y='52' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='520' y='52' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='528' y='52' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='536' y='52' fill='currentColor' style='font-size:1em'>)</text>
</g>

    </svg>
  
</div>
<h2 id="my-use-case-and-observations">My Use Case and Observations</h2>
<p>I tested this approach with my dataset of approximately 75,000 short text snippets, intending to identify the most relevant snippets for a given query. While generating embeddings took roughly 25 minutes on my Macbook Air — significantly slower than using OpenAI — I found this acceptable since I only needed to perform this process once. Another limitation of running this locally is the maximum input lengthof 256 tokens (for the <code>all-MiniLM-L6-v2</code> model). However, this wasn&rsquo;t an issue for me as my text snippets were short.</p>
<p>I&rsquo;m also thinking about using this method to create a local LLM assistant, that can look at my private notes and documents on my laptop or my own server. This way, it&rsquo;s much cheaper than using a service like OpenAI and I don&rsquo;t have to worry about my text data privacy. Overall, I think for many use cases where you have a smaller dataset and don&rsquo;t need real-time responses, running your own sentence transformer model can be a viable option.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>TIL: Simplifying URL Parsing with Python&#39;s urlparse Library</title>
      <link>https://saeedesmaili.com/til-simplifying-url-parsing-with-python-urlparse-library/</link>
      <pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/til-simplifying-url-parsing-with-python-urlparse-library/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;For quite a while now, I&amp;rsquo;ve been using &lt;a href=&#34;https://getpocket.com&#34; target=&#34;_blank&#34; &gt;Pocket&lt;/a&gt;
 as my go-to read-it-later app. A few months back, I found myself wanting a solution to listen to my saved articles. This led me to explore the text-to-speech feature in the &lt;a href=&#34;https://readwise.io/i/s4759&#34; target=&#34;_blank&#34; &gt;Reader&lt;/a&gt;
 app. Reader gave me the convenience of linking with my Pocket account, synchronizing its inbox automatically with my saved articles.&lt;/p&gt;
&lt;p&gt;This system has worked well. I&amp;rsquo;ve been listening to my saved articles on Reader while continuing to save new articles on Pocket. This lets me easily revisit them later if needed.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<h2 id="background">Background</h2>
<p>For quite a while now, I&rsquo;ve been using <a href="https://getpocket.com" target="_blank" >Pocket</a>
 as my go-to read-it-later app. A few months back, I found myself wanting a solution to listen to my saved articles. This led me to explore the text-to-speech feature in the <a href="https://readwise.io/i/s4759" target="_blank" >Reader</a>
 app. Reader gave me the convenience of linking with my Pocket account, synchronizing its inbox automatically with my saved articles.</p>
<p>This system has worked well. I&rsquo;ve been listening to my saved articles on Reader while continuing to save new articles on Pocket. This lets me easily revisit them later if needed.</p>
<p>There was, however, a small issue. I noticed the count of saved items on these two platforms didn&rsquo;t match. While this didn&rsquo;t initially bother me much (as I didn&rsquo;t want to go through hundreds of articles), curiosity eventually got me to investigate this using Pandas. The main source of discrepancy wasn&rsquo;t anything major, just a few instances of Reader changing urls from <code>http</code> to <code>https</code>. But, the point of interest here isn&rsquo;t the mismatch - it&rsquo;s the Python library that helped me spot it.</p>
<h2 id="pythons-urlparse-library">Python&rsquo;s urlparse Library</h2>
<p>While looking into this issue, I needed to compare the URLs of the saved items across both platforms. My initial plan was to use regex patterns to split each URL to different parts. However, I stumbled upon <a href="https://docs.python.org/3/library/urllib.parse.html" target="_blank" >Python&rsquo;s urlparse library</a>
. It not only did what I needed but also offered a has many additional functionalities.</p>
<p>Here&rsquo;s a glimpse of how it works:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> urllib.parse <span style="color:#f92672">import</span> urlparse
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>url_to_parse <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://saeedesmaili.com/exploring-openai-whisper-with-non-english-voices/?param=query&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>parsed_url <span style="color:#f92672">=</span> urlparse(url)
</span></span><span style="display:flex;"><span>print(parsed_url)
</span></span><span style="display:flex;"><span><span style="color:#75715e">## ParseResult(scheme=&#39;https&#39;, netloc=&#39;saeedesmaili.com&#39;, path=&#39;/exploring-openai-whisper-with-non-english-voices/&#39;, params=&#39;&#39;, query=&#39;query=param&#39;, fragment=&#39;&#39;)</span>
</span></span></code></pre></div><p>Looking back, I wish I&rsquo;d discovered this library sooner. Last year, I spent quite some time with a regex pattern to extract the domain name and extension (the <code>netloc</code> in <code>urlparse</code>&rsquo;s output) for a side project.</p>
<p>One additional advantage of <code>urlparse</code> is its simplicity in manipulating parts of a URL. Here&rsquo;s an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>parsed_url<span style="color:#f92672">.</span>_replace(query<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>geturl()
</span></span><span style="display:flex;"><span><span style="color:#75715e">## &#39;https://saeedesmaili.com/exploring-openai-whisper-with-non-english-voices/&#39;</span>
</span></span></code></pre></div><hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Exploring OpenAI&#39;s Whisper with Non-English Voices</title>
      <link>https://saeedesmaili.com/exploring-openai-whisper-with-non-english-voices/</link>
      <pubDate>Wed, 21 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/exploring-openai-whisper-with-non-english-voices/</guid>
      <description>&lt;p&gt;TL;DR: Whisper.cpp is the fastest when you&amp;rsquo;re trying to use the &lt;code&gt;large&lt;/code&gt; Whisper model on a Mac. For top-quality results with languages other than English, I recommend to ask model to translate into English.&lt;/p&gt;
&lt;h2 id=&#34;about-whisper&#34;&gt;About Whisper&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://openai.com/research/whisper&#34; target=&#34;_blank&#34; &gt;Whisper&lt;/a&gt;
 is OpenAI&amp;rsquo;s speech-to-text model and it&amp;rsquo;s well-known for its impressive results. Although I knew about it for a while, I didn&amp;rsquo;t get to test its real-world performance until recently. So, I spent a weekend seeing how it could handle converting speeches, in both English and other languages, into text.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>TL;DR: Whisper.cpp is the fastest when you&rsquo;re trying to use the <code>large</code> Whisper model on a Mac. For top-quality results with languages other than English, I recommend to ask model to translate into English.</p>
<h2 id="about-whisper">About Whisper</h2>
<p><a href="https://openai.com/research/whisper" target="_blank" >Whisper</a>
 is OpenAI&rsquo;s speech-to-text model and it&rsquo;s well-known for its impressive results. Although I knew about it for a while, I didn&rsquo;t get to test its real-world performance until recently. So, I spent a weekend seeing how it could handle converting speeches, in both English and other languages, into text.</p>
<p>Whisper comes in different sizes and two types. The smallest one (<code>tiny</code>) is really fast but not so accurate, while the largest one (<code>large</code>) is slower but way more precise. Some people have found that the smaller versions work fine for most English speeches, but I was interested in seeing if it could handle less common languages like Persian as well.</p>
<p><img alt="Whisper models" loading="lazy" src="/images/2023/20230621-exploring-openai-whisper-with-non-english-voices.png"></p>
<p>Whisper&rsquo;s performance varies widely depending on the language and <a href="https://raw.githubusercontent.com/openai/whisper/main/language-breakdown.svg" target="_blank" >this figure</a>
 shows a WER (Word Error Rate) breakdown by languages using the <code>large-v2</code> model (The smaller the numbers, the better the performance).</p>
<h2 id="voice-files">Voice Files</h2>
<p>I usually use Telegram for messaging, so I had many voice messages saved there. I picked a few for this experiment. I also used the first minute of sound from two English and two Persian YouTube videos. If you want to try this experiment yourself, you can download these files <a href="/files/20230621-english.wav" >here</a>
 and <a href="/files/20230621-persian.wav" >here</a>
.</p>
<h2 id="attempting-hugging-face-transformers-and-google-colab">Attempting Hugging Face Transformers and Google Colab</h2>
<p>At first, I tried to use Google Colab for this experiment so I wouldn&rsquo;t have to install anything on my laptop. I came across the <a href="https://huggingface.co/openai/whisper-large-v2" target="_blank" >Whisper model card on hugging face</a>
 and tried installing the transformers and loading the model on a Google Colab notebook. The results of the <code>tiny</code> and <code>small</code> models weren&rsquo;t good for Persian voice, and the <code>medium</code> model caused the Colab session to restart due to insufficient memory, meaning I couldn&rsquo;t use Google Colab for this experiment.</p>
<p>Also, I found the Hugging Face transformers to be somewhat complex, especially when compared to OpenAI&rsquo;s python library for Whisper, which seemed much more straightforward. So, I decided to switch over and install this library on my laptop to continue the experiment there.</p>
<h2 id="experimenting-with-whisper-python-library-on-macbook">Experimenting with Whisper Python Library on Macbook</h2>
<p>My 8GB RAM Macbook Air couldn&rsquo;t handle the <code>medium</code> and <code>large</code> models, so I switched to my Macbook Pro for a more intensive experimentation. <a href="https://github.com/openai/whisper" target="_blank" >The Whisper python library</a>
 is quite user-friendly. After installation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install -U openai-whisper
</span></span></code></pre></div><p>And ensuring <code>ffmpeg</code> is installed:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install ffmpeg
</span></span></code></pre></div><p>It&rsquo;s as easy as a few lines of code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> whisper
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>model <span style="color:#f92672">=</span> whisper<span style="color:#f92672">.</span>load_model(<span style="color:#e6db74">&#34;large&#34;</span>)
</span></span><span style="display:flex;"><span>result <span style="color:#f92672">=</span> model<span style="color:#f92672">.</span>transcribe(<span style="color:#e6db74">&#34;english.wav&#34;</span>)
</span></span><span style="display:flex;"><span>print(result[<span style="color:#e6db74">&#34;text&#34;</span>])
</span></span></code></pre></div><p>It took 60 seconds to transcribe a 60-second English voice file into:</p>
<blockquote>
<p>&quot; Hey, what&rsquo;s up? I&rsquo;m Kabeh HD here. Okay, I&rsquo;m gonna make myself sound really old here just for a second. But about 10 years ago, there was a vibrant ecosystem of a ton of different tablets. There&rsquo;s all kinds of tablets. There was like iPads versus Android tablets, all these fights going on. There were small screen tablets, big screen tablets, and all the Android tablets were sort of anchored by Google&rsquo;s family of Nexus tablets. So we had the smash hit Nexus 7 and the cult classic Nexus 10. But then Google kind of got a little bit lost with Android tablets and Chrome OS tablets a little bit in there for a bit, and they made a few bad ones in a row, and then they just gave up. Like they literally just gave up, stopped making tablets. But something else that&rsquo;s picked up a whole bunch of steam since then is a new product category, smart home displays. So Google bought Nest, and we&rsquo;ve seen this Nest Hub, and the Nest Hub Max came out, and this is a growing category.&quot;</p></blockquote>
<p>And 109 seconds for a 60 seconds Persian voice file to turn into:</p>
<blockquote>
<p>&rsquo; سلام من حسام هستم و به قسمت 8 از منجوری لذت اکاسی خوش اومدید توی این قسمت میخوایم راجب اکاسی قضا با هم صحبت بکنیم و یه سیری راه کارها رو با هم داشته باشیم که به تون کمک می کنن اکسهای بهتری را از اون قضاهایی که درست می کنید یا برتون سرف می شد و داشته باشین همراه من باشین ممنبع نوری اولین نکته برای داشتن اکسهای حرفه ای از قضا منبع نوریه خب اگر دست رسیب امکاناتی ویژه مثل نورهای استودیوی ندارید باید دقیق بکنید که از منبع نور طبیعی مثل پنجره استفاده بکنید اینجوری نتائجتون واقعا شگفتنگیز می شه این اکس رو می بینید این با نور پنجره سبت شده به اصلی گوشی و می بینید که چون منبع نوری مود به صورت طبیعیه خیلی نور خوبی متوجه قضا شده و قضا قشنگ'</p></blockquote>
<p>The English transcription was excellent. As for the Persian translation, I found it somewhat lacking. There were so many typos in the text, and if you were to use it as the subtitle in a video, it would come off as embarrassing. The model does seem to accurately pick up on individual sounds, both vowels and consonants. However, when it attempts to assemble these sounds into a complete word, the results often don&rsquo;t form valid words in Persian. For example, it heard the word &ldquo;akkasi&rdquo; which is the English spelling for the Persian word meaning &ldquo;photography&rdquo; (<code>عکاسی</code>), but it transcribed it as <code>اکاسی</code>. This isn&rsquo;t a valid word in Persian, but if it were, it would also be pronounced as &ldquo;akkasi.&rdquo;</p>
<h2 id="trying-whispercpp">Trying whisper.cpp</h2>
<p>Looking for a faster way to get the transcriptions, I found that <a href="https://github.com/ggerganov/whisper.cpp" target="_blank" >whisper.cpp</a>
 was much quicker than the Python library for the larger models. To use whisper.cpp, you first need to clone the project, then download a Whisper model:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bash ./models/download-ggml-model.sh large
</span></span></code></pre></div><p>Then simply run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./main -m models/ggml-large.bin -f samples/english.wav --output-txt
</span></span></code></pre></div><p>Make sure your voice file is a 16khz wav, and adjust the location as needed. The <code>--output-txt</code> part gives you a text file of the transcript. Transcribing the English voice took 22 seconds, which is three times faster than the Whisper python library.</p>
<p>For languages other than English, you can add <code>-l &lt;language&gt;</code> to the command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./main -m models/ggml-large.bin -f samples/persian.wav --output-txt -l fa
</span></span></code></pre></div><p>This one took about 27 seconds, which is four times faster than the Whisper Python library.</p>
<p>Here&rsquo;s the interesting part: if you want an English version of a non-English voice file, you can add a <code>-tr</code> flag to the command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./main -m models/ggml-large.bin -f samples/persian.wav --output-txt -l fa -tr
</span></span></code></pre></div><p>This command translated the Persian voice to the following English text in just 19 seconds!</p>
<blockquote>
<p>Hi, I&rsquo;m Hesam and welcome to the 8th episode of the &ldquo;Taste of Photography&rdquo; series. In this episode, we want to talk about food photography and have some guides with us to help you get the best photos of the foods that you make or serve. Stay with me. [Intro] The first tip for taking professional photos of food is a light source. If you don&rsquo;t have access to special facilities like studio lights, make sure to use natural light sources like a window. This way, your photos will be more attractive. This photo is taken with a window light. As you can see, the natural light source is a very good light source. The food is very tasty.</p></blockquote>
<p>The result is quite impressive and more practical than the Persian transcription. It is possible that the higher quality of the translated Persian transcript results from more translation hours in training datasets (see appendix E in the <a href="https://arxiv.org/abs/2212.04356" target="_blank" >Whisper&rsquo;s paper</a>
). So, if you&rsquo;re dealing with non-English speeches and you&rsquo;re not concerned about presenting the output to users, it&rsquo;s more effective to translate and use the English text.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cleaning up incorrect and duplicates in a 1password account using its CLI</title>
      <link>https://saeedesmaili.com/delete-unwanted-and-duplicated-items-on-1password/</link>
      <pubDate>Fri, 09 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/delete-unwanted-and-duplicated-items-on-1password/</guid>
      <description>&lt;p&gt;Recently, I decided to switch my primary password manager to &lt;a href=&#34;https://1password.com/&#34; target=&#34;_blank&#34; &gt;1Password&lt;/a&gt;
, using it across my Macbooks and Pixel phones on various browsers, including Firefox and Chrome. One bonus feature that I particularly enjoy is its compatibility with &lt;a href=&#34;https://www.alfredapp.com/&#34; target=&#34;_blank&#34; &gt;Alfred&lt;/a&gt;
, an app that helps me be more productive on my Mac.&lt;/p&gt;
&lt;p&gt;However, the transition was not all smooth sailing. When I transferred my passwords from Firefox and Chrome into 1Password, a couple of things didn&amp;rsquo;t go quite right. I ended up with:&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>Recently, I decided to switch my primary password manager to <a href="https://1password.com/" target="_blank" >1Password</a>
, using it across my Macbooks and Pixel phones on various browsers, including Firefox and Chrome. One bonus feature that I particularly enjoy is its compatibility with <a href="https://www.alfredapp.com/" target="_blank" >Alfred</a>
, an app that helps me be more productive on my Mac.</p>
<p>However, the transition was not all smooth sailing. When I transferred my passwords from Firefox and Chrome into 1Password, a couple of things didn&rsquo;t go quite right. I ended up with:</p>
<ul>
<li>Lots of records that had the website&rsquo;s URL saved as the username and my actual username saved as the password.</li>
<li>A cluttered 1Password account with lots of duplicate records – each one with the same website, username, and password.</li>
</ul>
<p>Naturally, manually going through hundreds of passwords to sort this out was not my idea of a fun time. Thankfully, 1Password provides a CLI tool, and with a little learning and scripting, I was able to address both issues.</p>
<p>To get started, I needed to install the <a href="https://1password.com/downloads/command-line/" target="_blank" >1Password CLI</a>
 and <a href="https://developer.1password.com/docs/cli/get-started#sign-in" target="_blank" >sign in</a>
. I also needed to have <a href="https://jqlang.github.io/jq/" target="_blank" >jq</a>
 installed on my machine.</p>
<p>To remove items where the username is actually a URL, I created a bash script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> id in <span style="color:#66d9ef">$(</span>op item list --format<span style="color:#f92672">=</span>json | jq -r <span style="color:#e6db74">&#39;.[] | select(.id != null) | .id&#39;</span><span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  item<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>op item get $id --format<span style="color:#f92672">=</span>json<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $item !<span style="color:#f92672">=</span> null <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    fields<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $item | jq -r <span style="color:#e6db74">&#39;.fields&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $fields !<span style="color:#f92672">=</span> null <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>      username<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $fields | jq -r <span style="color:#e6db74">&#39;.[] | select(.label==&#34;username&#34;).value&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $username <span style="color:#f92672">==</span> http* <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        op item delete $id --archive
</span></span><span style="display:flex;"><span>        echo <span style="color:#e6db74">&#34;</span>$id<span style="color:#e6db74"> deleted&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>Then, I made my script executable and ran it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod +x remove-urls.sh
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./remove-urls.sh
</span></span></code></pre></div><p>The first issue is resolved!</p>
<p>Next, I addressed the problem of duplicate entries. To do this, I had to first update Bash to a more recent version that could handle special types of lists called associative arrays. Because I was using a Mac, it initially came with an older version of Bash that lacked this feature (<code>bash --version</code> gave me v3.2). So, my first step was to install a newer version of Bash (v5.2):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install bash
</span></span></code></pre></div><p>And find its path:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>which bash
</span></span></code></pre></div><p>Then, used the path for running the new script to remove duplicates:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/opt/homebrew/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>declare -A itemMap
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> id in <span style="color:#66d9ef">$(</span>op item list --categories Login --format<span style="color:#f92672">=</span>json | jq -r <span style="color:#e6db74">&#39;.[] | select(.id != null) | .id&#39;</span><span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    item<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>op item get $id --format<span style="color:#f92672">=</span>json<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $item !<span style="color:#f92672">=</span> null <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        fields<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $item | jq -r <span style="color:#e6db74">&#39;.fields&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> $fields !<span style="color:#f92672">=</span> null <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>            username<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $fields | jq -r <span style="color:#e6db74">&#39;.[] | select(.label==&#34;username&#34;).value&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        urls<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $item | jq -r <span style="color:#e6db74">&#39;.urls&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>        href<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $urls | jq -r <span style="color:#e6db74">&#39;.[0].href&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>        website<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $href | awk -F<span style="color:#f92672">[</span>/:<span style="color:#f92672">]</span> <span style="color:#e6db74">&#39;{print $4}&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> -n $website <span style="color:#f92672">&amp;&amp;</span> -n $username <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>            key<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$website<span style="color:#e6db74">-</span>$username<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> <span style="color:#e6db74">${</span>itemMap[$key]<span style="color:#e6db74">}</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>                echo <span style="color:#e6db74">&#34;Duplicate found:&#34;</span>
</span></span><span style="display:flex;"><span>                echo <span style="color:#e6db74">&#34;Item 1: id: </span><span style="color:#e6db74">${</span>itemMap[$key]<span style="color:#e6db74">}</span><span style="color:#e6db74">, username: </span>$username<span style="color:#e6db74">, website: </span>$website<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>                echo <span style="color:#e6db74">&#34;Item 2: id: </span>$id<span style="color:#e6db74">, username: </span>$username<span style="color:#e6db74">, website: </span>$website<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>                op item delete $id --archive
</span></span><span style="display:flex;"><span>                echo <span style="color:#e6db74">&#34;</span>$id<span style="color:#e6db74"> deleted&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>                itemMap<span style="color:#f92672">[</span>$key<span style="color:#f92672">]=</span>$id
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>Just as before, I needed to make this script executable and then ran it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod +x remove-duplicates.sh
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./remove-duplicates.sh
</span></span></code></pre></div><hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Why do we need to have a central place for TODOs</title>
      <link>https://saeedesmaili.com/why-do-we-need-to-have-a-central-place-for-todos/</link>
      <pubDate>Sun, 26 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/why-do-we-need-to-have-a-central-place-for-todos/</guid>
      <description>&lt;p&gt;I keep hearing from people that they find it difficult to keep track of what needs to be done. Every one of us has a list of different things to remember, work on, and accomplish. This list includes not only the very well-defined tasks that we call &lt;em&gt;work&lt;/em&gt;, but also any Slack messages and emails that we have to reply to, documents that we have to read and review, and vacation time that we need to submit.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I keep hearing from people that they find it difficult to keep track of what needs to be done. Every one of us has a list of different things to remember, work on, and accomplish. This list includes not only the very well-defined tasks that we call <em>work</em>, but also any Slack messages and emails that we have to reply to, documents that we have to read and review, and vacation time that we need to submit.</p>
<h2 id="the-wrong-approach">The wrong approach</h2>
<p>We can rely on our brains to remember all these daily chores, but don&rsquo;t ask me to explain why it&rsquo;s a terrible idea. Our precious brain has a limited processing power for each day. This power can definitely be put to better use on something more creative than remembering to reply to an email.</p>
<p>A better but not ideal alternative is to use each tool&rsquo;s unique features to bookmark any piece of information that we&rsquo;ll need to act on some day. Gmail lets us <em>star</em> emails, messages can be <em>added to saved items</em> on Slack, and we can use one of the hundred TODO apps to store other chores like submitting vacation time. Saving and bookmarking stuff this way is mostly convenient to do, but very difficult to organize and retrieve. We will need to check various tools frequently to make sure nothing has gone off our radar.</p>
<h2 id="the-better-approach">The better approach</h2>
<p>We tend to seek the most convenient way to store information (that&rsquo;s why we might chat with ourselves on Slack), but don&rsquo;t care much about the convenience of retrieving that information. Having a balanced approach and putting everything that needs to be done in a central place will make our brains&rsquo; life much easier in reclaiming them. Bonus point if we could add enough context to each item and organize them properly.</p>
<p>Which tool to choose for this central place will be very dependent on what type of person we are. Some of us prefer pen and paper and will go with a <a href="https://bulletjournal.com/" target="_blank" >bullet journal</a>
. Others want to stay digital and will use <a href="https://obsidian.md/" target="_blank" >Obsidian</a>
 or <a href="https://logseq.com/" target="_blank" >Logseq</a>
. No matter which tool you decide to use, remember that it should be <em>one central place</em>. Try your best to stick with one single place as much as possible. Of course, this is not 100% practical advice, and there will be some exceptions since a few specific tools like digital calendars can handle particular tasks more efficiently.</p>
<p>Note that I&rsquo;m only talking about storing chunks of different types of work we need to do each day (and we usually call them our TODO list). Other pieces of information like what we learn from a book or a course, or our long-term planning docs can be (and should be) stored somewhere else.</p>
<h2 id="what-do-i-use">What do I use?</h2>
<p>Since I was already using Obsidian as my second brain and personal knowledge management system, I gave it a chance to be the host to my daily and ongoing tasks as well. Although Obsidian has a few useful TODO-related plugins, I didn&rsquo;t find myself comfortable using it for this purpose (I&rsquo;m happily continuing to use it as my second brain though).</p>
<p>I&rsquo;m currently using Logseq as my central TODO place. In addition to the fact that it stores all the notes in local markdown files, this tool comes with a few very nice features that make it very suitable for what I was looking for.</p>
<ul>
<li>Adding and toggling TODOs is easy and part of its core functionalities.</li>
<li>It&rsquo;s conveniently possible to schedule an item and set a priority for each TODO item.</li>
<li>Daily journaling is a very native feature as well.</li>
<li>Creating custom templates allows me to structure pages however I want.</li>
</ul>
<p>I have iterated on a daily journal template, with a specific layout and a few useful titles and information pre-filled. When I open Logseq each morning, it automatically creates a new note (titled with weekday and date) using my custom template. The template includes an arbitrary structure to help me organize TODOs in different buckets depending on when they need to be done. Then follows an unfinished TODOs from yesterday, and then a full list of unfinished TODO items from the past. The daily note ends with a reminder section including all the TODOs that are scheduled for this day or have a deadline set to today. I can easily move items between days and sections and change their priorities in lists whenever needed.</p>
<p>I also use the daily journal page on Logseq to take notes of my thoughts, meetings, ideas, or anything that I don&rsquo;t want to think about where to write them at the moment. Later in the day or week, I make sure to move anything important to its desired place (my second brain, the company&rsquo;s docs, or somewhere else).</p>
<p><img alt="Logseq Daily Journal Template" loading="lazy" src="/images/2023/20230326-logseq-daily-journal-template.png#center"></p>
<h2 id="limitations">Limitations</h2>
<p>I know, you&rsquo;re thinking, &ldquo;… it&rsquo;s a lot of work!&rdquo; . Actually it&rsquo;s not <em>a lot</em>, but yeah sure, it requires some discipline and work. Recall that to make the retrieving process easier and more efficient, we need a robust system. I&rsquo;m not completely disciplined in it yet either, but I&rsquo;m getting better. The secret is to just start. Start small and give it a try. I began with a blank daily journal on Logseq and added templates later on, and gradually improved it based on what I found essential for myself. Feel free to reach out if you are interested in knowing more about my workflow.</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>What I dislike about Google Docs (and what I like about it)</title>
      <link>https://saeedesmaili.com/what-i-dislike-and-like-about-google-docs/</link>
      <pubDate>Tue, 14 Mar 2023 00:00:00 +0000</pubDate>
      <guid>https://saeedesmaili.com/what-i-dislike-and-like-about-google-docs/</guid>
      <description>&lt;p&gt;I use &lt;a href=&#34;https://www.google.com/docs/about/&#34; target=&#34;_blank&#34; &gt;Google Docs&lt;/a&gt;
 every day at work to communicate with various groups of people. It&amp;rsquo;s a very effortless tool to start writing and share the output with whoever I want once I&amp;rsquo;m finished.&lt;/p&gt;
&lt;p&gt;But it definitely lacks a few critical features that hinder my productivity.&lt;/p&gt;
&lt;h2 id=&#34;what-i-like-convenience-and-collaboration&#34;&gt;What I like: Convenience and Collaboration&lt;/h2&gt;
&lt;p&gt;Google Docs makes it easy to write and share documents with others and to invite them to review and collaborate.&lt;/p&gt;</description>
      <content:encoded>
        <![CDATA[<p>I use <a href="https://www.google.com/docs/about/" target="_blank" >Google Docs</a>
 every day at work to communicate with various groups of people. It&rsquo;s a very effortless tool to start writing and share the output with whoever I want once I&rsquo;m finished.</p>
<p>But it definitely lacks a few critical features that hinder my productivity.</p>
<h2 id="what-i-like-convenience-and-collaboration">What I like: Convenience and Collaboration</h2>
<p>Google Docs makes it easy to write and share documents with others and to invite them to review and collaborate.</p>
<ul>
<li><em>It&rsquo;s super simple to share a document with someone</em>, a specific team or group of people, an organization, or the entire internet. Plus I can set different access levels for each person. This way, no one would accidentally edit the document, when they are just invited to provide their feedback and comments.</li>
<li>Speaking of comments, Google Docs makes it very convenient for other people to select a part of the text and leave their comments. Everyone then can reply to the original comment and start having a conversation around that specific selected text without cluttering up the original text.</li>
</ul>
<h2 id="what-i-dislike-findability">What I dislike: Findability</h2>
<p>Writing awesome docs is just half of the equation, and if I (or any other person) can&rsquo;t find it later, I&rsquo;ll be less motivated to spend time and effort writing a similar masterpiece next time. I wish Google Docs allowed me to:</p>
<ul>
<li>Filter the search results. It comes with basic search functionality to look for all the documents that contain some specific keywords and leaves me with a list of titles at the end. It doesn&rsquo;t let me filter by author, commenters, creation date, or almost anything else.</li>
<li>Add some metadata to each document and filter by them later on. I could add tags like <code>type: meeting-note</code> or <code>status: draft</code> to a document to be able to find it more easily next time. This would also be helpful when I want to share the list of documents that <code>author in [person a, person b] and project = X and status = published</code> with the stakeholders of Project X.</li>
<li>See each document&rsquo;s backlinks. Google Docs has a handy feature for linking to another document by typing <code>@</code> and searching for the title or just pasting the link to the document. But it misses the other side of the linking and doesn&rsquo;t provide a way to know which documents are linking back to the current one. This is a key feature of <a href="https://en.wikipedia.org/wiki/Personal_knowledge_management" target="_blank" >PKM</a>
 tools like <a href="https://obsidian.md/" target="_blank" >Obsidian</a>
 and <a href="https://logseq.com/" target="_blank" >Logseq</a>
. With a list of backlinks for each document, we could see where people are referencing our RFCs and guides. This gives us a much better idea of who are the actual audience and users of our documents, instead of guessing it or relying solely on our own perception.</li>
</ul>
<p>Also I would love to use Google Docs in dark mode. Please!</p>
<hr /><p>Comment? Reply via <a href='mailto:me@saeedesmaili.com?subject=Re: Blog Post on RSS Feeds'>Email</a>, <a href='https://mastodon.world/@saeedesmaili' target='_blank'>Mastodon</a> or <a href='https://twitter.com/saeedesmaili' target='_blank'>Twitter</a>.</p>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
