<?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: Mikkel</title>
    <description>The latest articles on DEV Community by Mikkel (@mikkelsj).</description>
    <link>https://dev.to/mikkelsj</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2478573%2Fea83f1f2-92f2-477f-9060-832f38fb4e9a.png</url>
      <title>DEV Community: Mikkel</title>
      <link>https://dev.to/mikkelsj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mikkelsj"/>
    <language>en</language>
    <item>
      <title>AI on Mobile Devices</title>
      <dc:creator>Mikkel</dc:creator>
      <pubDate>Tue, 03 Dec 2024 03:04:52 +0000</pubDate>
      <link>https://dev.to/mikkelsj/ai-on-mobile-devices-2ed8</link>
      <guid>https://dev.to/mikkelsj/ai-on-mobile-devices-2ed8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We’ve all heard about the major advancements in AI, including large language models, image recognition, and video generation. These are relatively demanding models, requiring significant computational power and hardware capabilities. But what about smaller devices, like smartphones, smartwatches, or Internet of Things (IoT) devices? Is it possible to take a picture on your phone and, within seconds, determine if your plant is dying due to neglect or a rare disease—letting you off the hook for the blame? In this post, we’ll take a closer look at that possibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Computing Power on Mobile Devices
&lt;/h2&gt;

&lt;p&gt;When it comes to AI on mobile devices, why can’t we just download ChatGPT and use it as we please?&lt;/p&gt;

&lt;p&gt;The primary reason is the sheer number of mathematical computations involved in running an AI model. Mobile devices have significant limitations in terms of how many calculations they can perform per second, how much memory they can store at once, and how much power they can consume for a task.&lt;/p&gt;

&lt;p&gt;Let’s use OpenAI’s GPT-3 as an example to understand these challenges. GPT-3 has &lt;strong&gt;175 billion parameters&lt;/strong&gt;—the numerical values used to generate responses to your questions. These parameters require about &lt;strong&gt;700 GB of memory&lt;/strong&gt; to run. Even the most expensive smartphones, typically equipped with 16–24 GB of RAM, fall far short of what’s needed to handle such a model.&lt;/p&gt;

&lt;p&gt;Now, let’s compare this to a specialized mobile model like &lt;strong&gt;MobileNet&lt;/strong&gt;, which is optimized for on-device performance. MobileNet has only &lt;strong&gt;4 million parameters&lt;/strong&gt; and requires approximately &lt;strong&gt;16 MB of RAM&lt;/strong&gt;—a massive reduction in computational and memory demands.&lt;/p&gt;

&lt;p&gt;While this comparison is somewhat exaggerated (as ChatGPT runs on specialized servers, meaning your phone or computer doesn’t actually handle the heavy computations), it effectively illustrates the constraints of mobile devices. If you want an AI model to run directly on your phone without relying on an internet connection, it’s crucial to design and optimize the model specifically for mobile use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile Sensors
&lt;/h2&gt;

&lt;p&gt;With nearly everyone owning a smartphone these days, it’s only natural to make use of the many sensors they contain. Most people are familiar with the microphone, camera, and GPS, but smartphones also come equipped with accelerometers and gyroscopes.&lt;/p&gt;

&lt;p&gt;Audio, image, and video are relatively intuitive applications. These can be used to identify anomalies or classify events. For example, audio can be used for diagnostics in the healthcare sector or to detect animal sounds in environmental monitoring.&lt;/p&gt;

&lt;p&gt;Where accelerometers and gyroscopes have proven particularly useful is in activity tracking. Common applications include step counters or running apps. Beyond these relatively simple uses, smartphones have the potential to play a much larger role in public health.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gait analysis&lt;/strong&gt; has been shown to be a valuable tool for diagnosing several cognitive disorders, such as dementia and Alzheimer’s, where the brain undergoes degeneration. Even in the early stages, deviations in a person’s walking pattern can be detected, allowing for early intervention. With specialized AI models, we could capture these changes without the need for expensive and bulky gait analysis equipment.&lt;/p&gt;

&lt;p&gt;A simpler but still impactful application related to cognitive disorders is monitoring how often a patient is active. By tracking when the phone, and consequently the patient, is in motion, we can estimate daily activity levels. This is particularly valuable for individuals who may struggle to remember their day-to-day activities. Since physical activity is a crucial factor in slowing the progression of cognitive disorders, this type of monitoring can provide meaningful insights and support for both patients and healthcare providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing Mobile Models
&lt;/h2&gt;

