<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ruth Kegicha</title>
    <description>The latest articles on DEV Community by Ruth Kegicha (@rkeggy).</description>
    <link>https://dev.to/rkeggy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3831166%2F68b84b99-8db2-4a86-879e-d04efd84b0b0.jpg</url>
      <title>DEV Community: Ruth Kegicha</title>
      <link>https://dev.to/rkeggy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rkeggy"/>
    <language>en</language>
    <item>
      <title>What I Learned About MND, Voice Banking, and Why Assistive Tech Is Personal</title>
      <dc:creator>Ruth Kegicha</dc:creator>
      <pubDate>Thu, 21 May 2026 12:40:42 +0000</pubDate>
      <link>https://dev.to/rkeggy/what-i-learned-about-mnd-voice-banking-and-why-assistive-tech-is-personal-424l</link>
      <guid>https://dev.to/rkeggy/what-i-learned-about-mnd-voice-banking-and-why-assistive-tech-is-personal-424l</guid>
      <description>&lt;p&gt;As part of my volunteering with &lt;a href="https://abilitynet.org.uk" rel="noopener noreferrer"&gt;AbilityNet&lt;/a&gt;, I recently attended a training session on motor neurone disease (MND) — and it quietly shifted how I think about what technology is actually for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MND?
&lt;/h2&gt;

&lt;p&gt;MND affects nerves in the brain and spinal cord called motor neurones — the nerve cells that tell your muscles what to do. It's a rare but progressive condition. There is no cure, and it's usually life-shortening.&lt;/p&gt;

&lt;p&gt;Around &lt;a href="https://www.mndassociation.org/about-us/who-we-are/mnd-key-facts" rel="noopener noreferrer"&gt;5,000 people in the UK&lt;/a&gt; are living with MND at any one time. Six people are diagnosed every single day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice banking
&lt;/h2&gt;

&lt;p&gt;Voice banking was something I hadn't encountered before this session. It allows a person to record their own voice or create personalised messages for use with speech-generating devices — and the &lt;a href="https://www.mndassociation.org" rel="noopener noreferrer"&gt;MND Association&lt;/a&gt; recommends starting early, even before speech is significantly affected.&lt;/p&gt;

&lt;p&gt;The goal is to preserve something that can't be replaced once it's gone: the sound of &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Research shows that "preserving identity" is the overarching motivation for people who choose to voice bank. Yet only around 12% of people diagnosed with MND in the UK actually go through with it — because the process has historically been too long, too hard, and too clinical.&lt;/p&gt;

&lt;p&gt;That gap between what's available and what people can actually access is exactly where technology and human support need to meet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good technology serves the people
&lt;/h2&gt;

&lt;p&gt;You don't need to be in tech to show up for this. Anyone can volunteer,&lt;br&gt;
learn, and help bridge that gap.&lt;/p&gt;

&lt;p&gt;I attended this session as part of my volunteering with&lt;br&gt;
&lt;a href="https://abilitynet.org.uk" rel="noopener noreferrer"&gt;AbilityNet&lt;/a&gt; — an organisation that helps&lt;br&gt;
older and disabled people use technology independently. If that's something you care about, their &lt;a href="https://abilitynet.org.uk/volunteering" rel="noopener noreferrer"&gt;volunteer programme&lt;/a&gt; is worth a look.&lt;/p&gt;

&lt;p&gt;If you want to understand more about what living with MND actually looks&lt;br&gt;
like, the MND Association put together a great explainer:&lt;/p&gt;

&lt;h2&gt;
  
  
    &lt;iframe src="https://www.youtube.com/embed/jjlFINbEzig"&gt;
  &lt;/iframe&gt;

&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nhs.uk/conditions/motor-neurone-disease/" rel="noopener noreferrer"&gt;NHS — Motor Neurone Disease&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mndassociation.org/" rel="noopener noreferrer"&gt;MND Association&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://abilitynet.org.uk/volunteering" rel="noopener noreferrer"&gt;AbilityNet volunteer programme&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>assistivetech</category>
      <category>health</category>
      <category>volunteer</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Window Functions for Call Centre Analytics — A Practical PostgreSQL Guide</title>
      <dc:creator>Ruth Kegicha</dc:creator>
      <pubDate>Wed, 18 Mar 2026 11:14:24 +0000</pubDate>
      <link>https://dev.to/rkeggy/window-functions-for-call-centre-analytics-a-practical-postgresql-guide-55n7</link>
      <guid>https://dev.to/rkeggy/window-functions-for-call-centre-analytics-a-practical-postgresql-guide-55n7</guid>
      <description>&lt;p&gt;If you've ever tried to calculate agent session durations, track missed call trends across multiple sites, or figure out when an agent went idle — you already know that aggregate functions will only take you so far.&lt;/p&gt;