&lt;p&gt;There are already several AI models optimized for mobile devices and smaller hardware. A few examples include &lt;strong&gt;Gemini&lt;/strong&gt;, &lt;strong&gt;Yamnet&lt;/strong&gt;, and &lt;strong&gt;MobileNet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini&lt;/strong&gt; is Google’s flagship AI product. It’s a versatile model with specialized versions. While it can be used similarly to ChatGPT, it also has smaller, optimized versions that make it practical for mobile use as an enhanced Google Assistant. By 2024, the mobile version is expected to function as an AI assistant capable of actively planning tasks for you, using inputs such as emails and calendar data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yamnet&lt;/strong&gt; is an audio classification model built on a large YouTube dataset. It can classify sounds into 512 different categories. Available on mobile, it can be used for classification tasks and also for converting audio into spectrograms or embeddings, which can be utilized by other models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MobileNet&lt;/strong&gt; is an image classification model optimized for mobile devices, as its name suggests. The model is versatile and can be applied to tasks like object detection, facial recognition, and geolocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;At Convai, we have worked on several mobile apps that either integrate AI or are in the process of doing so.&lt;/p&gt;

&lt;p&gt;As mentioned in the post &lt;a href="https://dev.to/mikkelsj/sound-event-detection-m5j"&gt;Sound Event Detection&lt;/a&gt;, we collaborated with Sonohaler, a company using a mobile app with AI to classify audio events and measure their quality. In some of our experiments, we utilized &lt;strong&gt;Yamnet&lt;/strong&gt; as it offers a straightforward method for extracting spectrograms from audio data.&lt;/p&gt;

&lt;p&gt;Another project we’re working on is &lt;strong&gt;Taptics&lt;/strong&gt;, an app designed to help football clubs train and test their players’ tactical understanding. One of the simpler ways we use AI here is as a quiz assistant. The assistant can generate new questions or answer options by analyzing existing quizzes. Additionally, it can evaluate players' free-text answers by comparing them to the coach's correct responses, saving the coach from having to manually review every answer. This allows for more complex answer structures compared to multiple-choice questions, without adding an extra feedback burden on the coach.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ai</category>
    </item>
    <item>
      <title>Sound Event Detection</title>
      <dc:creator>Mikkel</dc:creator>
      <pubDate>Tue, 03 Dec 2024 03:00:04 +0000</pubDate>
      <link>https://dev.to/mikkelsj/sound-event-detection-m5j</link>
      <guid>https://dev.to/mikkelsj/sound-event-detection-m5j</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Sound is vibrations in the air that are converted into electrical signals—either through the ear to the brain or via a microphone to a computer. Sound has several characteristics, such as frequency and amplitude, which can be used to identify patterns. Spectrograms are a way to visualize which frequencies are present and when during a sound. They can help determine when an event starts and ends, analyze what happens during the event, or assess the quality of the event. Use cases include healthcare, where sound can evaluate the quality of a patient’s exercise, or environmental monitoring, where sound can identify which birds are present in an area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If a tree falls in the forest and no one is around to hear it, does it make a sound? A paradox rendered irrelevant by AI and sound event detection (SED). AI and advanced audio analysis can detect and classify sounds in various environments, even when no humans are present.&lt;/p&gt;

&lt;p&gt;We can uncover patterns in sound that are imperceptible to the human ear. These patterns can be used to identify machine malfunctions, monitor animals in large ecosystems, or assess the quality of a patient’s condition.&lt;/p&gt;

&lt;p&gt;Some of the tools used today include spectrograms, while the future may bring innovations like transformers and transfer learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sound and Its Digital Representation
&lt;/h2&gt;

&lt;p&gt;Sound consists of vibrations in the air known as sound waves. These waves are captured by the ear as they impact the membrane in the ear, commonly known as the eardrum. From there, the sound is amplified by small bones and passed into a fluid-filled structure called the cochlea. Inside the cochlea, tiny hair cells convert these vibrations into electrical signals, which are sent to the brain via the auditory nerve.&lt;/p&gt;

&lt;p&gt;When we work with sound digitally, it must first be converted into a digital format. This is usually done through a microphone, which transforms sound waves into electrical signals. Much like the human ear, a microphone uses a membrane that reacts to sound. The membrane’s movement pushes a coil back and forth near a magnet, generating electrical signals that a computer can process.&lt;/p&gt;

&lt;p&gt;If you’ve taken high school physics, you might remember the study of waves—and perhaps found it less than thrilling. But if you &lt;em&gt;listened,&lt;/em&gt; you may recall that waves have various characteristics, such as frequency, intensity, and wavelength. These characteristics form the foundation for how AI processes sound.&lt;/p&gt;

&lt;p&gt;One example of these characteristics is frequency—the number of times a wave moves up and down per second. Frequency determines whether a sound is perceived as a deep bass or a high-pitched tone. A high frequency corresponds to a higher-pitched tone, while a low frequency results in a deeper sound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sound in AI
&lt;/h2&gt;

&lt;p&gt;When processing sound with AI, we aim to identify patterns in raw audio or its characteristics. Two of the most popular tools, spectrograms and MFCCs, focus on analyzing frequencies.&lt;/p&gt;

&lt;p&gt;To translate sound into frequencies, a mathematical tool called Fourier Transformations (FT) is used. These transformations identify which frequencies are present in an audio clip and the magnitude of those frequencies. The result can be visualized as a graph with frequencies on the x-axis and magnitude on the y-axis, which can then be used for both filtering and pattern recognition.&lt;/p&gt;

&lt;p&gt;An example could be a recording made in a room with wall outlets and wiring in the ceiling. In such a scenario, a significant amount of sound is often detected at 50Hz, caused by the electrical current from the power grid oscillating at that frequency. If the sound of interest occurs above 50Hz, a filter can be applied to remove lower frequencies.&lt;/p&gt;

&lt;p&gt;Fourier Transformations are also used to create spectrograms, which allow us to analyze sound in both the frequency and time domains. To create a spectrogram, the audio is divided into smaller segments, and FT is applied to each segment. Filters are then applied to further distinguish between frequencies by dividing them into bands. Finally, these segments are combined into a plot where time is represented along the x-axis, frequency bands along the y-axis, and the magnitude of each frequency is shown through color. Without detailed analysis, one can already identify four distinct events in the clip.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmfc9sps06s55ydpjjok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmfc9sps06s55ydpjjok.png" alt="Spectrogram" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case - Sonohaler
&lt;/h2&gt;

&lt;p&gt;At Convai, we have collaborated with the company Sonohaler, which uses sound in the healthcare sector. Their mission is to make quality tools accessible to everyone by utilizing mobile devices as a replacement for traditional electronic measuring equipment. Their app incorporates AI that analyzes sound and tracks the user's progress. Convai’s role has been to work on event detection and quality measurement of the events.&lt;/p&gt;

&lt;p&gt;For event detection, our task was to identify the start and end times of an event. We approached this as a classification problem, analyzing small segments of sound to determine whether they contained the event.&lt;/p&gt;

&lt;p&gt;Our process began with collecting audio data containing the events of interest and annotating the start and end times. The data was then divided into small segments, each labeled as either "event" or "non-event." For each of these segments, spectrograms were generated and used in the model for training, testing, and implementation in the app. This process resulted in a model capable of analyzing an audio file and detecting where it believes an event occurs. Below is the result of the spectrogram shown earlier, with the green bars indicating where the model predicts an event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc14hg3vl4u22c5vhbrus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc14hg3vl4u22c5vhbrus.png" alt="Audio Classification" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For quality assurance, our task was to identify the intensity of an event. We approached this as a regression problem. Once again, we utilized spectrograms, as there is often a correlation between the intensity of an event and the frequency content in the sound. Similar to the classification approach, the data was broken into smaller segments and transformed into spectrograms.&lt;/p&gt;

&lt;p&gt;The key difference here is that instead of assigning each segment a label (e.g., "event" or "non-event"), the correct answer is a continuous value representing the intensity. This intensity can range from 0 to 100, and it is up to the model to predict the appropriate intensity value.&lt;/p&gt;

&lt;p&gt;The result is that the sound is converted into intensity values, which can later be used for analysis within the app or by a professional using the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Potential of Sound
&lt;/h2&gt;

&lt;p&gt;With Sonohaler, we have explored just a small portion of what sound can do for us. We utilized spectrograms for both classification and regression, but technologies like transformers, which are used in large language models, can also be applied to sound—and they are continually improving. Transformers have the potential to train on much larger datasets and to work directly with raw audio instead of relying on spectrograms.&lt;/p&gt;

&lt;p&gt;One of the challenges of working with sound is that analysis often needs to be performed on small devices, such as phones or sensors connected to microprocessors. This makes the size and efficiency of AI models and feature extraction techniques crucial. It requires creative thinking to distill the process down to its fundamental components.&lt;/p&gt;