&lt;p&gt;Window functions changed how I approach call centre analytics entirely. Here's what I've learned building production dashboards on real call data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a window function?&lt;/strong&gt;&lt;br&gt;
A regular aggregate like &lt;em&gt;SUM()&lt;/em&gt; or &lt;em&gt;COUNT()&lt;/em&gt; collapses rows into one result. A window function performs a calculation across a set of rows related to the current row — without collapsing them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
  agent_id,&lt;br&gt;
  event_time,&lt;br&gt;
  event_type,&lt;br&gt;
  LAG(event_time) OVER (PARTITION BY agent_id ORDER BY event_time) AS previous_event&lt;br&gt;
FROM agent_activity_log;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That single &lt;em&gt;LAG()&lt;/em&gt; call gives you the previous event timestamp per agent, ordered by time — no subquery, no self-join.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real use case: calculating agent session duration&lt;/strong&gt;&lt;br&gt;
The classic call centre problem — an agent logs in, takes calls, goes on break, logs out. You have a log of events. You need session durations.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
  agent_id,&lt;br&gt;
  event_time AS login_time,&lt;br&gt;
  LEAD(event_time) OVER (&lt;br&gt;
    PARTITION BY agent_id&lt;br&gt;
    ORDER BY event_time&lt;br&gt;
  ) AS next_event_time,&lt;br&gt;
  LEAD(event_type) OVER (&lt;br&gt;
    PARTITION BY agent_id&lt;br&gt;
    ORDER BY event_time&lt;br&gt;
  ) AS next_event_type&lt;br&gt;
FROM agent_activity_log&lt;br&gt;
WHERE event_type = 'LOGIN';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Pair &lt;em&gt;LAG()&lt;/em&gt; and &lt;em&gt;LEAD()&lt;/em&gt; together and you can reconstruct full session timelines without ever leaving SQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real use case: missed call percentage per site&lt;/strong&gt;&lt;br&gt;
This one trips people up when you have multiple sites with different in-hours rules and company exclusions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT&lt;br&gt;
  site_id,&lt;br&gt;
  COUNT(*) FILTER (WHERE call_outcome = 'MISSED') AS missed_calls,&lt;br&gt;
  COUNT(*) AS total_calls,&lt;br&gt;
  ROUND(&lt;br&gt;
    COUNT(*) FILTER (WHERE call_outcome = 'MISSED') * 100.0 / NULLIF(COUNT(*), 0),&lt;br&gt;
    2&lt;br&gt;
  ) AS missed_pct,&lt;br&gt;
  AVG(&lt;br&gt;
    COUNT(*) FILTER (WHERE call_outcome = 'MISSED') * 100.0 / NULLIF(COUNT(*), 0)&lt;br&gt;
  ) OVER () AS avg_missed_pct_all_sites&lt;br&gt;
FROM universal_queue&lt;br&gt;
WHERE&lt;br&gt;
  queued_at BETWEEN '2024-01-01' AND '2024-01-31'&lt;br&gt;
  AND in_hours = true&lt;br&gt;
  AND company_id NOT IN (/* exclusions */)&lt;br&gt;
GROUP BY site_id;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice &lt;em&gt;NULLIF(COUNT(*), 0)&lt;/em&gt; — always protect against division by zero when calculating percentages on filtered data. And the _AVG(...) OVER () _at the end gives you the cross-site average in the same query without a subquery or CTE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three window functions I use most&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ROW_NUMBER()&lt;/em&gt; — deduplicate events, get the latest record per agent:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT * FROM (&lt;br&gt;
  SELECT *,&lt;br&gt;
    ROW_NUMBER() OVER (&lt;br&gt;
      PARTITION BY agent_id ORDER BY event_time DESC&lt;br&gt;
    ) AS rn&lt;br&gt;
  FROM agent_activity_log&lt;br&gt;
) t&lt;br&gt;
WHERE rn = 1;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;_LAG() / LEAD() _— calculate time between events, detect gaps in activity, reconstruct sequences.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;SUM() OVER ()&lt;/em&gt; — running totals across a result set without grouping away your detail rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One thing that catches people out&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PARTITION BY&lt;/strong&gt; is not the same as &lt;strong&gt;GROUP BY&lt;/strong&gt;. Group by collapses your rows. Partition by keeps them — the window function just sees a subset of rows for its calculation. You can mix aggregates and window functions in the same query but they run at different stages. If you're getting unexpected results, check whether your &lt;strong&gt;WHERE&lt;/strong&gt; clause is filtering rows before or after the window function runs — it usually is.&lt;/p&gt;

&lt;p&gt;Where to go next&lt;/p&gt;

&lt;p&gt;If this is new to you, practice with &lt;strong&gt;ROW_NUMBER()&lt;/strong&gt; first — it's the most intuitive and the errors are easy to read. Then move to **LAG()/LEAD() **for anything involving sequences of events. Once those feel natural, &lt;em&gt;RANK()&lt;/em&gt;, &lt;em&gt;DENSE_RANK()&lt;/em&gt; and &lt;em&gt;NTILE()&lt;/em&gt; are just variations on the same idea.&lt;/p&gt;

&lt;p&gt;Window functions are one of those SQL features that once you know, you can't imagine working without. Especially when the data is telling a story across time.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>beginners</category>
      <category>analytics</category>
    </item>
  </channel>
</rss>