&lt;p&gt;Beyond the healthcare sector, the environmental sector is another area where AI has been widely applied to sound. A classic example is identifying bird species in natural habitats. Birds can be difficult to spot, making image recognition impractical. However, each bird has a unique sound, both as a species and as an individual. By recording audio in an area and allowing AI to analyze the sound, it is possible to identify which birds are present in the audio clip.&lt;/p&gt;

&lt;p&gt;In the industrial sector, sound is also used for &lt;strong&gt;anomaly detection&lt;/strong&gt;, which involves identifying unusual sounds that are not normally present. This capability allows for the detection of machinery with reduced or faulty performance, helping to identify and fix issues before they become critical.&lt;/p&gt;

&lt;p&gt;Some of the large-scale sound models freely available online are highly comprehensive classification models trained on thousands of YouTube videos. These models can distinguish between more than 200 different events, including people talking, cars driving by, gunshots, and other everyday occurrences. These models also provide opportunities for further training on data tailored to your specific problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Sound contains a wealth of information that can be used for far more than just playing music or facilitating conversations. By digitizing and analyzing sound, we can gain deep insights in fields such as healthcare and environmental monitoring.&lt;/p&gt;

&lt;p&gt;Tools like spectrograms have been indispensable in recent years, but technologies like transformers may pave the way for even greater advancements. They have the potential to enable new models that perform better while remaining accessible on devices like smartphones.&lt;/p&gt;

&lt;p&gt;We are only at the beginning of exploring what sound can offer us. With the rapid advancements in AI and machine learning, the future of sound analysis is both exciting and full of possibilities.&lt;/p&gt;

</description>
      <category>deeplearning</category>
      <category>tensorflow</category>
      <category>learning</category>
    </item>
    <item>
      <title>Retrieval-Augmented Generation</title>
      <dc:creator>Mikkel</dc:creator>
      <pubDate>Tue, 03 Dec 2024 00:57:59 +0000</pubDate>
      <link>https://dev.to/mikkelsj/retrieval-augmented-generation-2ilh</link>
      <guid>https://dev.to/mikkelsj/retrieval-augmented-generation-2ilh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) builds upon large language models (LLMs) like ChatGPT and Gemini by enabling them to access relevant internal and external documents. These documents can serve as references to answer questions, assist in writing reports, or support decision-making. By leveraging references, RAG increases trust in the generated responses, enables exploration of large knowledge bases, and prevents the model from "hallucinating" answers. Hosting on platforms like Azure ensures the setup remains private and GDPR-compliant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;For over 15 years, Google has been synonymous with information retrieval. The internet became the world's library, with Google's advanced algorithm serving as our librarian. However, this paradigm has recently shifted to an all-knowing sage called ChatGPT. This sage has too much time on their hands and has read everything in the library for you. They can recall and explain it to you in seconds—even simplifying complex topics as though you were five years old.&lt;/p&gt;

&lt;p&gt;Now imagine this sage takes a crash course in your company. Perhaps you’ve amassed hundreds of documents, including internal protocols, annual reports, and methodologies. Within hours, this sage could become an expert in your organization. This crash course is called Retrieval-Augmented Generation (RAG). With RAG, you can have an AI assistant that answers questions, generates reports, and supports decision-making based on your company’s specific knowledge base.&lt;/p&gt;

&lt;p&gt;In this blog post, we’ll briefly explore what RAG is, the technologies behind it, and—most importantly—how it can benefit you. To understand RAG and specialized chatbots, it’s helpful to know a bit about NLP, embeddings, and LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Blocks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Natural Language Processing (NLP)
&lt;/h3&gt;

&lt;p&gt;Natural Language Processing (NLP) is the branch of AI that deals with understanding language. It includes applications like text-to-speech, image generation from text, and speech-to-image. Some of the biggest AI breakthroughs in recent years have come from this field. OpenAI has been a key player, introducing models like ChatGPT for language and DALL-E for image generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embeddings
&lt;/h3&gt;

&lt;p&gt;To process human language, computers need to convert words and sentences into numbers—a process known as embedding. Text is broken into smaller pieces called tokens, and each token is assigned a vector (a series of numbers) in a lookup table. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;King&lt;/strong&gt;: &lt;code&gt;[0.8, 0.6, 0.1]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Man&lt;/strong&gt;: &lt;code&gt;[0.5, 0.3, 0.1]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Woman&lt;/strong&gt;: &lt;code&gt;[0.6, 0.4, 0.1]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queen&lt;/strong&gt;: &lt;code&gt;[0.9, 0.7, 0.1]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This numerical representation enables fascinating operations. By converting words into numbers, we can perform mathematical calculations on their meanings. For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;King - Man + Woman = Queen&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;King - Man = [0.8, 0.6, 0.1] - [0.5, 0.3, 0.1] = [0.3, 0.3, 0.0]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Result + Woman = [0.3, 0.3, 0.0] + [0.6, 0.4, 0.1] = [0.9, 0.7, 0.1]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, the result is &lt;strong&gt;Queen&lt;/strong&gt;, represented by the vector &lt;code&gt;[0.9, 0.7, 0.1]&lt;/code&gt;. This is a simplification, but the principle is the same.&lt;/p&gt;

&lt;p&gt;ChatGPT embeddings operate in 1,536 dimensions, enabling it to understand the semantic relationships between words and sentences far beyond this simple example.&lt;/p&gt;

&lt;p&gt;By calculating the “cosine similarity” between two vectors, we can measure how similar the meanings of two words are. For example, if we compare the words "cat" and "dog," which have closely related vectors, we will obtain a high similarity score, indicating their semantic closeness. This enables AI models to understand the context and relationships between words in a highly nuanced way, forming the foundation for advanced NLP applications such as machine translation, sentence meaning comparison, and much more.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLMs
&lt;/h3&gt;

&lt;p&gt;The next major component of RAG is large language models, or LLMs. Popular examples include OpenAI's ChatGPT, Google’s Gemini, and Meta’s Llama. LLMs are built on transformer architectures—a revolutionary design that allows models to recognize patterns and derive meaning from vast amounts of data. For instance, ChatGPT-3 was trained on 570GB of text data, equivalent to reading a 316-million-page PDF. And newer models have only grown larger.&lt;/p&gt;

&lt;p&gt;Transformers were introduced in Google’s groundbreaking paper, &lt;em&gt;“Attention is All You Need.”&lt;/em&gt; Before this, language models primarily relied on recurrent or convolutional neural networks, which process data sequentially, step by step. These architectures were time-consuming and lacked the ability to run computations in parallel. Transformers addressed this by introducing &lt;strong&gt;attention mechanisms&lt;/strong&gt;—a way to assign meaning to words based on their surrounding context.&lt;/p&gt;

&lt;p&gt;This attention mechanism subtly modifies the vectors representing words. For example, in the earlier example, the word &lt;em&gt;king&lt;/em&gt; was represented by the vector &lt;code&gt;[0.8, 0.6, 0.1]&lt;/code&gt;. In the sentence &lt;em&gt;“The king loves his queen,”&lt;/em&gt; the vector for &lt;em&gt;king&lt;/em&gt; might transform into &lt;code&gt;[0.4, 0.5, 0.6]&lt;/code&gt;. These transformations occur through a series of calculations that evaluate the relationships between word vectors.&lt;/p&gt;

&lt;p&gt;When you ask ChatGPT a question, it responds based on all the knowledge it has accumulated during training. This foundational design enables the model to generalize and perform well across a wide range of topics. It can quickly answer questions about science, history, technology, and more, understanding the context of your inquiries to provide relevant and accurate responses. However, challenges arise when dealing with specialized tasks or ensuring trustworthiness.&lt;/p&gt;

&lt;p&gt;A helpful analogy is to think of an LLM as a judge. The judge has extensive knowledge of laws and regulations. When faced with a new, specific case, their authority and expertise improve if they have an assistant who retrieves case files from the library containing similar rulings and procedures. In this scenario, the assistant represents the RAG functionality.&lt;/p&gt;

&lt;p&gt;Before RAG, addressing such specialized tasks involved either fine-tuning the model—adding more data and retraining—or attempting to engineer specific prompts to guide the model’s behavior. Both methods were time-consuming and less efficient. This is where RAG comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG
&lt;/h3&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) is an extension of LLMs. Here’s how it works: you upload your own data—this could be PDFs, PowerPoint presentations, or Word documents. Similar to how LLMs process text, these documents are divided into smaller chunks and embedded into numerical vectors. However, RAG diverges from traditional models at this point. Instead of retraining the model on this new data, the embedded vectors are stored in a vector database.&lt;/p&gt;

&lt;p&gt;When the LLM is asked a question or tasked with generating text, it first retrieves relevant data from the vector database and uses it as context for its response. This approach allows the model to access additional data and provide answers with references.&lt;/p&gt;

&lt;p&gt;The advantages of this method are significant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; It is much faster than retraining a model with new data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust:&lt;/strong&gt; By providing references for its responses, the model builds greater trust with users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization:&lt;/strong&gt; If you have proprietary data, such as internal processes or company-specific information, the LLM can incorporate this knowledge into its responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a GDPR perspective, it’s crucial to ensure that the servers hosting your data and vectors are located within the EU. This can be a challenge, as many vector databases are hosted by U.S.-based companies. However, solutions like Azure and Supabase—which we have experience with—offer database hosting in GDPR-compliant locations such as Germany and Sweden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiences
&lt;/h2&gt;

&lt;p&gt;At Convai, we have spent the past few months working extensively with chatbots and RAG. Our projects have ranged widely, requiring various types of solutions. For simpler tasks, we’ve used a drag-and-drop framework called &lt;strong&gt;Flowise&lt;/strong&gt;. This tool allows predefined building blocks to be arranged on a board, enabling quick and efficient solutions.&lt;/p&gt;

&lt;p&gt;For more advanced projects that demand flexibility, we’ve turned to a Python library called &lt;strong&gt;LangChain&lt;/strong&gt;, the foundation upon which Flowise is built. LangChain offers a highly customizable approach, allowing us to assemble a variety of components to process data in the way that best fits the use case. Additionally, it enables us to incorporate tailored prompts for the models when needed, adding an extra layer of precision and functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential
&lt;/h2&gt;

&lt;p&gt;As you can see, there are numerous opportunities to integrate RAG into your business. By granting an LLM access to internal documents, a chatbot can serve as a domain expert. Additionally, providing access to relevant external documents can further enhance its capabilities.&lt;/p&gt;

&lt;p&gt;A traditional chatbot can be significantly improved by giving it access to the company’s FAQ. Beyond that, an LLM can be used to generate content for the company’s website. With tools like LangChain, the model can analyze existing content on the site, using it as a stylistic guide. This ensures that any new content the LLM generates matches the tone and format of the existing material.&lt;/p&gt;

&lt;p&gt;If you want to use an LLM for planning purposes, you can give the model access to historical plans. Based on this data, the LLM can create new plans, such as event schedules or meeting agendas, tailored to your needs.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>learning</category>
    </item>
    <item>
      <title>Usecase: TumbleLog</title>
      <dc:creator>Mikkel</dc:creator>
      <pubDate>Mon, 25 Nov 2024 02:28:16 +0000</pubDate>
      <link>https://dev.to/mikkelsj/usecase-tumblelog-klg</link>
      <guid>https://dev.to/mikkelsj/usecase-tumblelog-klg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A few weeks ago, I was contacted by my old coach, Dima Kachan, to help him out with an idea for a new training aid software.&lt;/p&gt;

&lt;p&gt;Dima is a very successful international gymnastics coach. He has six world championship medals, across five of his athletes and two disciplines. I have been lucky enough to train with him over a period of two years (unfortunately I haven’t won him any medals) and I’ve learned a lot. &lt;/p&gt;

&lt;p&gt;A lot of factors goes into becoming a world-class gymnast, like training intensity and recovery. One of the big factors Dima monitors, is total difficulty of skills, performed in a training session. This metric helps measure the physical demands placed on gymnasts based on how difficult skills they perform and varies depending on the training phase in a season.&lt;/p&gt;

&lt;p&gt;Currently, Dima uses spreadsheets for tracking difficulty. Athletes manually record the number of repetitions for each skill performed during a session. However, this system is cumbersome, error-prone, and often neglected by athletes. This is where I come in.&lt;/p&gt;

&lt;p&gt;This blog will first explain the more overall process I go through in a project like this, followed by a bit of technological decision making. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;When I start new project, I use an iterative process. Rapid prototyping allows me to get a functional version into users’ hands early, ensuring the app meets their needs. Feedback is invaluable, helping me avoid spending time on unnecessary features.&lt;/p&gt;

&lt;p&gt;The first step is to figure out the requirements. What does the app need to do? In this case, it need to replace the spreadsheet and make tracking and monitoring training, more dynamic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7kpgkext675wj7exvat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7kpgkext675wj7exvat.png" alt="Feature and corresponding requirements" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After spending some time talking with Dima and looking at the existing approach, the main requirements of the app is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Athletes&lt;/strong&gt; should be able to track each skill they do and on what apparatus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Athletes&lt;/strong&gt; should be able to see their difficulty for each session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coaches&lt;/strong&gt; should be able to see which skills the athlete has done during a session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coaches&lt;/strong&gt; should be able to see a weekly or monthly view of what an athlete has done&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coaches&lt;/strong&gt; should be able to assign programs (set of skills for a session) to an athlete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These requirements translated into the app’s core features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tracking&lt;/strong&gt;: Where users choose their program and record their skills&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt;: Where athlete and coach can monitor completed sessions and difficulty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programming&lt;/strong&gt;: Where the coach can put skills into programs and assign them to athletes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the features set and confirmed with Dima, I can start developing. &lt;/p&gt;

&lt;p&gt;To ensure the app is useful from the start, I prioritise features in this order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracking → Monitoring → Programming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This sequence allows me to collect and review training data early, gradually adding more functionality. Each feature is deployed on GitHub Pages for Dima and his athletes to test, providing immediate feedback. These insights guides refinements before I move on to the next feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Insights
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oqwql22iu45hcd7vejy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oqwql22iu45hcd7vejy.png" alt="Tech stack" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;p&gt;My base tools of choice are Flutter and Supabase. Flutter is a cross-platform frontend framework, which allows me to make apps for both Android, iOS and web. Supabase is my backend which delivers database and authentication, it is open source and it runs on PostgreSQL.&lt;/p&gt;

&lt;p&gt;These choices came out of necessity, as I had to implement ML models into a Flutter app, in a previous job for a medical monitoring app. I fell in love with both Flutter and Supabase and have used it since. &lt;/p&gt;

&lt;p&gt;I wanted to try out the MVC design pattern, as it seems to be the standard in a lot of companies, but as the app grew, the unfamiliar framework got in the way of quality. The controllers got too bloated and the interplay between the different components wasn’t clear to me.&lt;/p&gt;

&lt;p&gt;Because of this, I switch to Clean Architecture, which I’ve worked with in other projects. For me it offers more granularity, as the organisation into individual features makes a lot of sense to me and it ensured that each feature remained independent and testable, simplifying future maintenance and scalability as more users are added to the app. I make use of BLoC for state management as well, which makes my widgets very clean and simple. &lt;/p&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;p&gt;With the foundation of tools, architecture, and state management in place, I focus on implementing the core features that will bring the app to life. Each feature is designed to replace the existing spreadsheet workflow while prioritising ease of use and functionality for both athletes and coaches.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tracking
&lt;/h4&gt;

&lt;p&gt;The main functionality from the spreadsheet, that I replace, is tracking. It needs to be quick and simple, so the athletes wont get their training disturbed, but it just becomes a habit. In order to accomplish this, I come up with two designs. A button layout and a bar layout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u7eilo35l5ynd6pfc9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u7eilo35l5ynd6pfc9f.png" alt="Tracking views" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design needs to fit in adding, subtracting, reseting and editing a large number of skills. As you can see, my solution is a toggle menu for layout and skill name format, allowing the athletes to smoothly go back and forth. This makes it possible for the athlete to customise the experience, while utilising all the features with one tap.&lt;/p&gt;

&lt;p&gt;The functionality itself is a simple set of counters stored in BLoC and uploaded to Supabase. The challenge with this is how to format the data. Sessions and Skills are the two main models, but within them, they need to keep track of data for each piece of equipment. Although I don’t believe it’s the best solution, I’ve used a map to solve this. This keeps the data structure compact but may require additional handling for custom equipment in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Session Model:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:equatable/equatable.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:json_annotation/json_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:tumblelog/constants.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'session_model.g.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@JsonSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;explicitToJson:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fieldRename:&lt;/span&gt; &lt;span class="n"&gt;FieldRename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;athleteId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;athleteName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;totalDd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EquipmentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;equipmentDd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SessionModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;athleteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;totalDd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;athleteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'NoName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equipmentDd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultEquipmentDd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Factory method to create a SessionModel from JSON&lt;/span&gt;
  &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;SessionModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;_$SessionModelFromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Method to convert a SessionModel to JSON&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_$SessionModelToJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;athleteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;athleteName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalDd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equipmentDd&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Skill Model:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:equatable/equatable.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:json_annotation/json_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:tumblelog/constants.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'skill_model.g.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@JsonSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;explicitToJson:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fieldRename:&lt;/span&gt; &lt;span class="n"&gt;FieldRename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SkillModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EquipmentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;equipmentReps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SkillModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equipmentReps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Factory method to create a SkillModel from JSON&lt;/span&gt;
  &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;SkillModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;_$SkillModelFromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Method to convert a SkillModel to JSON&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_$SkillModelToJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equipmentReps&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, I use json_serializable, to automate the transformation from model to json. I do this to avoid a bunch of human errors I’ve made in the past. It adds more complexity up front, but saves me a lot of time down the line. &lt;/p&gt;

&lt;h4&gt;
  
  
  Monitoring
&lt;/h4&gt;

&lt;p&gt;The next feature is monitoring, which lets the coaches and athletes see their progress. After doing the data ground work in the tracking feature, this is relatively straight forward. The data is just fetched an displayed nicely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzkakh65toxuneo3b07d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzkakh65toxuneo3b07d.png" alt="Monitoring views" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This design lets the coach get an overview of how much work the athlete has done during the week, and make decision for the upcoming training. For a more detailed view, the coach can see the individual session.&lt;/p&gt;

&lt;h4&gt;
  
  
  Programming
&lt;/h4&gt;

&lt;p&gt;Finally there is the programming part. The goal is to give coaches the option to customise which skills an athlete needs to do in a session and assign this new program to athletes. Four new tables are added to the database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programs: For tracking the specific programs&lt;/li&gt;
&lt;li&gt;SkillLibrary: A database of skills for coaches to choose between&lt;/li&gt;
&lt;li&gt;ProgramSkills: For linking programs to skills&lt;/li&gt;
&lt;li&gt;AthletePrograms: For linking athletes to programs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup makes the database flexible and easy to scale, as you can add as many links as needed. The drawback is that queries can be a bit more challenging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;programsJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;supabaseClient&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'programs'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'*, athlete_programs!inner(), program_skills(skill_library(*))'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'athlete_programs.athlete_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model can also get more bloated, but for me this is worth it, as I can automate conversions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ProgramModel:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:equatable/equatable.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:json_annotation/json_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:tumblelog/core/models/skill_library_model.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'program_model.g.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@JsonSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;explicitToJson:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fieldRename:&lt;/span&gt; &lt;span class="n"&gt;FieldRename&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProgramModel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@JsonKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'program_skills'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;fromJson:&lt;/span&gt; &lt;span class="n"&gt;_extractSkillsFromProgramSkills&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SkillLibraryModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ProgramModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;/// Factory method to create a `ProgramModel` from JSON&lt;/span&gt;
  &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;ProgramModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;_$ProgramModelFromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;/// Method to convert `ProgramModel` to JSON&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_$ProgramModelToJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;/// Helper method to convert `program_skills` into a list of `SkillLibraryModel`&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SkillLibraryModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_extractSkillsFromProgramSkills&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;programSkills&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;programSkills&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;programSkills&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SkillLibraryModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'skill_library'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;/// Convert `ProgramModel` to a map for database saving&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toProgramsTableMap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;'creator_id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;/// Map skills to a list of `program_skills` entries&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mapSkillsToProgramSkills&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;programId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'program_id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;programId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;'skill_id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The end product of the programming pages looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ra82kduxd4irq8fyv1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ra82kduxd4irq8fyv1d.png" alt="Programming views" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is the rough outline of the app. As you can see, I am not a design genius and have gone with either simple or default colors. I have mainly focused on building a functioning prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Outcome and Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Now that the project is done, it needs to get used. Every time I have implemented a new feature, I have sent it to Dima for him to test with his athletes. The feedback has been very positive, but will the app stand the test of time?&lt;/p&gt;

&lt;p&gt;Will the design be simple enough for athletes to seamlessly integrate it into their training routines without distractions or missed entries?&lt;/p&gt;

&lt;p&gt;As the app is being used and more data is being recorded, hopefully we will be able to see patterns in training frequency, difficulty and performance or injuries.&lt;/p&gt;

&lt;p&gt;I might make a small workshop with the club, to see them use the app and identify pain points. &lt;/p&gt;

&lt;p&gt;Refactoring the app to Clean Architecture was a key moment where I focused on applying principles like separation of concerns and DRY. This approach made the app more maintainable and scalable.&lt;/p&gt;

&lt;p&gt;As the app continues to be used and evolves, I'm excited to see the insights it brings to Dima and his athletes. This project has been a significant step in my growth as a developer, and I'm looking forward to tackling even more challenging projects in the future.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>supabase</category>
      <category>fullstack</category>
      <category>cleanarchitecture</category>
    </item>
  </channel>
</rss>
