<?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: Google Developer Experts</title>
    <description>The latest articles on DEV Community by Google Developer Experts (gde).</description>
    <link>https://dev.to/gde</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%2Forganization%2Fprofile_image%2F11939%2Fe3080d5b-ecde-42a8-b089-bafecc31fa97.png</url>
      <title>DEV Community: Google Developer Experts</title>
      <link>https://dev.to/gde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gde"/>
    <language>en</language>
    <item>
      <title>Stop Calling getGenerativeModel() Everywhere! The Hidden Traps of Scaling Firebase AI to Multi-Model Angular Apps</title>
      <dc:creator>Connie Leung</dc:creator>
      <pubDate>Wed, 11 Nov 2026 10:53:39 +0000</pubDate>
      <link>https://dev.to/gde/stop-calling-getgenerativemodel-everywhere-the-hidden-traps-of-scaling-firebase-ai-to-lg1</link>
      <guid>https://dev.to/gde/stop-calling-getgenerativemodel-everywhere-the-hidden-traps-of-scaling-firebase-ai-to-lg1</guid>
      <description>&lt;p&gt;Calling &lt;code&gt;getGenerativeModel()&lt;/code&gt; directly inside &lt;code&gt;VisionService&lt;/code&gt; and &lt;code&gt;TextToSpeechService&lt;/code&gt; duplicates &lt;code&gt;firebase/ai&lt;/code&gt; imports, safety settings, and thinking configurations while recreating client-side &lt;code&gt;GenerativeModel&lt;/code&gt; objects on every request and triggering unnecessary garbage collection. In this post, we extract &lt;code&gt;AiModelFactory&lt;/code&gt; to centralize and cache model instances for multi-model routing, and we resolve two hidden caching traps: request-level &lt;code&gt;generationConfig&lt;/code&gt; overwrites and stale &lt;code&gt;AI&lt;/code&gt; backends after Remote Config updates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture &amp;amp; Mental Model
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;VisionService&lt;/code&gt; and &lt;code&gt;TextToSpeechService&lt;/code&gt; delegate model creation and lifecycle management to &lt;code&gt;AiModelFactory&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-Demand Model Requests&lt;/strong&gt;: &lt;code&gt;VisionService&lt;/code&gt; calls &lt;code&gt;getModel(options)&lt;/code&gt; with &lt;code&gt;AiModelOptions&lt;/code&gt;, while &lt;code&gt;TextToSpeechService&lt;/code&gt; calls &lt;code&gt;getTtsModel()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Validation and Cache Eviction (&lt;code&gt;getValidBackend()&lt;/code&gt;)&lt;/strong&gt;: Before returning a model, &lt;code&gt;getValidBackend()&lt;/code&gt; compares &lt;code&gt;ConfigService.getAiBackend()&lt;/code&gt; against &lt;code&gt;#cachedBackend&lt;/code&gt;. If Remote Config refreshes the &lt;code&gt;AI&lt;/code&gt; backend, it clears &lt;code&gt;#models&lt;/code&gt; and resets &lt;code&gt;#ttsModel&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyed Vision Model Cache (&lt;code&gt;#models&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;getModel(options)&lt;/code&gt; builds a deterministic key via &lt;code&gt;generateKey()&lt;/code&gt; and caches instances in &lt;code&gt;#models&lt;/code&gt; (&lt;code&gt;Map&amp;lt;string, GenerativeModel&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton TTS Model Cache (&lt;code&gt;#ttsModel&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;getTtsModel()&lt;/code&gt; caches a single &lt;code&gt;#ttsModel&lt;/code&gt; instance configured with &lt;code&gt;responseModalities: [ResponseModality.AUDIO]&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    subgraph Callers["Domain Services"]
        VS["VisionService (1 model now -&amp;gt; 3 models with MediaPipe routing)"]
        TTS["TextToSpeechService (per-request voice speechConfig)"]
    end

    subgraph Factory["AiModelFactory (@/core/firebase)"]
        GVB["getValidBackend(): compare with #cachedBackend"]
        EVICT["Backend changed? #models.clear() &amp;amp; #ttsModel = null"]
        GM["getModel(options?: AiModelOptions)"]
        KEY["generateKey(): model_thinking_instruction_schema_tools"]
        MAP[("#models: Map&amp;lt;string, GenerativeModel&amp;gt;")]
        GTTS["getTtsModel()"]
        TTS_CACHE[("#ttsModel: GenerativeModel | null")]
    end

    VS -- "getModel(options)" --&amp;gt; GVB
    TTS -- "getTtsModel()" --&amp;gt; GVB
    GVB -- "New AI instance" --&amp;gt; EVICT --&amp;gt; GM &amp;amp; GTTS
    GVB -- "Same AI instance" --&amp;gt; GM &amp;amp; GTTS
    GM --&amp;gt; KEY --&amp;gt; MAP
    GTTS --&amp;gt; TTS_CACHE&lt;/code&gt;&lt;/pre&gt;






&lt;h2&gt;
  
  
  1. Defining &lt;code&gt;AiModelOptions&lt;/code&gt; and Deterministic Cache Keys
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1. The &lt;code&gt;AiModelOptions&lt;/code&gt; Interface
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AiModelOptions&lt;/code&gt; interface defines the configuration needed to create a vision model and compute its cache key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optional Configuration Properties&lt;/strong&gt;: &lt;code&gt;systemInstruction&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt; (&lt;code&gt;SchemaRequest | TypedSchema&lt;/code&gt;), and the &lt;code&gt;tools&lt;/code&gt; array (&lt;code&gt;Tool[]&lt;/code&gt;) let callers configure structured JSON output, plain-text responses, or grounding tools.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/core/firebase/ai-model-options.interface.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;SchemaRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TypedSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'firebase/ai'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AiModelOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;systemInstruction&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;SchemaRequest&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;TypedSchema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;Tool&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2. Private State and &lt;code&gt;generateKey()&lt;/code&gt; in &lt;code&gt;AiModelFactory&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration Injection (&lt;code&gt;#configService&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;AiModelFactory&lt;/code&gt; injects &lt;code&gt;ConfigService&lt;/code&gt; into &lt;code&gt;#configService&lt;/code&gt; to read Remote Config values and obtain the Firebase AI Logic backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached AI Backend (&lt;code&gt;#cachedBackend&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;#cachedBackend&lt;/code&gt; stores the active &lt;code&gt;AI&lt;/code&gt; backend reference used to create generative models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton TTS Cache (&lt;code&gt;#ttsModel&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;#ttsModel&lt;/code&gt; holds the single-instance TTS model used to synthesize speech from English text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision Model Map (&lt;code&gt;#models&lt;/code&gt; and &lt;code&gt;generateKey&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;#models&lt;/code&gt; is a &lt;code&gt;Map&amp;lt;string, GenerativeModel&amp;gt;&lt;/code&gt; that stores vision models by the key returned from &lt;code&gt;generateKey()&lt;/code&gt;. The key joins the model name (&lt;code&gt;geminiModelName&lt;/code&gt;), &lt;code&gt;thinkingLevel&lt;/code&gt;, &lt;code&gt;systemInstruction&lt;/code&gt;, the structured-output &lt;code&gt;schema&lt;/code&gt;, and &lt;code&gt;tools&lt;/code&gt; with underscore (&lt;code&gt;_&lt;/code&gt;) characters.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/core/firebase/ai-model-factory.service.ts (fields &amp;amp; key generation)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;AI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GenerationConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ModelParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SafetySetting&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'firebase/ai'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiModelFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; #&lt;span class="n"&gt;configService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; #&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&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="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="n"&gt;ttsModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GenerativeModel&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="n"&gt;cachedBackend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;AI&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;private&lt;/span&gt; &lt;span class="nf"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;AiModelOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;geminiModelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thinkingLevel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appConfig&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;instruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemInstruction&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&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;schemaKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&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;toolsKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`${geminiModelName}_${thinkingLevel}_${instruction}_${schemaKey}_${toolsKey}`&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;h2&gt;
  
  
  2. Creating Models On Demand and Invalidating the Cache in &lt;code&gt;AiModelFactory&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1. Lazily Loading the SDK and Assembling Parameters (&lt;code&gt;createModel&lt;/code&gt;, &lt;code&gt;createModelParams&lt;/code&gt;, &lt;code&gt;createGenerationConfig&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;It is tempting to import the &lt;code&gt;HarmBlockThreshold&lt;/code&gt; and &lt;code&gt;HarmCategory&lt;/code&gt; enums at the top of the file, but doing so leaks &lt;code&gt;firebase/ai&lt;/code&gt; runtime code into &lt;code&gt;main.js&lt;/code&gt; and increases the initial bundle size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic SDK Import (&lt;code&gt;createModel&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;createModel()&lt;/code&gt; dynamically imports &lt;code&gt;getGenerativeModel&lt;/code&gt;, &lt;code&gt;HarmBlockThreshold&lt;/code&gt;, and &lt;code&gt;HarmCategory&lt;/code&gt; from &lt;code&gt;firebase/ai&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Safety Settings (&lt;code&gt;createModel&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;createModel()&lt;/code&gt; builds the &lt;code&gt;SafetySetting[]&lt;/code&gt; array and calls &lt;code&gt;getGenerativeModel(backend, modelParams)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thinking Config (&lt;code&gt;createGenerationConfig&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;createGenerationConfig()&lt;/code&gt; configures &lt;code&gt;thinkingLevel&lt;/code&gt; with &lt;code&gt;includeThoughts: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured Output Schema (&lt;code&gt;createGenerationConfig&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;createGenerationConfig()&lt;/code&gt; attaches &lt;code&gt;responseSchema&lt;/code&gt; and &lt;code&gt;responseMimeType: 'application/json'&lt;/code&gt; when the caller provides &lt;code&gt;options?.schema&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Parameter Assembly (&lt;code&gt;createModelParams&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;createModelParams()&lt;/code&gt; combines the model name, &lt;code&gt;generationConfig&lt;/code&gt;, and &lt;code&gt;safetySettings&lt;/code&gt;, and conditionally adds &lt;code&gt;tools&lt;/code&gt; and &lt;code&gt;systemInstruction&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// src/app/core/firebase/ai-model-factory.service.ts (model creation helpers)&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;AI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;AiModelOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HarmCategory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'firebase/ai'&lt;/span&gt;&lt;span class="p"&gt;)&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;safetySettings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SafetySetting&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="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HARM_CATEGORY_SEXUALLY_EXPLICIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BLOCK_ONLY_HIGH&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="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HARM_CATEGORY_DANGEROUS_CONTENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BLOCK_ONLY_HIGH&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="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HARM_CATEGORY_HARASSMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BLOCK_ONLY_HIGH&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="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HARM_CATEGORY_HATE_SPEECH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HarmBlockThreshold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BLOCK_ONLY_HIGH&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="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;modelParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createModelParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safetySettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&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="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelParams&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="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;createModelParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safetySettings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SafetySetting&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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AiModelOptions&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ModelParams&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;generationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGenerationConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&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;modelParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ModelParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geminiModelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;safetySettings&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;modelParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&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;options&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemInstruction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;modelParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemInstruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;systemInstruction&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;modelParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;createGenerationConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AiModelOptions&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GenerationConfig&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;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GenerationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;thinkingConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;thinkingLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thinkingLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;includeThoughts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="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;options&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseMimeType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&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;generationConfig&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;h3&gt;
  
  
  2.2. Reusing Models On Demand (&lt;code&gt;getModel&lt;/code&gt; and &lt;code&gt;getTtsModel&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Obtaining a vision model and a TTS model follows two different caching flows in &lt;code&gt;AiModelFactory&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vision Model Lookup (&lt;code&gt;getModel&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;getModel()&lt;/code&gt; generates a deterministic cache key from &lt;code&gt;AiModelOptions&lt;/code&gt; via &lt;code&gt;generateKey(options)&lt;/code&gt; and checks &lt;code&gt;this.#models.get(key)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision Model Cache Miss&lt;/strong&gt;: When the key is not in &lt;code&gt;#models&lt;/code&gt;, &lt;code&gt;getModel()&lt;/code&gt; calls &lt;code&gt;createModel()&lt;/code&gt; to instantiate the model and stores it in the map.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTS Model Flow (&lt;code&gt;getTtsModel&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;getTtsModel()&lt;/code&gt; calls &lt;code&gt;createTtsModel()&lt;/code&gt; once to instantiate a singleton TTS model configured with &lt;code&gt;responseModalities: [ResponseModality.AUDIO]&lt;/code&gt; and assigns it to &lt;code&gt;#ttsModel&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminating Per-Call Allocation and Garbage Collection&lt;/strong&gt;: Reusing cached instances across requests avoids repeated &lt;code&gt;getGenerativeModel()&lt;/code&gt; calls and unnecessary garbage collection.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// src/app/core/firebase/ai-model-factory.service.ts (on-demand getters)&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;AiModelOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValidBackend&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createTtsModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;AI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ResponseModality&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'firebase/ai'&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="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geminiTTSModelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;responseModalities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ResponseModality&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AUDIO&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="p"&gt;,&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getTtsModel&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValidBackend&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;ttsModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;ttsModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTtsModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;ttsModel&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;h3&gt;
  
  
  2.3. Evicting Cached Models When Remote Config Rotates the Backend (&lt;code&gt;getValidBackend&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stale Backend on Remote Config Refresh&lt;/strong&gt;: Constructing a &lt;code&gt;GenerativeModel&lt;/code&gt; requires a valid Firebase AI Logic &lt;code&gt;AI&lt;/code&gt; backend, which becomes outdated when dependent Firebase Remote Config parameters (such as &lt;code&gt;useLimitedUseAppCheckTokens&lt;/code&gt; and &lt;code&gt;vertexAILocation&lt;/code&gt;) receive updated values and &lt;code&gt;ConfigService&lt;/code&gt; resets its &lt;code&gt;AI&lt;/code&gt; instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purging Cached Models (&lt;code&gt;getValidBackend&lt;/code&gt;)&lt;/strong&gt;: When &lt;code&gt;getValidBackend()&lt;/code&gt; detects that &lt;code&gt;this.#configService.getAiBackend()&lt;/code&gt; no longer matches &lt;code&gt;#cachedBackend&lt;/code&gt;, it updates &lt;code&gt;#cachedBackend&lt;/code&gt;, clears the &lt;code&gt;#models&lt;/code&gt; map, and resets &lt;code&gt;#ttsModel&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// src/app/core/firebase/ai-model-factory.service.ts (cache invalidation)&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getValidBackend&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;AI&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAiBackend&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;cachedBackend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;cachedBackend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&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="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;ttsModel&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;cachedBackend&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Refactoring &lt;code&gt;VisionService&lt;/code&gt; and &lt;code&gt;TextToSpeechService&lt;/code&gt; (and the Shallow-Spread Trap)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1. Simplifying &lt;code&gt;VisionService&lt;/code&gt; for Future Multi-Model Routing
&lt;/h3&gt;

&lt;p&gt;We refactored &lt;code&gt;VisionService&lt;/code&gt; for extensibility and scalability by delegating model creation to &lt;code&gt;AiModelFactory&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Removing SDK Setup Boilerplate&lt;/strong&gt;: Introducing &lt;code&gt;AiModelFactory&lt;/code&gt; removes &lt;code&gt;SAFETY_SETTINGS&lt;/code&gt;, &lt;code&gt;ConfigService&lt;/code&gt;, and the private &lt;code&gt;getGenerativeAIModel()&lt;/code&gt; helper from &lt;code&gt;VisionService&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extracting &lt;code&gt;altTextPrompt&lt;/code&gt;&lt;/strong&gt;: Moving &lt;code&gt;altTextPrompt&lt;/code&gt; to &lt;code&gt;vision.prompt.ts&lt;/code&gt; keeps &lt;code&gt;VisionService&lt;/code&gt; focused on orchestrating image analysis—generating tags, writing accessible alt text, and finding an obscure fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-Demand Model Retrieval with Custom Tools&lt;/strong&gt;: &lt;code&gt;VisionService&lt;/code&gt; injects &lt;code&gt;AiModelFactory&lt;/code&gt; into &lt;code&gt;#aiModelFactory&lt;/code&gt; and calls &lt;code&gt;this.#aiModelFactory.getModel({ schema: ImageAnalysisSchema, tools: [{ googleSearch: {} }] })&lt;/code&gt;. While the current model uses the Google Search grounding tool, &lt;code&gt;VisionService&lt;/code&gt; can pass a Google Maps tool or custom function-calling tools through &lt;code&gt;AiModelOptions&lt;/code&gt; to configure additional models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready for MediaPipe Multi-Model Routing&lt;/strong&gt;: Because &lt;code&gt;getModel()&lt;/code&gt; caches instances by their configuration key, we can extend &lt;code&gt;VisionService&lt;/code&gt; with an on-device MediaPipe classifier to request 3 specialized models (places, people and faces, and general objects) on demand.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/core/vision/vision.service.ts (refactored excerpt)&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VisionService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; #&lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;WINDOW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; #&lt;span class="n"&gt;aiModelFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AiModelFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;analyzeImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ImageAnalysisResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optimizationMetrics&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;preprocessImageForVision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&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;imagePart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InlineDataPart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inlineData'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mimeType&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="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;aiModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;aiModelFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ImageAnalysisSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;tools&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="n"&gt;googleSearch&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="p"&gt;}&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="p"&gt;)&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;result&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;aiModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;altTextPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imagePart&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;// ... parse response, tokenUsage, and grounding citations&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;h3&gt;
  
  
  3.2. Reusing &lt;code&gt;#ttsModel&lt;/code&gt; in &lt;code&gt;TextToSpeechService&lt;/code&gt; Without Wiping Out &lt;code&gt;responseModalities&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Similarly, &lt;code&gt;TextToSpeechService&lt;/code&gt; delegates TTS model creation to &lt;code&gt;AiModelFactory&lt;/code&gt; while passing the selected voice per request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared TTS Model Lookup&lt;/strong&gt;: &lt;code&gt;TextToSpeechService&lt;/code&gt; injects &lt;code&gt;AiModelFactory&lt;/code&gt; into &lt;code&gt;#aiModelFactory&lt;/code&gt;, and &lt;code&gt;createAIModelGenerationRequest()&lt;/code&gt; retrieves the cached TTS model via &lt;code&gt;this.#aiModelFactory.getTtsModel()&lt;/code&gt; instead of instantiating a new model for every synthesis call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-Request &lt;code&gt;speechConfig&lt;/code&gt; Assembly&lt;/strong&gt;: The &lt;code&gt;createAIModelGenerationRequest(text, voiceName)&lt;/code&gt; helper builds the &lt;code&gt;GenerateContentRequest&lt;/code&gt; (shared by &lt;code&gt;synthesizeStream()&lt;/code&gt; and &lt;code&gt;synthesize()&lt;/code&gt;) and configures &lt;code&gt;speechConfig&lt;/code&gt; with the requested &lt;code&gt;voiceName&lt;/code&gt; and &lt;code&gt;languageCode: 'en-US'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserving &lt;code&gt;ResponseModality.AUDIO&lt;/code&gt; Against Shallow Overwrites&lt;/strong&gt;: In &lt;code&gt;@firebase/ai&lt;/code&gt;, passing a request-level &lt;code&gt;generationConfig&lt;/code&gt; overwrites the model's default &lt;code&gt;aiModel.generationConfig&lt;/code&gt;. Spreading &lt;code&gt;...aiModel.generationConfig&lt;/code&gt; into the request's &lt;code&gt;generationConfig&lt;/code&gt; preserves &lt;code&gt;responseModalities: [ResponseModality.AUDIO]&lt;/code&gt; alongside &lt;code&gt;speechConfig&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/core/speech/text-to-speech.service.ts (refactored excerpt)&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextToSpeechService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; #&lt;span class="n"&gt;aiModelFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AiModelFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createAIModelGenerationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voiceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AIModelGenerativeRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;aiModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;aiModelFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTtsModel&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Content&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="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;parts&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="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'text'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&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="p"&gt;}&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GenerationConfig&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aiModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;speechConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;voiceConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;prebuiltVoiceConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;voiceName&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="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;languageCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'en-US'&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="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="n"&gt;aiModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;generationRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generationConfig&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;synthesizeStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SpeechPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AsyncGenerator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AudioStreamChunk&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;aiModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generationRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAIModelGenerationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;)&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;responseStream&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;aiModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContentStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generationRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;responseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&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;chunkData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractValidChunkData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&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;chunkData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;decodeAudioChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunkData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunkData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mimeType&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;synthesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SpeechPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;aiModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generationRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAIModelGenerationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="p"&gt;)&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;result&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;aiModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generationRequest&lt;/span&gt;&lt;span class="p"&gt;)&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;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractValidChunkData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'No audio data received in response.'&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;toWavBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mimeType&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Lazy Model Creation&lt;/strong&gt;: &lt;code&gt;AiModelFactory&lt;/code&gt; dynamically imports &lt;code&gt;firebase/ai&lt;/code&gt; to construct both text-to-speech and image-analysis models. It centralizes safety settings and &lt;code&gt;thinkingLevel&lt;/code&gt; for the image-analysis model while locking &lt;code&gt;responseModalities&lt;/code&gt; to &lt;code&gt;[ResponseModality.AUDIO]&lt;/code&gt; for the TTS model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Reuse Over Per-Request Allocation&lt;/strong&gt;: &lt;code&gt;AiModelFactory&lt;/code&gt; caches models in the &lt;code&gt;#models&lt;/code&gt; map or the &lt;code&gt;#ttsModel&lt;/code&gt; field and reuses them across requests, eliminating unnecessary per-request &lt;code&gt;GenerativeModel&lt;/code&gt; instantiation and garbage collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable Multi-Model Routing for &lt;code&gt;VisionService&lt;/code&gt;&lt;/strong&gt;: Keyed caching in the &lt;code&gt;#models&lt;/code&gt; map makes &lt;code&gt;VisionService&lt;/code&gt; scalable so an upcoming MediaPipe classifier can route images to specialized models (places, people and faces, and general objects) for analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserving &lt;code&gt;ResponseModality.AUDIO&lt;/code&gt; Across Per-Request Voices&lt;/strong&gt;: Because text-to-speech synthesis accepts a voice per request, spreading &lt;code&gt;...aiModel.generationConfig&lt;/code&gt; into the request's &lt;code&gt;generationConfig&lt;/code&gt; preserves &lt;code&gt;ResponseModality.AUDIO&lt;/code&gt; alongside the selected &lt;code&gt;voiceName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Cache Invalidation on Remote Config Updates&lt;/strong&gt;: When &lt;code&gt;ConfigService&lt;/code&gt; fetches Remote Config values in the background and invalidates the cached &lt;code&gt;AI&lt;/code&gt; backend, &lt;code&gt;AiModelFactory&lt;/code&gt; purges all cached models and recreates fresh instances on the next call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/railsstudent/ng-firebase-tts" rel="noopener noreferrer"&gt;GitHub Repository: ng-firebase-tts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/ai-logic" rel="noopener noreferrer"&gt;Firebase AI Logic Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/ai-logic/generate-structured-output" rel="noopener noreferrer"&gt;Generating Structured Output with Firebase AI Logic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/ai-logic/analyze-images?api=vertex#before-you-begin" rel="noopener noreferrer"&gt;Analyze Images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/docs/ai-logic/grounding-google-search?api=vertex#ground-the-model" rel="noopener noreferrer"&gt;Grounding with Google Search in Firebase AI Logic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>firebase</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Google AI Classroom Part 10: Green AI FinOps, Self-Cleaning Storage Lifecycles &amp; 1-Command Terraform IaC for Multi-School Deployment</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Wed, 11 Nov 2026 08:21:46 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-10-green-ai-finops-self-cleaning-storage-lifecycles-1-command-1fn</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-10-green-ai-finops-self-cleaning-storage-lifecycles-1-command-1fn</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Three Pillars of Institutional Day-2 Production Readiness
&lt;/h2&gt;

&lt;p&gt;Building a flashy AI classroom demo for a hackathon takes a weekend; operating a production AI platform for &lt;strong&gt;teaching, learning, student performance evaluation, and proctored examinations&lt;/strong&gt; across an academic department with dozens of lecturers, thousands of students, and strict public-institution budget governance requires three &lt;strong&gt;Day-2 Engineering Guarantees&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Granular FinOps &amp;amp; Atomic Quota Circuit Breakers&lt;/strong&gt;: Department administrators must see exact, itemized USD spending down to the sixth decimal place across &lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;🪶 Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;🎙️ Gemini 3.5 Transcribe (&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt;)&lt;/strong&gt;, combined with real-time Cloud Storage byte tracking across &lt;strong&gt;9 distinct media categories&lt;/strong&gt; and automated circuit breakers that halt runaway spending before a single class can exceed its budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Cleaning Storage Lifecycles &amp;amp; Zero-Orphan Cascading Purge&lt;/strong&gt;: Over a semester of recorded lectures, practical task grading videos, capstone demos, and proctored exam evidence, classes generate thousands of media files. Student screenshots, lab videos, voice consultations, and exam clips must expire automatically via configurable TTL policies after grading and exam appeals conclude—and deleting a class at the end of an academic year must purge &lt;strong&gt;100% of associated Cloud Storage blobs and 20+ Firestore subcollections/collections&lt;/strong&gt; with zero orphaned records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1-Command Reproducible Terraform Infrastructure-as-Code (&lt;code&gt;./setup-new-project.sh&lt;/code&gt;) &amp;amp; Continuous Verification&lt;/strong&gt;: Any university, polytechnic, or Google Developer community worldwide must be able to provision a fresh Google Cloud project—enabling all 27 APIs, Firestore Native databases, Cloud Storage buckets, Identity Platform (GCIP), and a 24/7 Demo Sandbox—with a single terminal command, guarded by automated unit, security rules, and Playwright E2E test suites.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;strong&gt;Part 10 (the finale of our 10-part Google Developer Expert series)&lt;/strong&gt;, we walk through the exact production code and infrastructure patterns that deliver all three guarantees.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Diagram 1: Real-Time AI FinOps Pipeline, Live Cloud Billing SKU Sync &amp;amp; Atomic Quota Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/scheduled_tasks/scheduledTasks.js" rel="noopener noreferrer"&gt;&lt;code&gt;scheduledTasks.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/cost.js" rel="noopener noreferrer"&gt;&lt;code&gt;cost.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/jobLogger.js" rel="noopener noreferrer"&gt;&lt;code&gt;jobLogger.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useCloudPricing.js" rel="noopener noreferrer"&gt;&lt;code&gt;useCloudPricing.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/utils/aiCostAggregator.js" rel="noopener noreferrer"&gt;&lt;code&gt;aiCostAggregator.js&lt;/code&gt;&lt;/a&gt;, every AI invocation and storage event is metered in real time against live Google Cloud Billing Catalog SKU rates:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fph1fwpngf6qpe8291gl8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fph1fwpngf6qpe8291gl8.png" alt="Real-Time AI FinOps Pipeline, Live Cloud Billing SKU Sync &amp;amp; Atomic Quota Circuit Breakers" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code 1: Live Google Cloud Billing Catalog SKU Sync (&lt;code&gt;syncGeminiPricing&lt;/code&gt;) &amp;amp; Multi-Stage Cost Engine (&lt;code&gt;cost.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Hardcoding cloud pricing constants in frontend code inevitably drifts as new model tiers launch or regional storage SKU rates change. Every 24 hours, &lt;code&gt;syncGeminiPricing&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/scheduled_tasks/scheduledTasks.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/scheduled_tasks/scheduledTasks.js&lt;/code&gt;&lt;/a&gt; queries the official &lt;strong&gt;Google Cloud Billing Catalog API v1&lt;/strong&gt; for both the Gemini / Cloud AI service (&lt;code&gt;C7E2-9256-1C43&lt;/code&gt;) and Regional Standard Cloud Storage in Hong Kong (&lt;code&gt;95FF-2EF5-5EA1&lt;/code&gt;, &lt;code&gt;asia-east2&lt;/code&gt;), writing the authoritative rate table into &lt;code&gt;system_config/pricing&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/scheduled_tasks/scheduledTasks.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;syncGeminiPricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onSchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'every 24 hours'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'256MB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FUNCTION_REGION&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;VERTEX_SERVICE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'C7E2-9256-1C43'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;STORAGE_SERVICE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'95FF-2EF5-5EA1'&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;pricingData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;'gemini-4-argon'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'gemini-3.5-flash-lite'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'gemini-3.8-flash'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'gemini-3.7-pro'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'gemini-3.5-transcribe-preview'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'cloud-storage'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'GiB/month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;ratePerGibMonth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'asia-east2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Standard Storage Hong Kong (Baseline)'&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="n"&gt;lastSyncedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&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="nf"&gt;toISOString&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="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'catalog_sync_or_baseline'&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;// Queries https://cloudbilling.googleapis.com/v1/services/{SERVICE_ID}/skus&lt;/span&gt;
  &lt;span class="c1"&gt;// Extracts live nanos/units tieredRates for asia-east2 &amp;amp; updates system_config/pricing&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'system_config'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pricing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pricingData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;}&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;On the AI execution plane, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/cost.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/cost.js&lt;/code&gt;&lt;/a&gt; maintains a 12-hour warm memory cache of &lt;code&gt;system_config/pricing&lt;/code&gt;, normalizes token usage metadata across Genkit (&lt;code&gt;inputTokens&lt;/code&gt;/&lt;code&gt;outputTokens&lt;/code&gt;), the Google GenAI SDK (&lt;code&gt;promptTokenCount&lt;/code&gt;/&lt;code&gt;candidatesTokenCount&lt;/code&gt;), and composite two-stage pipelines (&lt;code&gt;gemini-3.5-transcribe-preview + gemini-4-argon&lt;/code&gt;), and computes exact sub-cent USD cost (&lt;code&gt;Number((inputCost + outputCost).toFixed(6))&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/cost.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;MODEL_PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-4-argon'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-flash-lite'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.8-flash'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.7-flash'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.7-pro'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-transcribe'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-transcribe-preview'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&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="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_MODEL&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;usageMetadata&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'object'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;pricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getModelPricing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&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;promptTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promptTokenCount&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promptTokens&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="p"&gt;)&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;candidatesTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidatesTokenCount&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completionTokens&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="p"&gt;)&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;inputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="no"&gt;MODEL_PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_MODEL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&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;outputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidatesTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="no"&gt;MODEL_PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_MODEL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&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="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;outputCost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&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="mi"&gt;0&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;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/AiCostReportView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;AiCostReportView.jsx&lt;/code&gt;&lt;/a&gt;, lecturers and system administrators can inspect itemized AI token costs alongside an interactive &lt;strong&gt;1–12 Month Cloud Storage Holding Period Slider&lt;/strong&gt;, viewing exact storage &amp;amp; AI costs across 18 categories—including &lt;strong&gt;&lt;code&gt;👥 Project Meeting Audio, Demos &amp;amp; Photos&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;voiceClipsCount&lt;/code&gt;, &lt;code&gt;demoClipsCount&lt;/code&gt;, &lt;code&gt;photosCount&lt;/code&gt;) and &lt;strong&gt;&lt;code&gt;📋 Task Rubric &amp;amp; Submissions&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Diagram 2: 9-Category Event-Driven Storage Quota &amp;amp; 4-Stage Cascading Class Purge (&lt;code&gt;onClassDocDeleted&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;How do we prevent a class from uploading unlimited gigabytes of video, and how do we guarantee zero orphaned files when a class is deleted?&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code 2: 9-Category Event-Driven Cloud Storage Quota Metering &amp;amp; Self-Healing Non-Negative Drift Guard (&lt;code&gt;storageQuota.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Every time a file is finalized or deleted in our Google Cloud Storage bucket, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/storageQuota.js" rel="noopener noreferrer"&gt;&lt;code&gt;updateStorageUsageOnUpload&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/storageQuota.js" rel="noopener noreferrer"&gt;&lt;code&gt;updateStorageUsageOnDelete&lt;/code&gt;&lt;/a&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/storageQuota.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/storage_triggers/storageQuota.js&lt;/code&gt;&lt;/a&gt; classify the blob path into &lt;strong&gt;one of 9 granular storage categories&lt;/strong&gt; via &lt;code&gt;getStorageCategoryAndClass(filePath)&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage Path Prefix&lt;/th&gt;
&lt;th&gt;Firestore Metadata Counter (&lt;code&gt;classes/{id}/metadata/storage&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Classroom Asset Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;screenshots/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageScreenShots&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raw periodic screen &amp;amp; webcam JPEGs (auto-purged after MP4 compilation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;videos/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageVideos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compiled 1-FPS H.264 student session timelapse MP4s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;recordings/{classId}/...&lt;/code&gt; &amp;amp; &lt;code&gt;subtitles/{classId}/...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageRecordings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Teacher lecture recordings, normalized M4A audio &amp;amp; &lt;code&gt;.vtt&lt;/code&gt;/&lt;code&gt;.srt&lt;/code&gt; subtitles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;audio/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageAudio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Student acoustic anomaly snippets &amp;amp; session audio chunks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;irregularities/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageIrregularities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High-severity proctoring incident snapshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zips/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageZips&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bulk evidence archive &lt;code&gt;.zip&lt;/code&gt; exports (24-hour auto-TTL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;classes/{classId}/tasks/...&lt;/code&gt;, &lt;code&gt;tasks/...&lt;/code&gt;, &lt;code&gt;submissions/...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageTasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Practical task reference demo videos &amp;amp; sliced student attempt MP4s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reports/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageReports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Court-ready &lt;code&gt;.docx&lt;/code&gt; &amp;amp; &lt;code&gt;.csv&lt;/code&gt; forensic incident dossiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;projectMeetings/{classId}/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storageUsageProjectMeetings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Capstone consultation voice clips, &lt;code&gt;720p&lt;/code&gt; mobile camera demo videos &amp;amp; &lt;code&gt;0.1s&lt;/code&gt; photos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If an uploaded file pushes &lt;code&gt;storageUsage&lt;/code&gt; above the class's configured &lt;code&gt;storageQuota&lt;/code&gt;, &lt;code&gt;updateStorageUsageOnUpload&lt;/code&gt; &lt;strong&gt;immediately deletes the offending blob from Cloud Storage&lt;/strong&gt; and atomically reverts the counter via &lt;code&gt;FieldValue.increment(-fileSize)&lt;/code&gt;! Furthermore, &lt;code&gt;updateStorageUsageOnDelete&lt;/code&gt; includes a &lt;strong&gt;Self-Healing Non-Negative Drift Guard&lt;/strong&gt; so out-of-order deletion events can never drive any category counter below &lt;code&gt;0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/storage_triggers/storageQuota.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStorageCategoryAndClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&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;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'classes'&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="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usageField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageTasks'&lt;/span&gt; &lt;span class="p"&gt;}&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;classId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&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;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;screenshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageScreenShots'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageVideos'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;recordings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageRecordings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subtitles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageRecordings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageAudio'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;irregularities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageIrregularities'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;zips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageZips'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageTasks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;submissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageTasks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageReports'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;projectMeetings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageProjectMeetings'&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usageField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;And if a lecturer or system administrator ever wants to verify that Firestore's event-driven byte counters match the physical bytes stored in the Google Cloud Storage bucket after a bulk manual archive purge, clicking &lt;strong&gt;🔄 Audit &amp;amp; Recalculate Storage&lt;/strong&gt; invokes &lt;code&gt;recalculateStorageUsage&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/storageQuota.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/storage_triggers/storageQuota.js&lt;/code&gt;&lt;/a&gt;, scanning every physical prefix in parallel and stamping &lt;code&gt;lastAuditedAt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/storage_triggers/storageQuota.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recalculateStorageUsageInternal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&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;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adminStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`screenshots/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageScreenShots'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`videos/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageVideos'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`zips/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageZips'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`audio/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageAudio'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`recordings/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageRecordings'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`subtitles/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageRecordings'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`irregularities/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageIrregularities'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`classes/${classId}/tasks/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageTasks'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`tasks/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageTasks'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`submissions/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageTasks'&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="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`reports/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'storageUsageReports'&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="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;storageUsageScreenShots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageUsageVideos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageUsageZips&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storageUsageAudio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageUsageRecordings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageUsageIrregularities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storageUsageTasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageUsageReports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageUsage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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="k"&gt;for&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;cat&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]&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;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefix&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="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;catTotal&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;for&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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&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;sz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;catTotal&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&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="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;catTotal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storageUsage&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;catTotal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`classes/${classId}/metadata/storage`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastAuditedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FieldValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serverTimestamp&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="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`classes/${classId}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;storageUsage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storageUsage&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="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&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;h3&gt;
  
  
  Production Code 3: The 4-Stage Zero-Orphan Cascading Class Purge (&lt;code&gt;onClassDocDeleted&lt;/code&gt; in &lt;code&gt;cleanupTriggers.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;In Firestore, deleting a parent document (&lt;code&gt;classes/{classId}&lt;/code&gt;) does &lt;strong&gt;not&lt;/strong&gt; automatically delete its subcollections, top-level job documents referencing &lt;code&gt;classId&lt;/code&gt;, or physical files in Cloud Storage.&lt;/p&gt;

&lt;p&gt;To guarantee &lt;strong&gt;100% Zero-Orphan Cleanup&lt;/strong&gt; when a teacher deletes a class at the end of a semester, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/cleanupTriggers.js" rel="noopener noreferrer"&gt;&lt;code&gt;onClassDocDeleted&lt;/code&gt;&lt;/a&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/cleanupTriggers.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/storage_triggers/cleanupTriggers.js&lt;/code&gt;&lt;/a&gt; executes a comprehensive &lt;strong&gt;4-Stage Cascading Purge&lt;/strong&gt;:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7fdbfw1i2qn6iqnw7v4q.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7fdbfw1i2qn6iqnw7v4q.png" alt="4-Stage Zero-Orphan Cascading Class Purge (onClassDocDeleted)" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the production implementation from &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/storage_triggers/cleanupTriggers.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/storage_triggers/cleanupTriggers.js&lt;/code&gt;&lt;/a&gt;, using 500-document chunked batch commits (&lt;code&gt;deleteCollectionByClassId&lt;/code&gt; and &lt;code&gt;deleteSubcollectionByPath&lt;/code&gt;) to respect Firestore's 500-operation batch ceiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/storage_triggers/cleanupTriggers.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;onClassDocDeleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onDocumentDeleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'classes/{classId}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FUNCTION_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'512MiB'&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;classId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classId&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;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket&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;// Stage 1: Purge all 13 Cloud Storage prefixes (including projectMeetings &amp;amp; taskDemos)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;prefixes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sb"&gt;`screenshots/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`videos/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`zips/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sb"&gt;`audio/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`recordings/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`subtitles/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sb"&gt;`irregularities/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`classes/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`tasks/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sb"&gt;`submissions/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`reports/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`taskDemos/${classId}/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sb"&gt;`projectMeetings/${classId}/`&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&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;prefix&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;prefixes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;catch&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;=&amp;gt;&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Stage 2: Purge top-level collections matching classId in 500-doc batches&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;collectionsToClean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'screenshots'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'audio'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'videoJobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'zipJobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'videoAnalysisJobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'irregularities'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'progress'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'propertyUploadJobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'lectureMergeJobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lectureSubtitleJobs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'aiJobs'&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&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;col&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;collectionsToClean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deleteCollectionByClassId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classId&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;// Stage 3: Purge all 20+ nested subcollections in 500-doc batches&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;subcollectionsToPurge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'projectMeetings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'projectGroups'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'projectStudentProfiles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'activeMeetingCaptures'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lectureRecordings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bingoSessions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lessons'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bingoRecords'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'attendanceAdjustments'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'studentProperties'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'classProperties'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'passkeyBypassRequests'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'screenBroadcastViewers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'messages'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'audio_audits'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'irregularities'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'questionBank'&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&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;subcol&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;subcollectionsToPurge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deleteSubcollectionByPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`classes/${classId}/${subcol}`&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;// Stage 4: Unlink classId from teacherProfiles &amp;amp; studentProfiles&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;removeClassFromProfiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'teacherProfiles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;removeClassFromProfiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'studentProfiles'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classId&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Diagram 3: 1-Command Zero-Click Terraform Onboarding &amp;amp; Multi-Layer Automated Verification Pyramid
&lt;/h2&gt;

&lt;p&gt;To empower any university, school, or Google Developer Group (GDG) to deploy their own sovereign instance of the Google AI Classroom without clicking through dozens of Cloud Console wizards, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/setup-new-project.sh" rel="noopener noreferrer"&gt;&lt;code&gt;setup-new-project.sh&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/tree/main/terraform" rel="noopener noreferrer"&gt;&lt;code&gt;terraform/&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/deploy.sh" rel="noopener noreferrer"&gt;&lt;code&gt;deploy.sh&lt;/code&gt;&lt;/a&gt; automate the entire provisioning and verification lifecycle:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9r9kuih9twlvfisvo1qi.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9r9kuih9twlvfisvo1qi.png" alt="1-Command Zero-Click Terraform IaC &amp;amp; Automated Verification Pyramid" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How &lt;code&gt;./setup-new-project.sh&lt;/code&gt; Eliminates Manual Cloud Console Clicks
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;./setup-new-project.sh my-school-ai-2026 01ABCD-23EFGH-45IJKL&lt;/code&gt; executes an end-to-end automated pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Project Creation &amp;amp; Billing Link&lt;/strong&gt;: Creates the GCP project (if not already present) and attaches the billing account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform State &amp;amp; 27 APIs&lt;/strong&gt;: Runs &lt;code&gt;terraform init &amp;amp;&amp;amp; terraform apply&lt;/code&gt; inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/tree/main/terraform" rel="noopener noreferrer"&gt;&lt;code&gt;terraform/&lt;/code&gt;&lt;/a&gt;, enabling Compute, Cloud Run, Cloud Functions Gen 2, Cloud Build, Artifact Registry, Eventarc, Cloud Tasks, Cloud Scheduler, Secret Manager, Google Cloud AI / Firebase AI Logic (&lt;code&gt;aiplatform.googleapis.com&lt;/code&gt; &amp;amp; &lt;code&gt;firebasevertexai.googleapis.com&lt;/code&gt;), Cloud Billing Catalog, Firestore Native, Firebase Storage, and Identity Platform (GCIP).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Codebase Config Synchronization (&lt;code&gt;sync-config.sh&lt;/code&gt;)&lt;/strong&gt;: Automatically extracts the newly generated Firebase Web App SDK credentials and writes them into &lt;code&gt;web-app/.env&lt;/code&gt; and &lt;code&gt;functions/config.js&lt;/code&gt;, then runs &lt;code&gt;./sync-config.sh&lt;/code&gt; to propagate identical region, CORS, and model constants across all &lt;strong&gt;7 isolated Cloud Functions codebases&lt;/strong&gt; (&lt;code&gt;ai_flows&lt;/code&gt;, &lt;code&gt;media_processing&lt;/code&gt;, &lt;code&gt;lecture_processing&lt;/code&gt;, &lt;code&gt;scheduled_tasks&lt;/code&gt;, &lt;code&gt;storage_triggers&lt;/code&gt;, &lt;code&gt;ClassFunctions&lt;/code&gt;, and &lt;code&gt;user_management&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Cold-Start 24/7 Demo Sandbox Seeding (&lt;code&gt;seed-demo-data.mjs&lt;/code&gt;)&lt;/strong&gt;: Pre-populates the &lt;code&gt;IT114115-Demo&lt;/code&gt; class with realistic student telemetry, pre-compiled H.264 lab videos, bilingual &lt;code&gt;.vtt&lt;/code&gt; lecture recordings, and all &lt;strong&gt;13 Default AI System Prompts&lt;/strong&gt; so teachers can test every Gemini 4 Argon studio within 60 seconds of deployment!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  5. Summary of the 10-Part Google AI Classroom Series
&lt;/h2&gt;

&lt;p&gt;Across this 10-part architectural deep dive, we have shared every layer of the &lt;strong&gt;Google AI Classroom&lt;/strong&gt; platform for teaching, learning, student performance evaluation, and proctored assessments:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Architectural Focus&lt;/th&gt;
&lt;th&gt;Key Google AI &amp;amp; Cloud Technologies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-1-solving-the-25-per-exam-problem-architecting-a-sub-002student-fjc"&gt;Google AI Classroom Part 1&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/01-serverless-ai-classroom-architecture.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Solving the $25-Per-Exam Problem: Serverless Architecture&lt;/td&gt;
&lt;td&gt;Gemini 4 Argon, Gemma 4 E2B, Firebase Gen 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-2-zero-egress-browser-edge-ai-investigator-running-gemma-4-e2b-2k6a"&gt;Google AI Classroom Part 2&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/02-browser-edge-ai-gemma-4-mediapipe-whisper.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Zero-Egress Browser Edge AI Investigator (Gemma 4 E2B, MediaPipe, Whisper)&lt;/td&gt;
&lt;td&gt;WebGPU Gemma 4 E2B, MediaPipe 3D Mesh, LiteRT Whisper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-3-multimodal-orchestration-right-sizing-gemini-4-argon-gemini-38-538b"&gt;Google AI Classroom Part 3&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/03-gemini-4-argon-genkit-multimodal-resilience.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Frontier Multimodal Orchestration with Gemini 4 &amp;amp; Genkit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gemini-4-argon&lt;/code&gt;, &lt;code&gt;3.8-flash&lt;/code&gt;, &lt;code&gt;3.5-lite&lt;/code&gt;, Google Genkit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-4-defeating-proxy-attendance-lab-keyloggers-with-webauthn-passkeys-2gc0"&gt;Google AI Classroom Part 4&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/04-webauthn-passkeys-rotating-hmac-qr-auth.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;WebAuthn Passkeys, Rotating HMAC QRs &amp;amp; GCIP Blocking&lt;/td&gt;
&lt;td&gt;FIDO2 WebAuthn, GCIP Blocking Hooks, 15s HMAC QR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-5-real-time-multilingual-live-subtitles-hong-kong-code-switching-with-11ap"&gt;Google AI Classroom Part 5&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/05-realtime-live-subtitles-code-switching-gemini-4-gemma-4.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Real-Time Multilingual Live Subtitles &amp;amp; Code-Switching&lt;/td&gt;
&lt;td&gt;Gemini 4/3.8, LiteRT Gemma 4 E2B, Audio Worklet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-6-low-bandwidth-classroom-screen-broadcasting-single-pass-whole-lecture-1k0l"&gt;Google AI Classroom Part 6&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/06-scalable-screen-broadcast-and-gemini-4-lecture-studio.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Low-Bandwidth Classroom Screen Broadcast &amp;amp; Lecture Recording Studio&lt;/td&gt;
&lt;td&gt;Firestore WebP Delta Sync, Gemini 3.5 Transcribe + Gemini 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-7-serverless-map-reduce-map-video-intelligence-rubric-driven-practical-12on"&gt;Google AI Classroom Part 7&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/07-serverless-map-reduce-video-and-gemini-4-rubric-grading.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Map-Reduce-Map Video Intelligence &amp;amp; Practical Task Grading&lt;/td&gt;
&lt;td&gt;Cloud Run FFmpeg, Cloud Tasks, Gemini 4 Rubric Engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-8-ai-project-supervision-streamlined-mobile-720p-demo-2-stage-3h98"&gt;Google AI Classroom Part 8&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/08-multimodal-capstone-project-supervision-gemini-4.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Streamlined Mobile 720p Capstone Supervision &amp;amp; Log Auditing&lt;/td&gt;
&lt;td&gt;Single-Device Mobile Studio, 2-Stage Diarization + Gemini 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/gde/google-ai-classroom-part-9-gamified-bingo-active-presence-verification-zero-trust-proctored-52dg"&gt;Google AI Classroom Part 9&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/09-gamified-bingo-active-presence-and-zero-trust-exam-mode.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Gamified Bingo Active Presence &amp;amp; Zero-Trust Proctored Exam Mode&lt;/td&gt;
&lt;td&gt;Two-Strike Cloud Tasks, &lt;code&gt;.docx&lt;/code&gt; Dossiers, Storage Rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://dev.to/cyruswong"&gt;Google AI Classroom Part 10&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/blogs/10-green-ai-finops-storage-lifecycle-and-terraform-iac.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Green AI FinOps, Storage Lifecycles &amp;amp; 1-Command Terraform&lt;/td&gt;
&lt;td&gt;Cloud Billing Catalog API, Cascading Purge, Terraform IaC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By combining &lt;strong&gt;Browser WebGPU Edge AI Investigation (&lt;code&gt;Gemma 4 E2B&lt;/code&gt;, &lt;code&gt;MediaPipe 3D Face Mesh&lt;/code&gt;, &lt;code&gt;LiteRT Whisper&lt;/code&gt;)&lt;/strong&gt; with &lt;strong&gt;Frontier Cloud Multimodal Reasoning (&lt;code&gt;Gemini 4 Argon&lt;/code&gt;, &lt;code&gt;Gemini 3.8 Flash&lt;/code&gt;, &lt;code&gt;Gemini 3.5 Flash-Lite&lt;/code&gt;)&lt;/strong&gt;, we achieved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Proctored Exam Investigation Across 40+ Seats&lt;/strong&gt; (catching fleeting 3-second visual and spoken exam irregularities at 60 FPS with synchronized screen, webcam, and audio evidence),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal Teacher Agency for Teaching &amp;amp; Student Performance&lt;/strong&gt; to choose &lt;strong&gt;Gemini 4 (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; across every live subtitle, screen broadcast, bingo, practical video grading, and capstone supervision studio, and&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale-to-Zero FinOps &amp;amp; Automated Storage Lifecycles&lt;/strong&gt; (eliminating idle server billing and orphaned storage via on-device edge inference, atomic AI quota circuit breakers, and cascading class purges).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Explore the Open-Source Repository
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/docs/presentation/google-cloud-slides.marp.md" rel="noopener noreferrer"&gt;🎤 View the 45-Minute Google Cloud Tech Talk Slide Deck (&lt;code&gt;google-cloud-slides.marp.md&lt;/code&gt;)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/README.md" rel="noopener noreferrer"&gt;🏠 Main Repository README &amp;amp; Architecture Documentation Index&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>finops</category>
      <category>terraform</category>
      <category>devops</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Google AI Classroom Part 9: Gamified "Bingo" Active Presence Verification &amp; Zero-Trust Proctored Exam Mode on Google Cloud</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Wed, 11 Nov 2026 07:49:09 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-9-gamified-bingo-active-presence-verification-zero-trust-proctored-52dg</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-9-gamified-bingo-active-presence-verification-zero-trust-proctored-52dg</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Problem Statement: Defeating "Fake Video Playback", Proxy Classmates &amp;amp; Static Screen Decoys
&lt;/h2&gt;

&lt;p&gt;In a 3-hour coding lab or a 100-student hybrid lecture, passive screen-share telemetry and traditional door-scan attendance have three classic blind spots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Static Screen Decoy (AFK)&lt;/strong&gt;: A student opens VS Code or a lecture slide deck, starts screen sharing, and walks away from their workstation for 45 minutes while their screen capture continues uploading identical frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Give Password to a Classmate + Play a Pre-Recorded Video" Proxy Lab Exploit&lt;/strong&gt;: Even worse, an absent student gives their account password to a friend sitting in the computer lab. That friend logs into the absent student's account on an adjacent lab PC, &lt;strong&gt;starts screen sharing, and plays a pre-recorded coding video in full-screen&lt;/strong&gt; so the screen-capture feed looks like someone is typing code! When a standard web-based Bingo quiz pops up on that second monitor, the classmate tries to click an answer on their friend's behalf.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Tap Student ID &amp;amp; Run" Lecture Evasion Exploit (Why Attendance Must Be Verifiable Anytime)&lt;/strong&gt;: In modern higher education institutions , lecture halls often provide RFID card readers or kiosks at the entrance where students tap their physical student badge or enter an attendance code right at 9:00 AM. Once tapped, the campus system logs them as "Present"—whereupon disengaged students immediately leave the lecture hall to go to the cafeteria, hang out, or leave campus! A single attendance tap at the beginning of a 3-hour session is fundamentally vulnerable. Lecturers need the ability to &lt;strong&gt;take attendance anytime during class&lt;/strong&gt; (mid-lecture, unannounced, or near the end).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Conversely, during a formal &lt;strong&gt;proctored test or examination&lt;/strong&gt;, full-screen monitor capture, webcam/microphone investigation, and tamper-evident evidence retention must be strictly enforced.&lt;/p&gt;

&lt;p&gt;Solving both modes effectively requires addressing three operational challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Formative Teaching vs. High-Stakes Proctored Exams&lt;/strong&gt;: During ordinary lectures and labs, instructors want lightweight, interactive comprehension checks that keep students engaged in learning; during formal tests and exams, invigilators require strict full-monitor capture (&lt;code&gt;displaySurface === 'monitor'&lt;/code&gt;), continuous webcam/audio investigation, and write-once Cloud Storage locks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distinguishing Honest Learning Mistakes from Absence&lt;/strong&gt;: If an instructor sends a pop quiz during a lecture and docks attendance marks when a student selects the wrong answer, students are penalized for struggling with new technical material even though they were sitting attentively at their desks!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminating Idle Polling Bills&lt;/strong&gt;: Running a Cloud Scheduler cron job every 60 seconds to scan pending attendance timers across 50 classes burns tens of thousands of idle Firestore reads per month.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;strong&gt;Part 9&lt;/strong&gt; of our Google Developer Expert series, we detail how we engineered two complementary classroom integrity systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gamified "Classroom Bingo" Active Presence Verification&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoModal.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoQuestionBankModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoQuestionBankModal.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoResultsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoResultsView.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/monitor/LectureQrBingoModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;LectureQrBingoModal.jsx&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;bingoFlows.js&lt;/code&gt;&lt;/a&gt;) featuring &lt;strong&gt;4 Sourced Verification Types (including 2 QR Code Attendance Modes to take attendance anytime)&lt;/strong&gt;, a &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;) Question Bank AI Studio&lt;/strong&gt;, Fisher-Yates positional bias elimination, and an event-driven &lt;strong&gt;Non-Punitive Two-Strike State Machine&lt;/strong&gt; powered by &lt;strong&gt;Google Cloud Tasks (&lt;code&gt;dispatchBingoRetryTask&lt;/code&gt;)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Trust Exam Mode Lockdown &amp;amp; Court-Ready Forensic Dossiers&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/ExamReadinessWizard.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ExamReadinessWizard.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/storage.rules" rel="noopener noreferrer"&gt;&lt;code&gt;storage.rules&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/getStudentVideoPlaybackUrl.js" rel="noopener noreferrer"&gt;&lt;code&gt;getStudentVideoPlaybackUrl.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/processReportJob.js" rel="noopener noreferrer"&gt;&lt;code&gt;processReportJob.js&lt;/code&gt;&lt;/a&gt;), enforcing full-monitor capture (&lt;code&gt;displaySurface === 'monitor'&lt;/code&gt;), write-once Cloud Storage metadata locks (&lt;code&gt;isExam == 'true'&lt;/code&gt;), Student Portal confidentiality shielding during exam windows, and 1-click export of formal &lt;strong&gt;Microsoft Word (&lt;code&gt;.docx&lt;/code&gt;) &amp;amp; RFC 4180 CSV Incident Dossiers&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Um-0StLgjhs" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Diagram 1: Four Classroom Bingo Verification Modes &amp;amp; The Gemini 4 Question Bank Studio
&lt;/h2&gt;

&lt;p&gt;When a lecturer triggers a Classroom Bingo round (either manually with one click, via a &lt;strong&gt;Staggered Jitter Queue&lt;/strong&gt; that challenges random subsets of students every few minutes so neighbours cannot shout out "Option B!", or via &lt;strong&gt;Auto-Bingo&lt;/strong&gt; during live screen broadcasts), &lt;code&gt;resolveBingoQuestion&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/bingoFlows.js&lt;/code&gt;&lt;/a&gt; supports &lt;strong&gt;four distinct verification types&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Type 1 — Predefined Question Bank (&lt;code&gt;question_bank&lt;/code&gt;)&lt;/strong&gt;: ($0.00 / Zero AI Tokens) Instant, zero-cost comprehension checks pulled from an instructor-curated question bank (bulk-imported from Moodle Aiken format or batch-generated via Gemini 4 Argon).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type 2 — Multimodal Teacher Broadcast Screen + Subtitle AI Fusion (&lt;code&gt;teacher_screen&lt;/code&gt;)&lt;/strong&gt;: A single multimodal Gemini call (&lt;code&gt;~$0.00015 USD&lt;/code&gt; total cohort cost!) that fuses the teacher's live broadcast frame (&lt;code&gt;liveFrame&lt;/code&gt;) with the last 5 minutes of spoken captions (&lt;code&gt;liveSubtitles&lt;/code&gt;) and an anti-duplication rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type 3 — Personalized Student Lab Screen Inspection (&lt;code&gt;student_screen&lt;/code&gt;)&lt;/strong&gt;: Targeted Gemini vision evaluation of an individual student's workstation monitor to verify active lab coding work and defeat static window decoys or pre-recorded video playback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type 4 — WebAuthn Biometric QR Attendance (2 QR Modes to Take Attendance Anytime)&lt;/strong&gt;: Designed specifically to defeat the &lt;strong&gt;"Tap Student ID Card &amp;amp; Run"&lt;/strong&gt; lecture evasion exploit. Instead of relying on a one-time badge tap at the entrance door, instructors can trigger attendance verification at any moment during the lesson via two complementary QR modes:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mode 4A: Workstation Mobile Passkey QR (&lt;code&gt;mobile_passkey&lt;/code&gt;)&lt;/strong&gt;: Displayed on the student's lab PC monitor. The student must scan it with their paired smartphone and authenticate via on-device Face ID / Fingerprint. A proxy classmate sitting at an adjacent PC cannot click or verify on their behalf because WebAuthn enforces hardware device binding (&lt;code&gt;1-Phone = 1-Student&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mode 4B: Lecture Hall Dynamic Rotating Projector QR (&lt;code&gt;lecture_passkey_qr&lt;/code&gt;)&lt;/strong&gt;: Displayed on the central classroom/lecture hall projector screen. The QR code dynamically rotates every 10 seconds with HMAC time-signatures. Students physically sitting in the hall point their smartphone camera at the projector screen to sign in. Students who tapped their card at the door and left cannot scan the projector, and remote friends cannot use a forwarded photo because the QR code expires and rotates in 10 seconds!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw10p38zl0znn9sir63cd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw10p38zl0znn9sir63cd.png" alt="Four Classroom Bingo Verification Modes &amp;amp; Gemini 4 Question Bank Studio" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Engineering Highlights of the Bingo Question Engine
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Universal Model Agency in the Question Bank Studio (&lt;code&gt;BingoQuestionBankModal.jsx&lt;/code&gt;)&lt;/strong&gt;: Before class, teachers can open &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoQuestionBankModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoQuestionBankModal.jsx&lt;/code&gt;&lt;/a&gt;, type a lesson topic (or customize the system prompt), and select &lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, or &lt;strong&gt;🪶 Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt; to generate 1 to 15 multiple-choice questions at once—or bulk-import existing quizzes in standard &lt;strong&gt;Moodle Aiken text format&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminating LLM Positional Bias (&lt;code&gt;shuffleOptionsAndCorrectIndex&lt;/code&gt;)&lt;/strong&gt;: When generating 4-option multiple-choice questions, LLMs exhibit a strong statistical bias toward placing the correct answer at index &lt;code&gt;0&lt;/code&gt; (Option A) or index &lt;code&gt;1&lt;/code&gt; (Option B). Every question generated by &lt;code&gt;generateBingoQuestionBank&lt;/code&gt; or &lt;code&gt;resolveBingoQuestion&lt;/code&gt; passes through a deterministic &lt;strong&gt;Fisher-Yates permutation (&lt;code&gt;shuffleOptionsAndCorrectIndex&lt;/code&gt;)&lt;/strong&gt; that randomizes option order and updates &lt;code&gt;correctIndex&lt;/code&gt; accordingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multimodal Teacher Screen + 5-Minute Live Subtitle Fusion (&lt;code&gt;Mode 2&lt;/code&gt;)&lt;/strong&gt;: In &lt;code&gt;teacher_screen&lt;/code&gt; mode, &lt;code&gt;resolveBingoQuestion&lt;/code&gt; makes &lt;strong&gt;a single multimodal Gemini call for the entire 50-student class&lt;/strong&gt; (&lt;code&gt;~$0.00015 USD&lt;/code&gt; total cohort cost!). Even better, it fuses the teacher's live broadcast frame (&lt;code&gt;classes/{classId}/screenBroadcast/liveFrame&lt;/code&gt;) with the last 5 minutes of the teacher's spoken commentary (&lt;code&gt;classes/{classId}/liveSubtitles/current&lt;/code&gt;) and injects an &lt;strong&gt;Anti-Duplication Rule&lt;/strong&gt; containing the last 5 asked questions (&lt;code&gt;isDuplicateQuestion&lt;/code&gt; with $80\%$ lexical Jaccard similarity rejection) so Gemini never repeats the same question while the teacher stays on a single slide!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Leak Client Payload Sanitization&lt;/strong&gt;: A savvy computer science student will immediately open Chrome DevTools (&lt;code&gt;Network&lt;/code&gt; / &lt;code&gt;Firestore&lt;/code&gt; tab) when a quiz pops up. In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;bingoFlows.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;correctIndex&lt;/code&gt; is written &lt;strong&gt;exclusively&lt;/strong&gt; to &lt;code&gt;classes/{classId}/bingoRecords/{bingoId}&lt;/code&gt; (which students are blocked from reading in &lt;code&gt;firestore.rules&lt;/code&gt;). The payload pushed to &lt;code&gt;studentProperties/{studentUid}.activeBingo&lt;/code&gt; contains only &lt;code&gt;question&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt;, and &lt;code&gt;expiresAtMillis&lt;/code&gt;—making client-side answer inspection impossible.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Production Code 1: Genkit Structured Schema Generation &amp;amp; Fisher-Yates Positional De-Biasing (&lt;code&gt;bingoFlows.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Let's inspect &lt;code&gt;generateBingoQuestionBank&lt;/code&gt; and &lt;code&gt;shuffleOptionsAndCorrectIndex&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/bingoFlows.js&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/bingoFlows.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateBingoQuestionBank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customPrompt&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="n"&gt;classId&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="n"&gt;model&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="p"&gt;)&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;targetModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;AI_MODEL&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;safeCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customPrompt&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;\&lt;span class="p"&gt;{&lt;/span&gt;\&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&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="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;\&lt;span class="p"&gt;{&lt;/span&gt;\&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&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="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safeCount&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="sb"&gt;`You are a computer science instructor preparing quick-check multiple choice questions for a lab/lecture.
Topic / Lesson Material: "${topic}".
Generate exactly ${safeCount} multiple-choice questions testing immediate comprehension.
Each question MUST have exactly 4 options and a correctIndex (0, 1, 2, or 3).`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;string&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'The question prompt'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;string&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4 multiple choice options'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;correctIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;number&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="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Index of the correct option (0-3)'&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="p"&gt;)&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="p"&gt;)&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;targetModel&lt;/span&gt;&lt;span class="p"&gt;)&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;shuffledQuestions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correctIndex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shuffleOptionsAndCorrectIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correctIndex&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correctIndex&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="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="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shuffledQuestions&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shuffleOptionsAndCorrectIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correctIndex&lt;/span&gt;&lt;span class="p"&gt;)&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;safeCorrectIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;correctIndex&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'number'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;correctIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;correctIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;correctIndex&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isCorrect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;safeCorrectIndex&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="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Unbiased Fisher-Yates shuffle eliminates LLM Option-A bias&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;)&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;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;correctIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isCorrect&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="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The 3 Prompts Powering Gamified Classroom Bingo (&lt;code&gt;bingoFlows.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Here are the exact prompts executed across the three AI-powered Bingo modes in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/bingoFlows.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/tree/main/admin/prompts/images" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/images/&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Question Bank Studio Prompt (&lt;code&gt;Bingo Question Bank Generator.md&lt;/code&gt; / &lt;code&gt;generateBingoQuestionBank&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Used when a teacher opens &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoQuestionBankModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoQuestionBankModal.jsx&lt;/code&gt;&lt;/a&gt; and generates a reusable bank of 1–15 multiple-choice questions with &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/strong&gt;, or &lt;strong&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Bingo Question Bank Generator
&lt;/span&gt;
You are an instructor preparing quick-check multiple choice questions for a classroom lab or lecture.

Topic / Lesson Material: "{{topic}}".

&lt;span class="gu"&gt;## Rules &amp;amp; Requirements
&lt;/span&gt;1. Generate exactly {{count}} multiple-choice questions testing immediate comprehension.
2. Each question MUST have exactly 4 options and a &lt;span class="sb"&gt;`correctIndex`&lt;/span&gt; (0, 1, 2, or 3).
3. Keep questions concise, practical, and unambiguous.
4. Directly test core principles, code patterns, or command usage from the stated topic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Mode 2 Prompt — 1-to-Many Teacher Broadcast Screen + 5-Minute Live Subtitle Fusion + Anti-Duplication Rule (&lt;code&gt;resolveBingoQuestion&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;When the lecturer triggers a &lt;strong&gt;&lt;code&gt;teacher_screen&lt;/code&gt;&lt;/strong&gt; Bingo check during a live broadcast, &lt;code&gt;resolveBingoQuestion&lt;/code&gt; combines the current &lt;code&gt;classes/{classId}/screenBroadcast/liveFrame&lt;/code&gt; image, the last 5 minutes of &lt;code&gt;classes/{classId}/liveSubtitles/current&lt;/code&gt; spoken commentary, and the last 5 asked questions from &lt;code&gt;classes/{classId}/bingoRecords&lt;/code&gt; into a single multimodal call for the entire cohort:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are an invigilator verifying student attention during a live lecture/explanation.
Analyze the instructor's shared screen image alongside their recent spoken commentary/captions:
Recent Instructor Spoken Commentary: "{{captionContext}}"

Formulate a 4-option multiple-choice question testing whether a student was actively following the instructor's lecture explanation.
The question can reference what the instructor just explained verbally, key details visible on the shared screen, or the connection between them.
ONE option MUST be the true detail from the screen or verbal explanation.
THREE options MUST be plausible but incorrect distractors.
Respond with JSON matching the schema:
{
  "question": "Which file was the instructor editing?",
  "options": ["main.py", "test.js", "app.py", "utils.go"],
  "correctIndex": 0,
  "observedEvidence": "main.py is open in VS Code with Python code displayed"
}

CRITICAL ANTI-DUPLICATION RULE:
The instructor may be holding or explaining the same slide/window. The following questions were ALREADY recently asked to students:
1. "{{recentQuestion1}}"
2. "{{recentQuestion2}}"
3. "{{recentQuestion3}}"

You MUST NOT repeat or rephrase any of the above questions.
You MUST choose a completely DIFFERENT detail, spoken concept, code line, toolbar item, question angle, or UI element that was NOT covered above.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Mode 3 Prompt — Personalized Student Lab Screen Presence Verification (&lt;code&gt;Bingo Student Screen Presence Question Generator.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Used during hands-on computer labs when each student is working at their own pace on their own workstation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Bingo Student Screen Presence Question Generator
&lt;/span&gt;
You are a classroom invigilator checking student presence in a computer lab.
Analyze this student's computer screen screenshot.

&lt;span class="gu"&gt;## Rules &amp;amp; Requirements
&lt;/span&gt;1. Formulate a 4-option multiple-choice question testing immediate awareness of their screen state (e.g., active editor file, command in terminal, running app, or lab guide step).
2. ONE option MUST be the true visible detail on the student's screen.
3. THREE options MUST be plausible distractors.
4. Respond with valid JSON matching the schema:
{
  "question": "What task or window is currently active on your screen?",
  "options": ["Running test suite in terminal", "Reading documentation in browser", "Editing config file", "Debugging breakpoint"],
  "correctIndex": 0,
  "observedEvidence": "Terminal running pytest is in the foreground"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Diagram 2: The Non-Punitive Two-Strike State Machine with Google Cloud Tasks &amp;amp; 3-Color Attendance Heatmap
&lt;/h2&gt;

&lt;p&gt;What happens after the high-priority &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoModal.jsx&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;z-index: 2147483647&lt;/code&gt;, accompanied by an optional Web Audio API chime) appears on the student's screen with a 45-second countdown timer?&lt;/p&gt;

&lt;p&gt;Our &lt;strong&gt;Two-Strike State Machine&lt;/strong&gt; (&lt;code&gt;submitBingoResponse&lt;/code&gt;, &lt;code&gt;enqueueBingoRetryTask&lt;/code&gt;, and &lt;code&gt;handleDispatchBingoRetry&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/bingoFlows.js&lt;/code&gt;&lt;/a&gt;) strictly decouples &lt;strong&gt;academic comprehension&lt;/strong&gt; from &lt;strong&gt;physical desk presence&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Case 1 — Correct Answer (&lt;code&gt;passed&lt;/code&gt;)&lt;/strong&gt;: The student earns base points (&lt;code&gt;100 pts&lt;/code&gt;), a speed bonus (up to &lt;code&gt;+50 pts&lt;/code&gt; scaled by remaining time ratio), and a top-3 podium rank bonus (&lt;code&gt;+50 / +30 / +20 pts&lt;/code&gt;). Any prior strike is cleared (&lt;code&gt;strikeNumber = 0&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case 2 — Wrong Answer (&lt;code&gt;failed_incorrect&lt;/code&gt;)&lt;/strong&gt;: Because the student actively clicked an option within the 45-second window, &lt;strong&gt;physical presence at the workstation is 100% verified!&lt;/strong&gt; The student receives instant visual feedback on the correct answer, &lt;strong&gt;zero strikes are triggered, and zero attendance minutes are deducted.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case 3 — Strike 1 Timeout (&lt;code&gt;missed_timeout&lt;/code&gt; on &lt;code&gt;strikeNumber === 1&lt;/code&gt;)&lt;/strong&gt;: What if the student stepped away to the washroom for 2 minutes? Instead of docking attendance on a single missed check, &lt;code&gt;submitBingoResponse&lt;/code&gt; sets &lt;code&gt;pendingRetryBingo: true&lt;/code&gt; and enqueues a delayed task in &lt;strong&gt;Google Cloud Tasks&lt;/strong&gt; (&lt;code&gt;locations/asia-east2/functions/dispatchBingoRetryTask&lt;/code&gt;) scheduled for &lt;code&gt;retryDelayMinutes&lt;/code&gt; later (configurable 1–15 minutes, default 3 minutes, &lt;strong&gt;$0.00 idle polling cost&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case 4 — Strike 2 Consecutive Timeout (&lt;code&gt;missed_timeout&lt;/code&gt; on &lt;code&gt;strikeNumber &amp;gt;= 2&lt;/code&gt;)&lt;/strong&gt;: When the Cloud Task wakes up after 3 minutes, it performs pre-flight idempotency checks (verifying the class is still active and the teacher hasn't cancelled the session) and dispatches a personal &lt;strong&gt;Strike 2 Follow-Up Challenge&lt;/strong&gt;. If the student responds to Strike 2 (even with a wrong answer!), presence is recovered. Only if the student &lt;strong&gt;misses both Strike 1 and Strike 2 consecutively&lt;/strong&gt; does the backend calculate the elapsed unverified minutes ($\Delta t = t_{\text{Strike 2}} - t_{\text{Strike 1}}$) and write an &lt;code&gt;attendanceAdjustments&lt;/code&gt; deduction record!&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0gd8s6hi4uprkcf4apeg.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0gd8s6hi4uprkcf4apeg.png" alt="Non-Punitive Two-Strike Attendance State Machine with Google Cloud Tasks" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code 2: Event-Driven Two-Strike Cloud Tasks Scheduling &amp;amp; Elapsed AFK Deduction (&lt;code&gt;bingoFlows.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Here is the exact Two-Strike state transition and deterministic Cloud Task deduplication logic inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/bingoFlows.js&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/bingoFlows.js&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Handle Two-Strike Attendance Protocol &amp;amp; Irregularities&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;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'passed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strikeNumber&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="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pendingRetryBingo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'failed_incorrect'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Student was present at desk, answered wrong. Do NOT dock attendance.&lt;/span&gt;
    &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastInattentiveAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FieldValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serverTimestamp&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'missed_timeout'&lt;/span&gt;&lt;span class="p"&gt;)&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;currentStrike&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strikeNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&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;currentStrike&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Strike 1: Schedule follow-up retry in configured delayMinutes (default: 3 mins)&lt;/span&gt;
      &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pendingRetryBingo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retryBingoScheduledAtMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retryDelaySeconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priorMissedBingoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bingoId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retryDelayMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retryDelayMinutes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Enqueue to Google Cloud Tasks for serverless event-driven execution ($0 idle polling)&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;enqueueBingoRetryTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;priorBingoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bingoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;delaySeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;retryDelaySeconds&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="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;currentStrike&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Strike 2 Confirmed Absence! Calculate elapsed unverified window (t_Strike2 - t_Strike1)&lt;/span&gt;
      &lt;span class="n"&gt;updatePayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pendingRetryBingo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;priorBingoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priorBingoId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuedAtMillis&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retryDelaySeconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&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;priorBingoId&lt;/span&gt;&lt;span class="p"&gt;)&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;priorDoc&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`classes/${classId}/bingoRecords/${priorBingoId}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priorDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;priorDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issuedAtMillis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;startMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;priorDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="n"&gt;issuedAtMillis&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;endMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;deductedMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endMillis&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;startMillis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60000&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;// Record attendance adjustment (renders as Red Bitmask Code 2 in AttendanceView.jsx)&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`classes/${classId}/attendanceAdjustments`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startMillis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endMillis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;startMillis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;endMillis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;deductedMinutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;voidedMinutesCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deductedMinutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Missed 2 consecutive Bingo checks (AFK/Decoy)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;strike1BingoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;priorBingoId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'strike_1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;strike2BingoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bingoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FieldValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serverTimestamp&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="p"&gt;}&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/AttendanceView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;AttendanceView.jsx&lt;/code&gt;&lt;/a&gt;, every minute of a lesson is encoded as a &lt;strong&gt;5-State Minute Bitmask (&lt;code&gt;0&lt;/code&gt;–&lt;code&gt;4&lt;/code&gt;)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; / &lt;code&gt;3&lt;/code&gt; / &lt;code&gt;4&lt;/code&gt; (&lt;strong&gt;🟢 Green&lt;/strong&gt;): Verified Active Presence (Screen Shared / Passkey Verified / Teacher Override).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt; (&lt;strong&gt;🔴 Red&lt;/strong&gt;): Voided AFK Window (&lt;code&gt;attendanceAdjustments&lt;/code&gt; from 2 consecutive missed Bingo checks, showing exact tooltip timestamps and a &lt;strong&gt;1-Click Teacher Excuse / Restore (&lt;code&gt;excused: true&lt;/code&gt;)&lt;/strong&gt; button).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; (&lt;strong&gt;⚪ Grey&lt;/strong&gt;): Offline / Disconnected.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Diagram 3: Zero-Trust Exam Mode Lockdown &amp;amp; Court-Ready &lt;code&gt;.docx&lt;/code&gt; Forensic Dossiers
&lt;/h2&gt;

&lt;p&gt;While Classroom Bingo keeps daily lectures and labs lively and non-punitive, formal practical examinations require &lt;strong&gt;Zero-Trust Assessment Security&lt;/strong&gt;. When a class enters a scheduled &lt;code&gt;examPeriods&lt;/code&gt; window (or &lt;code&gt;lessonType === 'exam'&lt;/code&gt;), four defense-in-depth layers activate automatically across the client browser, Cloud Run FFmpeg, Cloud Storage Security Rules, and our Signed URL Broker:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0hn8d2t66eo5gl3iyma.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0hn8d2t66eo5gl3iyma.png" alt="Zero-Trust Exam Mode Lockdown &amp;amp; Court-Ready .docx Forensic Dossiers" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Deep Dive into the Zero-Trust Exam Code: Storage Metadata Locks, Signed URL Shielding &amp;amp; &lt;code&gt;.docx&lt;/code&gt; Dossiers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Production Code 3: Write-Once Exam Metadata Locks in Cloud Storage (&lt;code&gt;storage.rules&lt;/code&gt;) &amp;amp; Signed URL Broker (&lt;code&gt;getStudentVideoPlaybackUrl.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;During a practical exam, students must not be able to delete uploaded screenshots/videos or replay their own exam recording afterward to leak exam paper questions to another class section.&lt;/p&gt;

&lt;p&gt;We enforce this at two independent boundaries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Direct Cloud Storage Rules (&lt;code&gt;storage.rules&lt;/code&gt;)&lt;/strong&gt;: Students have &lt;code&gt;write&lt;/code&gt;-only access to &lt;code&gt;screenshots/&lt;/code&gt; and &lt;code&gt;irregularities/&lt;/code&gt; (under &lt;code&gt;2 MB&lt;/code&gt; / &lt;code&gt;5 MB&lt;/code&gt; size limits), zero &lt;code&gt;delete&lt;/code&gt; permissions, and &lt;code&gt;videos/{classId}/{videoId}&lt;/code&gt; explicitly denies student reads whenever &lt;code&gt;resource.metadata.isExam == 'true'&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: storage.rules&lt;/span&gt;
    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;videos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Allow authenticated teachers to read any video in a class.&lt;/span&gt;
      &lt;span class="c1"&gt;// Students can only read non-exam videos that belong to them.&lt;/span&gt;
      &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="s1"&gt;'studentUid'&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'isExam'&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isExam&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'true'&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="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server-Side Signed URL Broker (&lt;code&gt;getStudentVideoPlaybackUrl.js&lt;/code&gt;)&lt;/strong&gt;: Even if a video was compiled before an instructor retroactively added an &lt;code&gt;examPeriods&lt;/code&gt; range to the class settings, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/getStudentVideoPlaybackUrl.js" rel="noopener noreferrer"&gt;&lt;code&gt;evaluateStudentRecordingsAccess&lt;/code&gt;&lt;/a&gt; checks both the &lt;code&gt;jobData.isExam&lt;/code&gt; flag and &lt;code&gt;isTimestampInExamPeriods(jobData.startTime, examPeriods, timeZone)&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/media_processing/getStudentVideoPlaybackUrl.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;evaluateStudentRecordingsAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;examPeriods&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'always_enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;releaseDate&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="n"&gt;jobData&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;timeZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Asia/Hong_Kong'&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Recordings for exam/test periods or stamped as exam are strictly confidential and not shared&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;isExam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;jobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isExam&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;jobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lessonType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'exam'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;isTimestampInExamPeriods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;examPeriods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;isTimestampInExamPeriods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;examPeriods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeZone&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isExam&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="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Screen recordings for exam or test periods are confidential and not shared with students to protect assessment questions.'&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="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Also enforces 'disabled' and 'delayed_release' policies&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Production Code 4: Court-Ready Microsoft Word (&lt;code&gt;.docx&lt;/code&gt;) &amp;amp; RFC 4180 CSV Forensic Dossier Compiler (&lt;code&gt;processReportJob.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;When an academic integrity board reviews a suspected exam irregularity, raw JSON logs in a database are difficult for non-technical committee members to inspect.&lt;/p&gt;

&lt;p&gt;When an instructor clicks &lt;strong&gt;📄 Export Formal Dossier (.docx)&lt;/strong&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/IncidentDossierExportModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;IncidentDossierExportModal.jsx&lt;/code&gt;&lt;/a&gt; (accessed via &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/IrregularitiesView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;IrregularitiesView.jsx&lt;/code&gt;&lt;/a&gt;), &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/processReportJob.js" rel="noopener noreferrer"&gt;&lt;code&gt;processReportJob.js&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;1 vCPU, 2 GiB RAM, 540s timeout&lt;/code&gt;) queries all time-scoped &lt;code&gt;irregularities&lt;/code&gt;, &lt;code&gt;audio&lt;/code&gt; transcripts (from Edge Whisper + Gemini verification), and &lt;code&gt;screenshots&lt;/code&gt; for the class, classifies each student's risk level (&lt;code&gt;HIGH RISK&lt;/code&gt;, &lt;code&gt;MODERATE RISK&lt;/code&gt;, &lt;code&gt;NORMAL / CLEAN&lt;/code&gt;), and uses the &lt;code&gt;docx&lt;/code&gt; and &lt;code&gt;csv-stringify&lt;/code&gt; libraries to compile a downloadable &lt;strong&gt;Microsoft Word (&lt;code&gt;.docx&lt;/code&gt;) Incident Dossier&lt;/strong&gt; complete with embedded &lt;code&gt;ImageRun&lt;/code&gt; evidence snapshots, 3D head-pose/gaze telemetry tables, and verbatim speech transcripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/media_processing/processReportJob.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;processReportJob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onDocumentCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'reportJobs/{jobId}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FUNCTION_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'2GiB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;540&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... Fetches classData, studentProperties, time-scoped irregularities, audioTranscripts &amp;amp; screenshots&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;studentReports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetStudentIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;student&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;studentMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;uid&lt;/span&gt; &lt;span class="p"&gt;}&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;studentIrregs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;irregularities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;ir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&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;studentAudios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audioTranscripts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&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;studentScreens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;screenshots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&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;highSeverityCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;studentIrregs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'high'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'multiple_faces'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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;totalIncidents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;studentIrregs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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;riskLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;highSeverityCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'HIGH RISK'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;totalIncidents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'MODERATE RISK'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'NORMAL / CLEAN'&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="n"&gt;student&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;irregularities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;studentIrregs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;audios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;studentAudios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;screenshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;studentScreens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalIncidents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highSeverityCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;riskLevel&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Compiles structured Microsoft Word (.docx) with embedded ImageRun evidence &amp;amp; RFC 4180 CSV&lt;/span&gt;
    &lt;span class="c1"&gt;// Uploads to reports/{classId}/{jobId}/ and updates reportJobs/{jobId} with signed download URLs&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The 3 Zero-Trust Exam Mode Invigilation Prompts Feeding the &lt;code&gt;.docx&lt;/code&gt; Dossier
&lt;/h3&gt;

&lt;p&gt;During a proctored exam, whenever the Tier-1 Browser Edge Investigator or teacher-triggered cloud sweeps escalate a visual or acoustic anomaly, these three prompts in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/tree/main/admin/prompts" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/&lt;/code&gt;&lt;/a&gt; evaluate the evidence and invoke Genkit tools (&lt;code&gt;recordIrregularity&lt;/code&gt;, &lt;code&gt;recordAudioIrregularity&lt;/code&gt;, &lt;code&gt;recordAudioAudit&lt;/code&gt;) to populate the &lt;code&gt;.docx&lt;/code&gt; forensic dossier:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Screen Irregularity &amp;amp; Tool-Calling Invigilator (&lt;code&gt;admin/prompts/images/AI invigilator.md&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**You are an AI invigilator. Your task is to analyze a student's screen, take appropriate action using tools, and then report the actions you took in a final text summary.**&lt;/span&gt;

&lt;span class="gu"&gt;## Guidelines
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Minor Distraction:**&lt;/span&gt; Briefly looking away, non-exam apps visible but not in use.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Significant Distraction:**&lt;/span&gt; Actively using another app, phone use, talking to someone, unauthorized communication or AI tools during an exam.

&lt;span class="gu"&gt;## Action &amp;amp; Response Protocol
&lt;/span&gt;1. &lt;span class="gs"&gt;**Analyze the screen.**&lt;/span&gt;
2. &lt;span class="gs"&gt;**If there are no issues, your final answer MUST be the exact text:**&lt;/span&gt; "All students are focused and on track."
3. &lt;span class="gs"&gt;**If there is an issue:**&lt;/span&gt;
   &lt;span class="k"&gt;* &lt;/span&gt;First, call all necessary tools in parallel. You &lt;span class="gs"&gt;**MUST**&lt;/span&gt; call &lt;span class="sb"&gt;`sendMessageToStudent`&lt;/span&gt; and &lt;span class="sb"&gt;`sendMessageToTeacher`&lt;/span&gt;. If the issue is a &lt;span class="gs"&gt;**Significant Distraction**&lt;/span&gt;, you &lt;span class="gs"&gt;**MUST ALSO**&lt;/span&gt; call &lt;span class="sb"&gt;`recordIrregularity`&lt;/span&gt;.
   &lt;span class="k"&gt;* &lt;/span&gt;Then, as your final answer, you &lt;span class="gs"&gt;**MUST**&lt;/span&gt; provide a detailed summary of the issue and the actions you took.

&lt;span class="gu"&gt;## Output Guidelines
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;When referring to a student in any output, you &lt;span class="gs"&gt;**MUST**&lt;/span&gt; use the format 'email (uid)'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Cloud Fallback Face &amp;amp; Gaze Invigilator (&lt;code&gt;admin/prompts/images/Cloud Fallback Face &amp;amp; Gaze Invigilator.md&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Cloud Fallback Face &amp;amp; Gaze Invigilator
&lt;/span&gt;
Analyze this classroom invigilation webcam photo of student {{studentEmail}} (UID: {{studentUid}}, class: {{classId}}).
Determine the student's face presence and gaze orientation.

&lt;span class="gu"&gt;## Detection Rules
&lt;/span&gt;1. Is there a human face present? If no face is visible, status is 'no_face'.
2. Are multiple faces present? If more than 1 person is in frame, status is 'multiple_faces'.
3. Is the student looking forward/centered at their computer screen? If their head or gaze is turned significantly away (left, right, looking at another device, looking away from exam), status is 'looking_away'.
4. If the student is sitting normally facing their screen/work, status is 'normal'.

&lt;span class="gu"&gt;## Output Format
&lt;/span&gt;Respond ONLY with valid JSON in this exact structure:
{
  "faceStatus": "normal" | "looking_away" | "no_face" | "multiple_faces",
  "confidence": 0.95,
  "reason": "Brief explanation"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Live Rolling Audio Collusion &amp;amp; Whisper Invigilator (&lt;code&gt;admin/prompts/audios/AI Voice Invigilator (Live Rolling Audio).md&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# AI Voice Invigilator (Live Rolling Audio)
&lt;/span&gt;
You are an expert AI Classroom Invigilator analyzing a live student audio stream during an examination.

&lt;span class="gu"&gt;## Context
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Class ID: {{classId}}
&lt;span class="k"&gt;- &lt;/span&gt;Student UID: {{studentUid}}
&lt;span class="k"&gt;- &lt;/span&gt;Student Email: {{studentEmail}}
&lt;span class="k"&gt;- &lt;/span&gt;Audio Duration: Rolling 20–45s window

&lt;span class="gu"&gt;## Audio Transcript
&lt;/span&gt;"""
{{transcript}}
"""

&lt;span class="gu"&gt;## Instructions
&lt;/span&gt;1. Analyze the transcript for unauthorized speech, exam collusion, question recitation, answer trading, or whispering to external parties/devices.
2. If multi-speaker discussion, unauthorized talking, or answer recitation is detected:
   &lt;span class="k"&gt;- &lt;/span&gt;Call the &lt;span class="sb"&gt;`recordAudioIrregularity`&lt;/span&gt; tool with &lt;span class="sb"&gt;`title`&lt;/span&gt;, &lt;span class="sb"&gt;`message`&lt;/span&gt;, &lt;span class="sb"&gt;`riskLevel`&lt;/span&gt; ('low' | 'medium' | 'high'), &lt;span class="sb"&gt;`speakerCount`&lt;/span&gt;, and &lt;span class="sb"&gt;`transcriptSnippet`&lt;/span&gt;.
3. Call the &lt;span class="sb"&gt;`recordAudioAudit`&lt;/span&gt; tool with &lt;span class="sb"&gt;`verdict`&lt;/span&gt; ('clean_exam' | 'suspicious_collaboration' | 'whisper_detected' | 'background_noise' | 'inconclusive'), &lt;span class="sb"&gt;`speakerCount`&lt;/span&gt;, &lt;span class="sb"&gt;`summary`&lt;/span&gt;, and &lt;span class="sb"&gt;`transcript`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next in Part 10?
&lt;/h2&gt;

&lt;p&gt;We have now explored the entire pedagogical and multimodal AI stack—from Browser Edge AI (&lt;code&gt;Gemma 4 E2B&lt;/code&gt;, &lt;code&gt;MediaPipe&lt;/code&gt;, &lt;code&gt;LiteRT Whisper&lt;/code&gt;) to Cloud Broadcasts, Gemini 4 Argon Video Grading, Capstone Mobile Supervision, and Zero-Trust Exams.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 10 (our Series Finale)&lt;/strong&gt;, we look under the hood at &lt;strong&gt;Day-2 Institutional Operations&lt;/strong&gt;: how we govern AI and storage budgets using &lt;strong&gt;Live Google Cloud Billing SKU Sync (&lt;code&gt;syncGeminiPricing&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;9-Category Event-Driven Storage Quota Enforcement (&lt;code&gt;storageQuota.js&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;4-Stage Zero-Orphan Cascading Class Purge (&lt;code&gt;onClassDocDeleted&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;1-Command Terraform Infrastructure-as-Code (&lt;code&gt;./setup-new-project.sh&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>gamification</category>
      <category>googlecloud</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Google AI Classroom Part 8: AI Project Supervision — Streamlined Mobile 720p Demo, 2-Stage Diarization &amp; Fake-Log Auditing with Gemini 4</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Wed, 11 Nov 2026 07:41:52 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-8-ai-project-supervision-streamlined-mobile-720p-demo-2-stage-3h98</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-8-ai-project-supervision-streamlined-mobile-720p-demo-2-stage-3h98</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Capstone Supervision Bottleneck: ChatGPT Logbooks, Free-Riders &amp;amp; Multi-Device Consultation Friction
&lt;/h2&gt;

&lt;p&gt;Supervising 15 final-year engineering capstone project groups (60+ students) across a 16-week semester is one of the most rewarding yet administratively exhausting responsibilities in higher education. In our Software Engineering and Cloud Computing Higher Diploma programmes, three recurring pain points threatened both pedagogical quality and assessment fairness:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The "ChatGPT Logbook" Epidemic (&lt;code&gt;suspected_ai_fake&lt;/code&gt;) &amp;amp; 1-Word Filler (&lt;code&gt;vague_useless&lt;/code&gt;)&lt;/strong&gt;: Students are required to submit weekly progress logbooks via Google Forms before their consultation meeting. Some students type one-word filler (&lt;code&gt;"done"&lt;/code&gt;, &lt;code&gt;"finish"&lt;/code&gt;, &lt;code&gt;"thinking"&lt;/code&gt;), while others paste LLM-generated corporate buzzword salads (&lt;em&gt;"Implemented real-time microservice telemetry, RBAC authentication, and mock AI chatbot"&lt;/em&gt;) right after three consecutive weeks of reporting zero work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Capstone Supervision Is Never "Just Voice" — And Why We Replaced Student PC Screen Sharing with a Single-Device Mobile Studio&lt;/strong&gt;: A real engineering capstone consultation is &lt;strong&gt;inherently multimodal, not just a voice conversation&lt;/strong&gt;. Around a physical lab table, students show physical IoT microcontroller breadboards, hand-drawn ER diagrams on paper/whiteboards, mobile app prototypes, and live code/terminal outputs on their personal laptops. We no longer ask students to join a web session and share their PC screen—waiting for 4 students to authenticate and switch desktop screen shares wasted 3 to 4 minutes of a 15-minute consultation and could never capture hardware wiring or whiteboard architecture diagrams. Instead, the supervisor captures &lt;strong&gt;all three modalities from a single smartphone&lt;/strong&gt;: &lt;strong&gt;🎙️ Continuous Voice Consultation&lt;/strong&gt;, &lt;strong&gt;📸 Zero-Pause &lt;code&gt;0.1s&lt;/code&gt; Instant Photo Snaps (&lt;code&gt;1280px&lt;/code&gt; JPEG)&lt;/strong&gt;, and &lt;strong&gt;📹 Auto-Sliced &lt;code&gt;720p&lt;/code&gt; (&lt;code&gt;1280×720&lt;/code&gt;) Mobile Camera Demo Videos&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multilingual Speaker Attribution in Noisy Labs&lt;/strong&gt;: When 4 project teammates and a supervisor debate system architecture around a single table—code-switching rapidly between conversational Cantonese, Mandarin, and English technical terms (&lt;code&gt;React&lt;/code&gt;, &lt;code&gt;Firestore&lt;/code&gt;, &lt;code&gt;Docker&lt;/code&gt;, &lt;code&gt;Cloud Run&lt;/code&gt;, &lt;code&gt;PR&lt;/code&gt;)—a single-pass LLM prompt cannot reliably separate &lt;em&gt;who&lt;/em&gt; actually answered the technical challenge questions and demonstrated working prototypes from &lt;em&gt;who&lt;/em&gt; sat silently and free-rode on their teammates' work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;strong&gt;Part 8&lt;/strong&gt; of our Google Developer Expert series, we dive deep into the architecture of the &lt;strong&gt;Capstone Project Supervision &amp;amp; Weekly Log AI Pipeline&lt;/strong&gt; (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/meetings/ProjectMeetingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProjectMeetingsView.jsx&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt;), showing how we engineered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Self-Healing On-Demand Google Sheets Logbook Sync&lt;/strong&gt; with &lt;strong&gt;Hamming Distance = 1 Student ID Typo Healing&lt;/strong&gt; and holiday-aware academic week calculation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Pre-Meeting Fake-Log AI Auditor (&lt;code&gt;prepareProjectMeetingLogQuestions&lt;/code&gt;)&lt;/strong&gt; that cross-examines each student's current weekly log against their prior weeks' history and generates pointed live code-walkthrough challenge questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Streamlined Single-Device Teacher Mobile Supervision Studio&lt;/strong&gt; on the supervisor's smartphone: combining &lt;strong&gt;🎙️ Continuous Voice Recording&lt;/strong&gt; (+ 1-tap &lt;code&gt;🗣️ Speaking Now&lt;/code&gt; student voice anchors), &lt;strong&gt;📸 Zero-Pause &lt;code&gt;0.1s&lt;/code&gt; Photo Snaps (&lt;code&gt;1280px&lt;/code&gt; JPEG)&lt;/strong&gt;, and &lt;strong&gt;📹 Auto-Sliced &lt;code&gt;720p&lt;/code&gt; (&lt;code&gt;1280×720&lt;/code&gt;) Mobile Camera Demo Videos&lt;/strong&gt; that completely eliminate student PC screen-capture friction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Two-Stage Multimodal Cross-Examination Pipeline (&lt;code&gt;analyzeProjectMeeting&lt;/code&gt;)&lt;/strong&gt;: pairing &lt;strong&gt;Stage 1 Acoustic Speaker Diarization (&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; with automatic &lt;code&gt;gemini-3.8-flash&lt;/code&gt; low-volume &amp;amp; long-clip truncation recovery)&lt;/strong&gt; with &lt;strong&gt;Stage 2 Multimodal Verification (&lt;code&gt;gemini-4-argon&lt;/code&gt; / &lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; and &lt;strong&gt;1-Click Secret Manager SMTP HTML Report Dispatch (&lt;code&gt;batchEmailProjectMeetingReports&lt;/code&gt;)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/jtdm9qwhb40" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Diagram 1: Self-Healing Google Sheets Sync &amp;amp; Pre-Meeting Fake-Log AI Auditor
&lt;/h2&gt;

&lt;p&gt;Before the weekly consultation begins, the supervisor clicks &lt;strong&gt;🔄 Sync Weekly Logs&lt;/strong&gt; inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/meetings/ProjectMeetingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProjectMeetingsView.jsx&lt;/code&gt;&lt;/a&gt;. Rather than running wasteful background cron jobs every 5 minutes, the platform performs an on-demand incremental delta sync against the class's linked Google Form spreadsheet (&lt;code&gt;Form Responses 2&lt;/code&gt;), fetching only newly appended rows (&lt;code&gt;startRow = lastSyncedRowCount + 1&lt;/code&gt;).&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3tnb27bc63t1lm091em.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3tnb27bc63t1lm091em.png" alt="Self-Healing Google Sheets Sync &amp;amp; Pre-Meeting Fake-Log AI Auditor" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Hamming Distance = 1 Student ID Healing Saved Hours of Manual Fixes
&lt;/h3&gt;

&lt;p&gt;On mobile Google Forms, students frequently mistype a single digit of their 9-digit Student ID (e.g., typing &lt;code&gt;250105349&lt;/code&gt; instead of &lt;code&gt;250105348&lt;/code&gt;) or submit from a personal &lt;code&gt;@gmail.com&lt;/code&gt; address. A strict string equality check drops those logs as orphaned records.&lt;/p&gt;

&lt;p&gt;Our ingestion resolver compares any unmatched 9-digit numeric ID against the enrolled class roster (&lt;code&gt;projectGroups&lt;/code&gt;). If &lt;strong&gt;exactly one&lt;/strong&gt; enrolled student in the class has a 9-digit ID of identical length with &lt;strong&gt;Hamming distance $= 1$&lt;/strong&gt; (differing by exactly 1 character position), or if the normalized student name uniquely matches an enrolled group member, the sync engine automatically heals the typo, links the log to the student's group and academic week, and flags the healed record in the UI!&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code 1: Pre-Meeting Fake-Log Detection &amp;amp; Live Code-Walkthrough Challenge Generator (&lt;code&gt;handlePrepareProjectLogQuestions&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Once the weekly logs are synced into &lt;code&gt;classes/{classId}/projectMeetings/{meetingId}&lt;/code&gt;, the supervisor clicks &lt;strong&gt;🔍 Audit Logs &amp;amp; Prepare Questions&lt;/strong&gt; (or runs &lt;strong&gt;⚡ Smart Batch AI Mode 1&lt;/strong&gt; across all groups for Week $W$). Let's look at &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;handlePrepareProjectLogQuestions&lt;/code&gt;&lt;/a&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt;, which feeds each student's Week $W$ log &lt;em&gt;plus&lt;/em&gt; their multi-week historical log trajectory into &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/projectMeetingFlows.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePrepareProjectLogQuestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;meetingId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;groupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;projectTopic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;weekNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;weeklyLogs&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;preferredModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;auth&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;reasoningModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_LECTURE_AI_MODEL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'gemini-3.8-flash'&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;logsFormatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weeklyLogs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;priorSummary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priorHistory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priorHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;priorHistory&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="sb"&gt;`  * Week ${p.weekNumber}: Doing=${p.isDoingSomething ? 'Yes' : 'No'} | WorkDone="${p.workDone || p.inactiveReason || ''}" | Plan="${p.nextPlan || ''}"`&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'  * No prior weekly logs submitted.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`### Student ${idx + 1}: ${l.studentName || l.name || 'Student'} (ID: ${l.studentId || 'N/A'}, Email: ${l.email || 'N/A'})
- Week ${weekNumber} Submitted: ${l.submitted === false ? 'NO LOG SUBMITTED THIS WEEK' : 'Yes'}
- "I am doing something this week": ${l.isDoingSomething ? 'Yes' : 'No'}
- "Work done &amp;amp; findings since last recording": "${l.workDone || ''}"
- "Part of last plan not yet finished": "${l.unfinished || ''}"
- "Plan of work before next recording": "${l.nextPlan || ''}"
- Prior Weeks Log History (for Continuity &amp;amp; Sudden-Jump Fake Detection):
${priorSummary}`&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\n\n'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// ... (Constructs prompt instructing Gemini to classify each log into&lt;/span&gt;
  &lt;span class="c1"&gt;// "genuine" | "vague_useless" | "suspected_ai_fake" | "inactive"&lt;/span&gt;
  &lt;span class="c1"&gt;// and generate 2-3 live code-walkthrough challengeQuestions)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;prompt&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&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="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;responseMimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reasoningModel&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Persists preMeetingLogAudit[] onto classes/{classId}/projectMeetings/{meetingId}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the exact &lt;strong&gt;Pre-Meeting Fake-Log Audit &amp;amp; Challenge Question Prompt&lt;/strong&gt; executed inside &lt;code&gt;handlePrepareProjectLogQuestions&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Pre-Meeting Weekly Log Audit &amp;amp; Fake-Log Challenge Question Task
&lt;/span&gt;You are helping the project supervisor prepare for &lt;span class="gs"&gt;**{{groupName}} — Week {{weekNumber}}**&lt;/span&gt; supervision meeting (Project Topic: {{projectTopic}}).

In this course, &lt;span class="gs"&gt;**writing a high-quality weekly log that accurately reflects real engineering work is an explicit grading criterion**&lt;/span&gt;.
However, two common student problems occur in the weekly log sheet:
1. &lt;span class="gs"&gt;**Useless / Vague Logs (`vague_useless`)**&lt;/span&gt;: 1-to-4 word filler like "done", "finish", "thinking", "do website", "proposal", or copy-pasting previous weeks' logs without concrete technical findings or deliverables.
2. &lt;span class="gs"&gt;**Fake / AI-Generated Logs (`suspected_ai_fake`)**&lt;/span&gt;: Logs that appear written by ChatGPT/AI to sound impressive (buzzword-heavy corporate prose like "Implemented real-time dashboards, mock AI chatbot, and role-based access control" or sudden massive multi-module claims right after weeks of writing "done" or "No"), without specific file names, bugs encountered, or continuity with last week's plan.

&lt;span class="gu"&gt;## Students' Week {{weekNumber}} Logs &amp;amp; Prior History
&lt;/span&gt;{{logsFormatted}}

&lt;span class="gu"&gt;## Instructions
&lt;/span&gt;For EVERY student listed above:
1. Classify &lt;span class="sb"&gt;`logQualityStatus`&lt;/span&gt; as one of:
   &lt;span class="k"&gt;- &lt;/span&gt;&lt;span class="sb"&gt;`"genuine"`&lt;/span&gt;: Specific, honest, technically grounded log that connects logically to prior plans.
   &lt;span class="k"&gt;- &lt;/span&gt;&lt;span class="sb"&gt;`"vague_useless"`&lt;/span&gt;: Too brief, generic ("done", "thinking"), or copy-pasted to evaluate real work.
   &lt;span class="k"&gt;- &lt;/span&gt;&lt;span class="sb"&gt;`"suspected_ai_fake"`&lt;/span&gt;: Overly polished AI buzzword dump, suspicious sudden leap from prior "done"/"No" logs, or claims too many unverified subsystems at once.
   &lt;span class="k"&gt;- &lt;/span&gt;&lt;span class="sb"&gt;`"inactive"`&lt;/span&gt;: Answered "No" to doing work this week or did not submit a log.
2. Assign &lt;span class="sb"&gt;`logQualityScore`&lt;/span&gt; from &lt;span class="sb"&gt;`0`&lt;/span&gt; to &lt;span class="sb"&gt;`10`&lt;/span&gt; (e.g., 8-10 for genuine detailed logs; 3-5 for suspected AI/fake logs pending verification; 1-4 for vague/useless logs; 0-2 for inactive/missing).
3. Write a concise &lt;span class="sb"&gt;`logAuditSummary`&lt;/span&gt; (1-2 sentences explaining why the log received this score and what looks genuine, vague, or AI-faked).
4. Generate 2 to 3 sharp, specific &lt;span class="sb"&gt;`challengeQuestions`&lt;/span&gt; for the supervisor to ask the student live during the meeting:
   &lt;span class="k"&gt;- &lt;/span&gt;If &lt;span class="sb"&gt;`suspected_ai_fake`&lt;/span&gt;: Ask live code-walkthrough &amp;amp; debugging challenge questions that a student who faked the log with AI cannot answer without demonstrating real code or hardware live (e.g., "Open the exact Flask route / database table on your laptop right now for our 720p demo recording and show how X handles Y", "What specific error did you hit when integrating X and how did you fix it?").
   &lt;span class="k"&gt;- &lt;/span&gt;If &lt;span class="sb"&gt;`vague_useless`&lt;/span&gt;: Demand an immediate live demonstration (captured via 720p mobile demo video or photo snap) of concrete artifacts and measurable next-week deliverables.
   &lt;span class="k"&gt;- &lt;/span&gt;If &lt;span class="sb"&gt;`genuine`&lt;/span&gt;: Ask constructive technical design &amp;amp; edge-case verification questions about their reported findings.

Respond strictly with valid JSON matching this structure:
{
  "preMeetingLogAudit": [
    {
      "studentId": "250105348",
      "email": "250105348@stu.vtc.edu.hk",
      "name": "Student Name",
      "logQualityStatus": "suspected_ai_fake",
      "logQualityScore": 4,
      "logAuditSummary": "Sudden leap from 'done' in Weeks 6-7 to claiming full Flask backend, real-time dashboards, mock AI chatbot, and RBAC in one week.",
      "challengeQuestions": [
        "Open your Flask route file for a live demonstration: walk us through how the Doctor/Patient role-based access control check is implemented in code.",
        "Which database tables back your real-time dashboard and history page, and can you trigger a live state update right now?",
        "What was the hardest bug you fixed while connecting the frontend to Flask this week?"
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a student submits a suspected ChatGPT log claiming they built an entire Flask RBAC backend in one week after writing &lt;code&gt;"done"&lt;/code&gt; in Weeks 6 and 7, Gemini flags the badge as &lt;strong&gt;&lt;code&gt;🟠 Suspected AI / Fake (4/10)&lt;/code&gt;&lt;/strong&gt; right on the supervisor's smartphone card and prepares 3 targeted verification questions for live in-person demonstration:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;"Open your Flask route file for a live demonstration: walk us through how the Doctor/Patient role-based access control decorator is implemented in code."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Which database tables back your real-time dashboard, and can you trigger a live state update right now while we capture a 720p demo clip?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"What was the hardest bug you hit while connecting the React frontend to Flask this week?"&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Diagram 2: Streamlined Single-Device Teacher Mobile Supervision Studio (Voice + &lt;code&gt;0.1s&lt;/code&gt; Photo Snap + Auto-Sliced &lt;code&gt;720p&lt;/code&gt; Demo Video)
&lt;/h2&gt;

&lt;p&gt;When the group sits down for their weekly consultation, the supervisor opens the meeting card on their smartphone or tablet.&lt;/p&gt;

&lt;p&gt;To make live supervision effortless with &lt;strong&gt;one thumb&lt;/strong&gt;, we eliminated cross-device student PC screen-capture signaling completely and consolidated all multimodal capture into a &lt;strong&gt;Streamlined Single-Device Teacher Mobile Supervision Studio&lt;/strong&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/meetings/ProjectMeetingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProjectMeetingsView.jsx&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;🎙️ Continuous Voice Consultation (&lt;code&gt;audio/webm&lt;/code&gt; / &lt;code&gt;audio/mp4&lt;/code&gt;)&lt;/strong&gt;: Protected by the browser Screen Wake Lock API (&lt;code&gt;navigator.wakeLock.request('screen')&lt;/code&gt;) so the mobile browser never sleeps mid-consultation. As each student speaks or answers a challenge question, the supervisor taps their &lt;strong&gt;&lt;code&gt;🗣️ Speaking Now&lt;/code&gt;&lt;/strong&gt; pill, stamping &lt;code&gt;{ studentUid, name, email, timeSeconds, timestamp, clipIndex }&lt;/code&gt; into &lt;code&gt;voiceAnchors[]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📸 Zero-Pause Instant Photo Snap (&lt;code&gt;0.1s&lt;/code&gt;, &lt;code&gt;1280px&lt;/code&gt; JPEG @ &lt;code&gt;0.85&lt;/code&gt; quality)&lt;/strong&gt;: When a student sketches an ER diagram on paper, shows an IoT microcontroller circuit, or points to a stack trace on their laptop, tapping &lt;strong&gt;&lt;code&gt;📸 Snap Photo&lt;/code&gt;&lt;/strong&gt; captures a crisp &lt;code&gt;1280px&lt;/code&gt; image and uploads it to &lt;code&gt;projectMeetings/{classId}/{groupId}/{meetingId}/&lt;/code&gt; &lt;strong&gt;in &lt;code&gt;0.1&lt;/code&gt; seconds without pausing or interrupting the ongoing voice recording!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📹 Auto-Sliced &lt;code&gt;720p&lt;/code&gt; Mobile Camera Demo Video (&lt;code&gt;1280×720&lt;/code&gt; @ &lt;code&gt;~1.5 Mbps&lt;/code&gt;)&lt;/strong&gt;: When a student demonstrates their working software or hardware prototype on their laptop or phone, the supervisor simply taps &lt;strong&gt;&lt;code&gt;📹 Record Demo Video (720p)&lt;/code&gt;&lt;/strong&gt; and points their smartphone camera at the student's demo:

&lt;ul&gt;
&lt;li&gt;The studio &lt;strong&gt;automatically finalizes and uploads &lt;code&gt;Voice Part N&lt;/code&gt;&lt;/strong&gt; in the background.&lt;/li&gt;
&lt;li&gt;It activates the mobile rear/front camera at &lt;strong&gt;&lt;code&gt;720p&lt;/code&gt; (&lt;code&gt;1280×720&lt;/code&gt;, &lt;code&gt;~1.5 Mbps&lt;/code&gt; bitrate)&lt;/strong&gt;—sharp enough for Gemini 4 Argon to read laptop UI states and terminal output, yet compact enough (&lt;code&gt;~11 MB/min&lt;/code&gt;) to upload in seconds over campus Wi-Fi.&lt;/li&gt;
&lt;li&gt;The instant the supervisor taps &lt;strong&gt;&lt;code&gt;⏹️ Stop Demo&lt;/code&gt;&lt;/strong&gt;, the studio uploads the &lt;code&gt;720p&lt;/code&gt; demo clip (&lt;code&gt;type: 'mobile_camera'&lt;/code&gt;) and &lt;strong&gt;automatically resumes &lt;code&gt;Voice Part N+1&lt;/code&gt;&lt;/strong&gt; so not a single second of verbal feedback is lost!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui8u296wrq28feq2izjg.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui8u296wrq28feq2izjg.png" alt="Streamlined Single-Device Teacher Mobile Supervision Studio State Machine" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomic Stale Analysis Invalidation When Moving Clips Across Groups or Weeks
&lt;/h3&gt;

&lt;p&gt;During back-to-back consultations with 10 project groups, a supervisor occasionally records Group 3's voice clip or demo video while Group 2's card is still open. In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/meetings/ProjectMeetingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProjectMeetingsView.jsx&lt;/code&gt;&lt;/a&gt;, we built a &lt;strong&gt;1-Click Clip Reassignment Modal (&lt;code&gt;➡️ Move Clip to Another Meeting / Week&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whenever a voice clip, 720p demo video, or photo is moved from Meeting $A$ to Meeting $B$ (or deleted after an AI report was already generated), a single atomic Firestore &lt;code&gt;writeBatch()&lt;/code&gt; moves the media metadata, shifts any associated &lt;code&gt;voiceAnchors&lt;/code&gt;, and &lt;strong&gt;automatically purges stale AI report fields&lt;/strong&gt; (&lt;code&gt;executiveSummary&lt;/code&gt;, &lt;code&gt;keyInsights&lt;/code&gt;, &lt;code&gt;attendanceRecord&lt;/code&gt;, &lt;code&gt;studentContributions&lt;/code&gt;, &lt;code&gt;actionItems&lt;/code&gt;, &lt;code&gt;rawDiarizedTranscript&lt;/code&gt;) on both meetings using &lt;code&gt;deleteField()&lt;/code&gt; while setting &lt;code&gt;needsReanalysis: true&lt;/code&gt;. This guarantees a supervisor can never accidentally email a stale AI report after modifying a meeting's evidence!&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Diagram 3: Two-Stage Multimodal Diarization &amp;amp; Gemini 4 Argon Cross-Examination (&lt;code&gt;analyzeProjectMeeting&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Once the consultation concludes, the supervisor selects &lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; directly in the meeting toolbar (or triggers &lt;strong&gt;Smart Batch AI Mode 1 Queuing&lt;/strong&gt;, which sequentially processes all ready meetings in Week $W$ and skips already-analyzed meetings unless &lt;code&gt;needsReanalysis === true&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Why split &lt;code&gt;analyzeProjectMeeting&lt;/code&gt; into &lt;strong&gt;two distinct AI stages&lt;/strong&gt; instead of feeding raw audio, video, photos, and logs into a single prompt?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stage 1 (&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; with &lt;code&gt;gemini-3.8-flash&lt;/code&gt; Resilience Fallbacks)&lt;/strong&gt; is dedicated 100% to &lt;strong&gt;verbatim multilingual acoustic &amp;amp; demo-video dialogue diarization&lt;/strong&gt; across every voice clip (&lt;code&gt;audioClips[]&lt;/code&gt;) and &lt;code&gt;720p&lt;/code&gt; mobile camera demo video (&lt;code&gt;screenClips[]&lt;/code&gt;), shifting text timestamps cumulatively (&lt;code&gt;offsetTranscriptTimestamps&lt;/code&gt;) across &lt;code&gt;Voice Part 1&lt;/code&gt;, &lt;code&gt;Demo 1&lt;/code&gt;, and &lt;code&gt;Voice Part 2&lt;/code&gt; to assemble a single chronological &lt;strong&gt;text transcript&lt;/strong&gt; without ever merging or re-encoding the underlying media files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage 2 (&lt;code&gt;gemini-4-argon&lt;/code&gt; or &lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; receives &lt;strong&gt;five multimodal evidence streams simultaneously&lt;/strong&gt; in a single multi-part Gemini API call (&lt;code&gt;prompt: [{ text: reasoningPrompt }, ...reasoningMediaParts]&lt;/code&gt;):

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;Stage 1 Combined Diarized Transcript (Text)&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;The supervisor's &lt;strong&gt;1-Tap &lt;code&gt;voiceAnchors[]&lt;/code&gt; Ground Truth Speaker Stamps (Text)&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;individual &lt;code&gt;720p&lt;/code&gt; Mobile Camera Demo Video segments (&lt;code&gt;screenClips[]&lt;/code&gt;)&lt;/strong&gt; passed as separate &lt;code&gt;gs://&lt;/code&gt; &lt;code&gt;video/mp4&lt;/code&gt; / &lt;code&gt;video/webm&lt;/code&gt; &lt;code&gt;media&lt;/code&gt; parts in &lt;code&gt;reasoningMediaParts&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;individual Snapped Meeting Photos (&lt;code&gt;photos[]&lt;/code&gt;)&lt;/strong&gt; of hardware breadboards, whiteboard ERDs, and laptop code passed as separate &lt;code&gt;gs://&lt;/code&gt; &lt;code&gt;image/jpeg&lt;/code&gt; &lt;code&gt;media&lt;/code&gt; parts in &lt;code&gt;reasoningMediaParts&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Students' Submitted Weekly Log Sheet Entries (&lt;code&gt;weeklyLogs[]&lt;/code&gt;)&lt;/strong&gt; + &lt;strong&gt;Prior Week's Assigned Action Items (&lt;code&gt;priorWeekContext&lt;/code&gt;)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architectural Clarification — Zero FFmpeg Video Stitching:&lt;/strong&gt; The platform &lt;strong&gt;never&lt;/strong&gt; merges or re-encodes the voice clips (&lt;code&gt;audioClips[]&lt;/code&gt;), still photos (&lt;code&gt;photos[]&lt;/code&gt;), and &lt;code&gt;720p&lt;/code&gt; video segments (&lt;code&gt;screenClips[]&lt;/code&gt;) into a single combined video/audio file before analysis. Every voice part, &lt;code&gt;0.1s&lt;/code&gt; JPEG photo, and &lt;code&gt;720p&lt;/code&gt; demo clip remains an independent object in Cloud Storage (&lt;code&gt;gs://...&lt;/code&gt;). Stage 1 transcribes each voice/video segment individually and stitches only their &lt;strong&gt;text transcripts&lt;/strong&gt; using cumulative &lt;code&gt;[MM:SS]&lt;/code&gt; timestamp offsets (&lt;code&gt;offsetTranscriptTimestamps&lt;/code&gt;), while Stage 2 passes the individual photo and &lt;code&gt;720p&lt;/code&gt; video &lt;code&gt;gs://&lt;/code&gt; URIs as a native multi-part &lt;code&gt;media&lt;/code&gt; array directly to Gemini!&lt;/p&gt;
&lt;/blockquote&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcc4x0jb62j9xcntkadl4.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcc4x0jb62j9xcntkadl4.png" alt="Two-Stage Multimodal Diarization &amp;amp; Gemini 4 Argon Cross-Examination (analyzeProjectMeeting)" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Deep Dive into the Production Code: Long-Clip Truncation Recovery &amp;amp; Deterministic Roster Reconciliation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Production Code 2: Stage 1 Acoustic Diarization with Automatic Low-Volume &amp;amp; Long-Clip Truncation Recovery (&lt;code&gt;projectMeetingFlows.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;During real classroom testing with 25-minute capstone consultations, we discovered two edge cases with dedicated speech-to-text preview models (&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Low-Volume / Quiet Clips&lt;/strong&gt;: If students sit far from the supervisor's phone in a quiet room, dedicated STT filters occasionally classify soft speech as background noise and return an empty string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mid-Meeting Silence Pause Truncation (&lt;code&gt;isLongClipTranscriptTruncated&lt;/code&gt;)&lt;/strong&gt;: If a group pauses speaking for 20 seconds at minute &lt;code&gt;08:00&lt;/code&gt; while compiling code on a laptop during a 20-minute recording (&lt;code&gt;&amp;gt;= 360s&lt;/code&gt;), &lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; can prematurely emit an end-of-sequence token, truncating the second half of the consultation!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt;, we built two automatic self-healing guards (&lt;code&gt;isLongClipTranscriptTruncated&lt;/code&gt; and &lt;code&gt;offsetTranscriptTimestamps&lt;/code&gt;) that detect both anomalies and seamlessly recover the full verbatim Cantonese/English diarization using multimodal &lt;code&gt;gemini-3.8-flash&lt;/code&gt; (&lt;code&gt;thinkingBudget: 0&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/projectMeetingFlows.js&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Shifts [MM:SS] timestamps in a diarized transcript by offsetSeconds for multi-clip meetings.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;offsetTranscriptTimestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcriptText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offsetSeconds&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="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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;transcriptText&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;offsetSeconds&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;offsetSeconds&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;transcriptText&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&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="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcriptText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;\&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&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="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&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="p"&gt;)&lt;/span&gt;\&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;secs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseTimestampToSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offsetSeconds&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="sb"&gt;`[${formatSecondsToMmSs(secs)}]`&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="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Detects whether `gemini-3.5-transcribe-preview` prematurely stopped (e.g. due to a 15s silence pause
 * or unary audio token ceiling) on a long meeting clip (&amp;gt;= 360s / 6+ minutes).
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isLongClipTranscriptTruncated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcriptText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;durationSeconds&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;dur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationSeconds&lt;/span&gt;&lt;span class="p"&gt;)&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dur&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcriptText&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;matches&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;\&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;\&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&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="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&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="p"&gt;)&lt;/span&gt;\&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;g&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;maxTsSecs&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;for&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;m&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&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;secs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseTimestampToSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxTsSecs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;maxTsSecs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secs&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;maxTsSecs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;maxTsSecs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;dur&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.45&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;When &lt;code&gt;isLongClipTranscriptTruncated(clipText, clipDurationSecs)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, Stage 1 automatically re-invokes &lt;code&gt;generateWithResilience&lt;/code&gt; with an explicit full-duration boundary instruction (&lt;code&gt;"IMPORTANT: This meeting recording is 22:15 long. Do NOT stop early on pauses; transcribe the entire recording from [00:00] through [22:15]."&lt;/code&gt;), ensuring 100% of the consultation dialogue reaches Stage 2!&lt;/p&gt;

&lt;h3&gt;
  
  
  The 2-Stage Multimodal Prompts Powering &lt;code&gt;analyzeProjectMeeting&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Here are the exact prompts executed across Stage 1 and Stage 2 of &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt; (and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/admin/prompts/audios/Project%20Group%20Meeting%20Analyzer.md" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/audios/Project Group Meeting Analyzer.md&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Stage 1 Prompt — Multilingual Acoustic Speaker Diarization &amp;amp; Mobile Demo Video Transcription (&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt;)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Stage 1A: Voice Consultation Clip Diarization Prompt
&lt;/span&gt;Transcribe this project supervision meeting audio clip ({{clipLabel}}) verbatim in its original spoken language (Cantonese, Mandarin, or English, preserving English technical terms like React, Firestore, Docker, API).
Identify distinct voices using speaker diarization labels with [MM:SS] timestamps (e.g., [00:05] Speaker 1: ..., [00:42] Speaker 2: ...).
If a speaker states their name or is addressed by name, include their name next to the speaker label (e.g., [00:42] Speaker 2 (Alice): ...).

&lt;span class="gu"&gt;# Stage 1B: Long-Clip Truncation Recovery Suffix (Injected if maxTsSecs &amp;lt; dur * 0.45)
&lt;/span&gt;IMPORTANT: This meeting recording is {{clipDurationMmSs}} long. Do NOT stop early on pauses; transcribe the entire recording from [00:00] through [{{clipDurationMmSs}}].

&lt;span class="gu"&gt;# Stage 1C: 720p Mobile Camera Demo Video Dialogue Prompt
&lt;/span&gt;Transcribe any speech or spoken dialogue during this live project demonstration video clip ({{demoLabel}}) verbatim with speaker diarization and [MM:SS] timestamps. If no speech is present, reply with [Demonstration without speech].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Stage 2 Prompt — Multimodal Roster Attribution &amp;amp; Weekly Log vs. Live Demo Cross-Examination (&lt;code&gt;gemini-4-argon&lt;/code&gt; / &lt;code&gt;gemini-3.8-flash&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;In Stage 2, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt; packages both the snapped &lt;code&gt;image/jpeg&lt;/code&gt; meeting photos (&lt;code&gt;meetingPhotos.slice(0, 8)&lt;/code&gt;) and the &lt;code&gt;720p&lt;/code&gt; &lt;code&gt;video/mp4&lt;/code&gt; / &lt;code&gt;video/webm&lt;/code&gt; mobile demo clips (&lt;code&gt;mobileDemoClips.slice(0, 3)&lt;/code&gt;) into &lt;code&gt;reasoningMediaParts&lt;/code&gt; alongside the text prompt so &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; visually inspects the student's actual hardware/code demonstrations as well as their spoken answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/projectMeetingFlows.js (Stage 2 Multimodal Media Assembly)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;reasoningMediaParts&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;for&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;photo&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;meetingPhotos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pGsUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveGsUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storagePath&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucketName&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;pGsUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reasoningMediaParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pGsUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'image/jpeg'&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;for&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;vClip&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;mobileDemoClips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;vGsUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveGsUri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storagePath&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;vClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucketName&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;vGsUri&lt;/span&gt;&lt;span class="p"&gt;)&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;vContentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vClip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mimeType&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mp4'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'video/mp4'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'video/webm'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;reasoningMediaParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vGsUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vContentType&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoningResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoningModelUsed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;prompt&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoningPrompt&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reasoningMediaParts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;responseMimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;reasoningModel&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;Here is the Stage 2 &lt;code&gt;reasoningPrompt&lt;/code&gt; sent with &lt;code&gt;...reasoningMediaParts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are an expert academic project supervisor and technical meeting analyst for higher education Capstone / Final Year Project (FYP) weekly supervision meetings in Hong Kong.
Speakers in the meeting code-switch naturally between Cantonese, Mandarin, and English technical terminology (e.g., &lt;span class="sb"&gt;`React`&lt;/span&gt;, &lt;span class="sb"&gt;`Firestore`&lt;/span&gt;, &lt;span class="sb"&gt;`Docker`&lt;/span&gt;, &lt;span class="sb"&gt;`Cloud Run`&lt;/span&gt;, &lt;span class="sb"&gt;`CI/CD`&lt;/span&gt;, &lt;span class="sb"&gt;`API`&lt;/span&gt;, &lt;span class="sb"&gt;`Git`&lt;/span&gt;, &lt;span class="sb"&gt;`PR`&lt;/span&gt;).

&lt;span class="gu"&gt;## Meeting Metadata
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Class ID: {{classId}}
&lt;span class="k"&gt;- &lt;/span&gt;Project Group: {{groupName}}
&lt;span class="k"&gt;- &lt;/span&gt;Project Topic: {{projectTopic}}
&lt;span class="k"&gt;- &lt;/span&gt;Week Number: Week {{weekNumber}}
&lt;span class="k"&gt;- &lt;/span&gt;Supervisor: {{supervisorName}} ({{supervisorEmail}})

&lt;span class="gu"&gt;## Enrolled Group Student Roster
&lt;/span&gt;{{rosterLines}}

&lt;span class="gu"&gt;## Deterministic Speaker &amp;amp; Multimodal Evidence Anchors (1-Tap Supervisor Voice Tags, 720p Mobile Demo Videos &amp;amp; Snapped Meeting Photos)
### 1-Tap Supervisor "Speaking Now" Anchors (100% Ground Truth Speaker Identity at Timestamp):
&lt;/span&gt;{{voiceAnchorLines}}

&lt;span class="gu"&gt;### Visual Demonstration Clips &amp;amp; Meeting Photo Artifacts:
#### Meeting Photo Artifacts Captured Live by Supervisor:
&lt;/span&gt;{{meetingPhotoLines}}

&lt;span class="gu"&gt;#### Demonstration Video Clips (720p Mobile Camera):
&lt;/span&gt;{{screenClipLines}}

&lt;span class="gu"&gt;## Prior Week's Assigned Action Items (for Continuity Check)
&lt;/span&gt;{{priorWeekContext}}

&lt;span class="gu"&gt;## Students' Submitted Weekly Log Sheet Entries for Week {{weekNumber}} (for Log Quality Grading &amp;amp; Fake-Log Verification)
&lt;/span&gt;{{weeklyLogsLines}}

&lt;span class="gu"&gt;## Stage 1 Diarized Audio &amp;amp; Demo Video Transcript (from gemini-3.5-transcribe-preview / gemini-3.8-flash)
&lt;/span&gt;"""
{{combinedDiarizedTranscript}}
"""

&lt;span class="gu"&gt;## Task Instructions
&lt;/span&gt;Using the Stage 1 Diarized Audio &amp;amp; Demo Video Transcript, the Deterministic Speaker &amp;amp; Multimodal Evidence Anchors, the attached Meeting Photo Artifacts and 720p Mobile Demo Videos, and the Students' Submitted Weekly Logs above:
1. Resolve anonymous speaker labels (Speaker 1, Speaker 2, etc.) to the exact students in the Enrolled Group Student Roster or the Supervisor using opening self-introductions, 1-tap voice anchors, discussed project tasks, and live 720p mobile camera demonstrations.
2. Determine the Attendance Record for every student in the roster ('present', 'late', or 'absent'), their speakingContribution ('high', 'moderate', 'low', 'none'), a concise evidence string with [MM:SS] timestamp, and firstHeardTimestamp ('MM:SS').
3. Extract Key Meeting Insights (executiveSummary, overallGroupProgress: 'on_track' | 'ahead' | 'at_risk' | 'behind', keyDecisions[], blockersAndRisks[], supervisorFeedback[], and weeklyGroupFeedback).
4. Cross-verify each student's &lt;span class="gs"&gt;**Submitted Weekly Log**&lt;/span&gt; against what they &lt;span class="gs"&gt;**actually discussed/demonstrated in the meeting**&lt;/span&gt;, including any attached &lt;span class="gs"&gt;**Meeting Photo Artifacts**&lt;/span&gt; (whiteboard diagrams, hardware wiring, terminal code) and &lt;span class="gs"&gt;**720p Mobile Camera Demonstration Videos**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Flag &lt;span class="gs"&gt;**vague/useless logs**&lt;/span&gt; (e.g. "done", "thinking") as &lt;span class="sb"&gt;`"vague_useless"`&lt;/span&gt; and &lt;span class="gs"&gt;**AI-generated/fake logs**&lt;/span&gt; that were NOT backed up by real understanding, code, or visual demonstration in the meeting as &lt;span class="sb"&gt;`"suspected_ai_unverified"`&lt;/span&gt; or &lt;span class="sb"&gt;`"contradicted_in_meeting"`&lt;/span&gt;. If verified by their spoken explanation, 720p demo video, or photo artifacts, mark &lt;span class="sb"&gt;`"genuine_verified"`&lt;/span&gt;.
   &lt;span class="k"&gt;- &lt;/span&gt;Grade &lt;span class="sb"&gt;`logQualityScore`&lt;/span&gt; (0 to 10) and &lt;span class="sb"&gt;`realWorkScore`&lt;/span&gt; (0 to 10). Cite specific visual evidence from photos or 720p demo clips when available.
   &lt;span class="k"&gt;- &lt;/span&gt;Provide 1-2 &lt;span class="sb"&gt;`challengeQuestions`&lt;/span&gt; and a constructive 2-3 sentence &lt;span class="sb"&gt;`weeklyFeedback`&lt;/span&gt; for the student covering both their real project work and how to improve their weekly log quality.
5. Output speaker-attributed transcriptSegments[] with startTime ('MM:SS'), endTime ('MM:SS'), speaker (resolved student name or Supervisor), and text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Production Code 3: Stage 2 Deterministic Roster Reconciliation (&lt;code&gt;reconcileMeetingRosterResults&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;In Stage 2, &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; cross-examines the diarized transcript, the &lt;code&gt;720p&lt;/code&gt; mobile demo videos, the snapped photos, and the students' written weekly logs. It evaluates whether a student's live demo and spoken answers &lt;strong&gt;verified&lt;/strong&gt; or &lt;strong&gt;contradicted&lt;/strong&gt; their written log (&lt;code&gt;genuine_verified&lt;/code&gt;, &lt;code&gt;contradicted_in_meeting&lt;/code&gt;, &lt;code&gt;suspected_ai_fake&lt;/code&gt;, &lt;code&gt;vague_useless&lt;/code&gt;, &lt;code&gt;inactive&lt;/code&gt;) and assigns two separate marks out of 10:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;logQualityScore&lt;/code&gt; (&lt;code&gt;/10&lt;/code&gt;)&lt;/strong&gt;: Quality, specificity, and honesty of the written weekly logbook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;realWorkScore&lt;/code&gt; (&lt;code&gt;/10&lt;/code&gt;)&lt;/strong&gt;: Verified engineering progress demonstrated live via the &lt;code&gt;720p&lt;/code&gt; demo video, photos, and technical Q&amp;amp;A.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, what if a 4-member group has a silent free-rider who never spoke a word during the meeting, causing the LLM to omit them from its output array? Or what if the supervisor manually tapped &lt;strong&gt;&lt;code&gt;🗣️ Speaking Now&lt;/code&gt;&lt;/strong&gt; for a student whose voice was too soft for the microphone?&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;reconcileMeetingRosterResults&lt;/code&gt; deterministically reconciles the AI's JSON output against the authoritative &lt;code&gt;groupData.members&lt;/code&gt; roster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/projectMeetingFlows.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reconcileMeetingRosterResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;existingAttendance&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;aiAttendance&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;aiContributions&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;voiceAnchors&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;screenClips&lt;/span&gt; &lt;span class="o"&gt;=&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="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;// Maps existing manual overrides, AI attendance, and AI contributions by email/uid/name&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;attendanceRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalizedMembers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="nf"&gt;trim&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;email&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;email&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;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existingByKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;existingByKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;aiAtt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiAttByKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;aiAttByKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="n"&gt;aiAttByKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="nf"&gt;toLowerCase&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Match 1-tap supervisor 'Speaking Now' voice anchors &amp;amp; demo clips for this student&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;studentAnchors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;voiceAnchors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;voiceAnchors&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="nf"&gt;trim&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="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;uid&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;studentScreens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screenClips&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;screenClips&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentEmail&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="nf"&gt;trim&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="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;uid&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;hasAnchorOrScreen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;studentAnchors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;studentScreens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiAtt&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hasAnchorOrScreen&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'present'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'absent'&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;hasAnchorOrScreen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'absent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'present'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Supervisor voice anchor or demo clip overrides false-negative absence&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;teacherVerified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;existing&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;teacherVerified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Honor explicit teacher attendance badge override&lt;/span&gt;
      &lt;span class="n"&gt;teacherVerified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;studentAnchors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;teacherVerified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;email&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'Member'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;detectedByAi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiAtt&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'present'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;aiAtt&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'late'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;hasAnchorOrScreen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;teacherVerified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;speakingContribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aiAtt&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speakingContribution&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hasAnchorOrScreen&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'high'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'present'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'moderate'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aiAtt&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'present'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'Detected in meeting discussion.'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'No spoken turn or demo detected.'&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="p"&gt;;&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;// Also normalizes studentContributions[], clamping logQualityScore &amp;amp; realWorkScore to [0, 10]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;attendanceRecord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;studentContributions&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. 1-Click Class-Wide HTML Email Dispatch &amp;amp; Longitudinal Student Portfolios
&lt;/h2&gt;

&lt;p&gt;Once the supervisor reviews the generated report (or edits any score/feedback inline), clicking &lt;strong&gt;&lt;code&gt;📧 Email All Week W Reports&lt;/code&gt;&lt;/strong&gt; invokes &lt;code&gt;batchEmailProjectMeetingReports&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/projectMeetingFlows.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using SMTP credentials mounted strictly at runtime from &lt;strong&gt;Google Cloud Secret Manager (&lt;code&gt;SMTP_PASSWORD&lt;/code&gt;)&lt;/strong&gt;, the function formats a responsive, badge-styled HTML supervision report containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An optional highlighted &lt;strong&gt;👨‍🏫 Supervisor Note&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Executive Summary &amp;amp; Key Architectural Decisions&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Attendance &amp;amp; Speaking Contribution Table&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;Each student's &lt;strong&gt;Written Weekly Log vs. Live Verified Evaluation (&lt;code&gt;Log Quality /10&lt;/code&gt;, &lt;code&gt;Real Work /10&lt;/code&gt;, &lt;code&gt;Status Badge&lt;/code&gt;, &lt;code&gt;Work Completed&lt;/code&gt;, &lt;code&gt;Next Week Action Items&lt;/code&gt;, and &lt;code&gt;Challenge Questions&lt;/code&gt;)&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;Dispatches the email simultaneously to all group members (&lt;code&gt;To&lt;/code&gt;) and the supervisor (&lt;code&gt;Cc&lt;/code&gt;), and writes an immutable &lt;code&gt;emailDispatch&lt;/code&gt; audit timestamp onto the Firestore meeting document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At any point during the semester, clicking &lt;strong&gt;&lt;code&gt;🎓 Student Profiles &amp;amp; History&lt;/code&gt;&lt;/strong&gt; opens the longitudinal &lt;strong&gt;Student Project Portfolio View&lt;/strong&gt;, charting each student's attendance rate, average &lt;code&gt;Log Quality&lt;/code&gt; vs. &lt;code&gt;Real Work&lt;/code&gt; scores across Weeks 1–16, and flags chronic free-riders before final capstone grading!&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next in Part 9?
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 9&lt;/strong&gt;, we turn to live classroom engagement and formal assessment security—exploring how we engineered &lt;strong&gt;Gamified "Classroom Bingo" Active Presence Verification&lt;/strong&gt; (backed by a &lt;strong&gt;Gemini 4 Argon Question Bank Studio&lt;/strong&gt; and a &lt;strong&gt;Non-Punitive Two-Strike Google Cloud Tasks State Machine&lt;/strong&gt;) alongside &lt;strong&gt;Zero-Trust Exam Mode Lockdown&lt;/strong&gt; and &lt;strong&gt;Court-Ready Microsoft Word (&lt;code&gt;.docx&lt;/code&gt;) Incident Dossiers&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>firebase</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Google AI Classroom Part 7: Serverless Map-Reduce-Map Video Intelligence &amp; Rubric-Driven Practical Task Auto-Grading with Gemini 4 Argon</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Wed, 11 Nov 2026 03:29:14 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-7-serverless-map-reduce-map-video-intelligence-rubric-driven-practical-12on</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-7-serverless-map-reduce-map-video-intelligence-rubric-driven-practical-12on</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The 80-Hour Lab Grading Bottleneck &amp;amp; The HTTP 429 Quota Wall
&lt;/h2&gt;

&lt;p&gt;In hands-on cloud computing, DevSecOps, and full-stack software engineering courses, students spend 2 to 3 hours building infrastructure in the AWS/GCP Console, writing Terraform modules in VS Code, and debugging container deployments in the terminal. Reviewing a single 40-student cohort's 2-hour screen recordings manually requires &lt;strong&gt;80 hours of instructor time per lab&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, when our engineering team first attempted to automate post-class video grading by firing 40 concurrent multimodal video analysis calls against the Gemini API at the end of a lab session, two hard distributed systems walls appeared immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Serverless Execution Timeout Wall (&lt;code&gt;540s&lt;/code&gt;)&lt;/strong&gt;: Even a 2nd-Gen Cloud Run-backed Cloud Function caps synchronous execution at 540 seconds (9 minutes). Sequentially evaluating 40 multi-megabyte lab recordings inside a single monolithic worker inevitably times out mid-cohort, leaving half the class ungraded and corrupting aggregate progress counters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini API HTTP &lt;code&gt;429 RESOURCE_EXHAUSTED&lt;/code&gt; Rate-Limit Spikes&lt;/strong&gt;: Sending 40 uncompressed 30-FPS screen recordings simultaneously floods regional Tokens-Per-Minute (TPM) and Requests-Per-Minute (RPM) quotas, triggering cascading HTTP &lt;code&gt;429&lt;/code&gt; rate-limit errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM Scoring Drift &amp;amp; Hallucinated Arithmetic&lt;/strong&gt;: When evaluating multi-step practical exams, raw LLM JSON responses occasionally award &lt;code&gt;25/20&lt;/code&gt; points on a rubric step, omit a milestone when a student stops early, or miscalculate the sum of individual step scores.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;strong&gt;Part 7&lt;/strong&gt; of our 10-part Google Developer Expert engineering series, we walk through how we solved all three challenges by combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Run FFmpeg 1-FPS H.264 Timelapse Compilation&lt;/strong&gt; (shrinking a 2-hour lab into a &lt;code&gt;&amp;lt;20 MB&lt;/code&gt; forensic MP4 and slashing multimodal video token consumption by &lt;strong&gt;90%+&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Tasks Rate-Limited Fan-Out (&lt;code&gt;maxDispatchesPerSecond: 2&lt;/code&gt;, &lt;code&gt;maxConcurrentDispatches: 4&lt;/code&gt;)&lt;/strong&gt; for zero-timeout, zero-&lt;code&gt;429&lt;/code&gt; distributed video processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-Stage Map-Reduce-Map Cohort Discovery&lt;/strong&gt; (&lt;code&gt;processVideoAnalysisJob&lt;/code&gt; $\rightarrow$ &lt;code&gt;analyzeSingleVideoTask&lt;/code&gt; $\rightarrow$ &lt;code&gt;generateLabTaskPrompt&lt;/code&gt;), enabling teachers to select &lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, or &lt;strong&gt;🪶 Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt; to automatically discover what milestones a class achieved and synthesize a tool-calling grading rubric.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 4-Tab Practical Task &amp;amp; Deterministic Rubric Grading Engine&lt;/strong&gt; (&lt;code&gt;extractTaskDemoSteps.js&lt;/code&gt;, &lt;code&gt;evaluateTaskSubmission.js&lt;/code&gt;, &lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;, and &lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;) with &lt;strong&gt;Ultrafast Stream-Copy Retroactive Video Slicing (&lt;code&gt;-c copy&lt;/code&gt;)&lt;/strong&gt; and a deterministic mathematical guardrail (&lt;code&gt;normalizeRubricEvaluation&lt;/code&gt; / &lt;code&gt;reconcileStepEvaluationsWithRubric&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/XA9bKmqJUZg" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Diagram 1: From Periodic Screenshots to Forensic 1-FPS H.264 MP4s (80% Storage &amp;amp; 90% Token Reduction)
&lt;/h2&gt;

&lt;p&gt;Why record a 30-FPS desktop video stream when a student coding in VS Code or configuring a VPC in the browser changes meaningful visual state every 5 to 15 seconds?&lt;/p&gt;

&lt;p&gt;Before Gemini ever evaluates a student's work, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/StudentView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;StudentView.jsx&lt;/code&gt;&lt;/a&gt; and our high-memory Cloud Run worker &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/processVideoJob.js" rel="noopener noreferrer"&gt;&lt;code&gt;processVideoJob.js&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;2 vCPU, 8 GiB RAM, 540s timeout&lt;/code&gt;) transform periodic screenshots into a razor-sharp, tamper-evident &lt;strong&gt;1-FPS H.264 MP4 timelapse&lt;/strong&gt; that is &lt;strong&gt;80% smaller&lt;/strong&gt; than continuous screen recordings:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj330qndlye2zwthrt2vw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj330qndlye2zwthrt2vw.png" alt="Adaptive Client Scaling, Sharp 40px SVG Overlay &amp;amp; Cloud Run FFmpeg 1-FPS MP4 Pipeline" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Engineering Highlights of the Cloud Run FFmpeg Pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single-Pass Client Geometric Scaling&lt;/strong&gt;: On 4K Retina displays, raw canvas captures can exceed &lt;code&gt;3 MB&lt;/code&gt;. Instead of an expensive multi-pass compression loop in the browser, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/StudentView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;StudentView.jsx&lt;/code&gt;&lt;/a&gt; computes a single-pass geometric scaling ratio:
$$\text{scale} = \sqrt{\frac{\text{maxImageSize}}{\text{blob.size}}} \times 0.9$$
This guarantees every uploaded frame stays well beneath the &lt;code&gt;2 MB&lt;/code&gt; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/storage.rules" rel="noopener noreferrer"&gt;&lt;code&gt;storage.rules&lt;/code&gt;&lt;/a&gt; threshold while preserving crisp terminal text legibility at up to &lt;code&gt;1920px&lt;/code&gt; (1080p).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory-Bounded Sharp SVG Watermarking (&lt;code&gt;BATCH_SIZE = 15&lt;/code&gt;)&lt;/strong&gt;: Inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/processVideoJob.js" rel="noopener noreferrer"&gt;&lt;code&gt;processVideoJob.js&lt;/code&gt;&lt;/a&gt;, downloading and compositing 720 high-resolution frames concurrently via &lt;code&gt;Promise.all()&lt;/code&gt; would spike heap memory and crash the container. We process frames in chunks of &lt;code&gt;BATCH_SIZE = 15&lt;/code&gt;, using &lt;code&gt;sharp&lt;/code&gt; to burn a &lt;strong&gt;40px forensic SVG banner&lt;/strong&gt; onto every frame (&lt;code&gt;YYYY-MM-DD HH:mm:ss | Class ID | Student Email | [SCREEN]/[WEBCAM]&lt;/code&gt;) and rounding dimensions to even integers (&lt;code&gt;w - (w % 2)&lt;/code&gt;) required by the H.264 &lt;code&gt;yuv420p&lt;/code&gt; planar chroma format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Burn-In Timestamps Matter for Gemini 4&lt;/strong&gt;: Because the compiled MP4 plays back at 1 frame per second (where 1 second of video equals 15 seconds of real classroom time), a naive LLM looking only at the MP4 container's playback clock would miscalculate how long a student spent on a task by a factor of $15\times$. Burning the real wall-clock timestamp directly into every frame allows &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; and &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; to read the exact on-screen clock via optical character recognition!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Diagram 2: Two-Stage Map-Reduce-Map Cohort Video Discovery with Cloud Tasks &amp;amp; Gemini 4 Argon
&lt;/h2&gt;

&lt;p&gt;What happens when an instructor runs an open-ended hands-on cloud lab—where students explore AWS/GCP services at their own pace—and wants AI to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Watch all 40 student timelapse videos without hitting rate limits (&lt;strong&gt;Phase 1: Map&lt;/strong&gt;),&lt;/li&gt;
&lt;li&gt;Discover the common coursework milestones, point weights, and technical blockers encountered across the entire cohort (&lt;strong&gt;Phase 2: Reduce&lt;/strong&gt;), and&lt;/li&gt;
&lt;li&gt;Re-evaluate every student against that newly synthesized rubric to populate a live &lt;strong&gt;Coursework Milestone Matrix&lt;/strong&gt; (&lt;strong&gt;Phase 3: Second Map&lt;/strong&gt;)?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/index.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/index.mjs&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/generateLabTaskPrompt.js" rel="noopener noreferrer"&gt;&lt;code&gt;generateLabTaskPrompt.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoLibrary.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoLibrary.jsx&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoAnalysisJobs.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoAnalysisJobs.jsx&lt;/code&gt;&lt;/a&gt;, we built a &lt;strong&gt;Serverless Map-Reduce-Map Pipeline&lt;/strong&gt; where teachers can select &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/strong&gt;, or &lt;strong&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/strong&gt; at both the Map and Reduce stages:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3g2x7tpo5b0lqt3ewz8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3g2x7tpo5b0lqt3ewz8.png" alt="Two-Stage Map-Reduce-Map Cohort Video Discovery with Cloud Tasks &amp;amp; Gemini 4 Argon" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code 1: The Reduce Stage — Synthesizing a Cohort Grading Rubric from 40 Student Videos (&lt;code&gt;generateLabTaskPrompt.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Let's examine &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/generateLabTaskPrompt.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/generateLabTaskPrompt.js&lt;/code&gt;&lt;/a&gt;. When Phase 1 (the exploratory Map pass) finishes, each student's raw activity observation is stored in &lt;code&gt;aiJobs/{jobId}&lt;/code&gt;. Because Firestore's &lt;code&gt;in&lt;/code&gt; query operator accepts at most 30 document IDs per query, &lt;code&gt;generateLabTaskPrompt&lt;/code&gt; fetches the completed child jobs in 30-document chunks, compiles them into a cohort-wide activity corpus, and invokes &lt;code&gt;generateWithResilience&lt;/code&gt; using the teacher's chosen model (such as &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/generateLabTaskPrompt.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;generateLabTaskPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;onCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FUNCTION_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;CORS_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'1GiB'&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unauthenticated'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'The function must be called while authenticated.'&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;masterJobRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'videoAnalysisJobs'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;)&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;jobDoc&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;masterJobRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;jobData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;classId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'unknown_class'&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;aiJobIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aiJobIds&lt;/span&gt; &lt;span class="o"&gt;||&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="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;completedJobs&lt;/span&gt; &lt;span class="o"&gt;=&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;// Fetch child aiJobs via 30-document batched FieldPath.documentId() queries&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;aiJobIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;aiJobIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&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;batchIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiJobIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&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;querySnapshot&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;db&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'aiJobs'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FieldPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;documentId&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="s1"&gt;'in'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batchIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="n"&gt;querySnapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;completedJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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="p"&gt;}&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Compile observations across all students in the cohort&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;compiledObservations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;completedJobs&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;studentIdentifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentEmail&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sb"&gt;`Student ${idx + 1}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`### Student ${idx + 1}: ${studentIdentifier}\n${job.result.trim()}`&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\n\n---\n\n'&lt;/span&gt;&lt;span class="p"&gt;)&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;preferredModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'gemini-3.8-flash'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;prompt&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metaPrompt&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="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&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="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;preferredModel&lt;/span&gt;
    &lt;span class="p"&gt;)&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;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt; &lt;span class="o"&gt;||&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;logJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;jobType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'generateLabTaskPrompt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metaPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;mediaPaths&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="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promptTokenCount&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="n"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidatesTokenCount&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;modelUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generatedPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;masterJobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jobId&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="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="n"&gt;generatedPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generatedPrompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="n"&gt;modelUsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;summaryCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completedJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;jobId&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="p"&gt;}&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;h3&gt;
  
  
  The 3 Prompts Powering the Map-Reduce-Map Video Pipeline
&lt;/h3&gt;

&lt;p&gt;To make this architecture concrete and reproducible, here are the exact prompts executed at each stage of the &lt;strong&gt;Map-Reduce-Map&lt;/strong&gt; workflow:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Phase 1 (Exploratory Map Prompt) — Unsupervised Activity Discovery (&lt;code&gt;Summarize Student's Work.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;In Phase 1, the lecturer does not need to write a lab rubric in advance. Each Cloud Tasks worker (&lt;code&gt;analyzeSingleVideoTask&lt;/code&gt; $\rightarrow$ &lt;code&gt;analyzeSingleVideoFlow&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/analysisFlows.js&lt;/code&gt;&lt;/a&gt;) wraps the exploratory template (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/admin/prompts/videos/Summarize%20Student's%20Work.md" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/videos/Summarize Student's Work.md&lt;/code&gt;&lt;/a&gt;) with the class's time-lapse capture interval (&lt;code&gt;captureInterval = 15s&lt;/code&gt;) and dispatches it in parallel across all 40 student videos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Summarize Student's Activity
&lt;/span&gt;
&lt;span class="gs"&gt;**You are an AI teaching assistant. Your task is to summarize the activity of each student based on their video.**&lt;/span&gt;

&lt;span class="gu"&gt;## Video Context
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;The video is a fast-forwarded time-lapse of screen captures, not a real-time recording (compiled at 1 frame per second from screenshots captured roughly every {{captureInterval}} seconds).
&lt;span class="k"&gt;* &lt;/span&gt;Each frame includes a burned-in timestamp (date and time). All calculations of duration (e.g., for attendance, concentration time) &lt;span class="gs"&gt;**MUST**&lt;/span&gt; be based on these in-frame timestamps.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Do not use the video's playback length for any time-based calculations.**&lt;/span&gt;

&lt;span class="gu"&gt;## Attendance &amp;amp; Engagement Analysis
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;Compare the timeframe of the student's activity (derived from the in-frame timestamps) with the scheduled lesson time ({{startTime}} to {{endTime}}) to estimate attendance. Note any late starts, early leaves, or gaps in screen sharing.
&lt;span class="k"&gt;* &lt;/span&gt;Based on the student's on-screen activity and focus, estimate the total "concentration minutes" during the observed period using the in-frame timestamps, and call the &lt;span class="sb"&gt;`recordActualWorkingTime`&lt;/span&gt; tool.

&lt;span class="gu"&gt;## Analysis Guidelines
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;Identify the main tasks, cloud consoles, CLI commands, and code files the student is working on throughout the video.
&lt;span class="k"&gt;* &lt;/span&gt;Describe the student's pace, milestones achieved, and any test scores or verification outputs visible on screen.
&lt;span class="k"&gt;* &lt;/span&gt;Note any periods of inactivity, error messages, or technical struggle.

&lt;span class="gu"&gt;## Actions
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;Call &lt;span class="sb"&gt;`recordActualWorkingTime`&lt;/span&gt; to record your estimate of the student's concentration minutes.
&lt;span class="k"&gt;* &lt;/span&gt;Call &lt;span class="sb"&gt;`recordLessonSummary`&lt;/span&gt; to record a concise, informative summary of their activity, progress, and blockers.
&lt;span class="k"&gt;* &lt;/span&gt;Provide the exact same summary as your final text response.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Phase 2 (Reduce Meta-Prompt) — Synthesizing a Cohort Rubric from 40 Observations (&lt;code&gt;Two-Stage Map-Reduce Lab Rubric Synthesizer.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Once the Phase 1 Map workers finish, clicking &lt;strong&gt;✨ Generate Lab Task Prompt&lt;/strong&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoAnalysisJobs.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoAnalysisJobs.jsx&lt;/code&gt;&lt;/a&gt; invokes &lt;code&gt;generateLabTaskPrompt.js&lt;/code&gt;, which injects all 40 student summaries into &lt;code&gt;{{compiledObservations}}&lt;/code&gt; inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/admin/prompts/videos/Two-Stage%20Map-Reduce%20Lab%20Rubric%20Synthesizer.md" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/videos/Two-Stage Map-Reduce Lab Rubric Synthesizer.md&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are an expert Computer Science university curriculum designer and AI prompt engineer for classroom invigilation and automated lab assessment.

Below are student activity observations collected from an initial video analysis across a class session (Class ID: "{{classId}}"):

=== BEGIN OBSERVED STUDENT ACTIVITIES ===
{{compiledObservations}}
=== END OBSERVED STUDENT ACTIVITIES ===

Based on these actual classroom observations, analyze the student activities and generate an optimized, comprehensive, ready-to-use Markdown task prompt for a high-precision second-pass evaluation of this lab.

The generated prompt MUST strictly follow this Markdown structure:

&lt;span class="gu"&gt;# [Descriptive Lab Title Based on Discovered Coursework]
&lt;/span&gt;
&lt;span class="gs"&gt;**You are an AI teaching assistant evaluating student screen recording time-lapses for [Lab Topic/Subject].**&lt;/span&gt;

&lt;span class="gu"&gt;## Video Timing Rules
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;The video is a fast-forward time-lapse with on-screen timestamps.
&lt;span class="k"&gt;* &lt;/span&gt;All duration, attendance, and task timing calculations &lt;span class="gs"&gt;**MUST**&lt;/span&gt; be derived from the in-frame date/time stamps, NOT the video playback length.

&lt;span class="gu"&gt;## Coursework Tasks to Evaluate
&lt;/span&gt;[Break down the lab into 3-5 clearly numbered, specific tasks discovered from the student observations. Include specific platforms (e.g. AWS, Azure, Cloud Shell, VS Code), repositories, scripts, resource names, and milestones].

&lt;span class="gu"&gt;## Milestones, Scores &amp;amp; Rubrics
&lt;/span&gt;[Detail any test numbers, scoring points (e.g., 10 pts, 20 pts), or completion verifications discovered from the observations].

&lt;span class="gu"&gt;## Known Blockers &amp;amp; Technical Obstacles to Watch For
&lt;/span&gt;[List the common technical errors, blockers, or pitfalls students encountered, such as backend errors, incorrect regions, or setup issues].

&lt;span class="gu"&gt;## Required Tool Actions
&lt;/span&gt;1. &lt;span class="gs"&gt;**Working Time**&lt;/span&gt;: Call 'recordActualWorkingTime' with the total estimated active concentration minutes (clamped to lesson duration).
2. &lt;span class="gs"&gt;**Task Durations**&lt;/span&gt;: For each identified task above, call 'recordTaskDuration' with studentUid, classId, taskName, and estimated durationMinutes spent on that task.
3. &lt;span class="gs"&gt;**Student Summary**&lt;/span&gt;: Call 'recordLessonSummary' with a structured bulleted summary of their progress across each task, points earned, blockers encountered, and engagement verdict.
4. &lt;span class="gs"&gt;**Final Response**&lt;/span&gt;: Output the exact same structured summary text provided to 'recordLessonSummary'.

Return ONLY the clean Markdown prompt text. Do not wrap in markdown code fence blocks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Phase 3 (Second-Map Evaluation Prompt) — Example Output Synthesized by Gemini 4 Argon
&lt;/h4&gt;

&lt;p&gt;Here is an actual example of the Markdown prompt synthesized by Phase 2 from a 40-student cloud infrastructure lab, which the teacher reviews in the modal and dispatches back to Cloud Tasks (&lt;code&gt;analyzeSingleVideoTask&lt;/code&gt;) for the &lt;strong&gt;Second Map&lt;/strong&gt; pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Serverless Cloud Run &amp;amp; Terraform Infrastructure Lab Evaluation
&lt;/span&gt;
&lt;span class="gs"&gt;**You are an AI teaching assistant evaluating student screen recording time-lapses for Cloud Infrastructure &amp;amp; Serverless Container Deployment.**&lt;/span&gt;

&lt;span class="gu"&gt;## Video Timing Rules
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;The video is a fast-forward time-lapse with on-screen timestamps (&lt;span class="sb"&gt;`YYYY-MM-DD HH:mm:ss`&lt;/span&gt;).
&lt;span class="k"&gt;* &lt;/span&gt;All duration, attendance, and task timing calculations &lt;span class="gs"&gt;**MUST**&lt;/span&gt; be derived from the in-frame date/time stamps, NOT the video playback length.

&lt;span class="gu"&gt;## Coursework Tasks to Evaluate
&lt;/span&gt;1. &lt;span class="gs"&gt;**Task 1: Environment Setup &amp;amp; Repository Cloning**&lt;/span&gt;: Cloning the starter repo in VS Code / Cloud Shell, configuring &lt;span class="sb"&gt;`.env`&lt;/span&gt; variables, and running &lt;span class="sb"&gt;`npm install`&lt;/span&gt;.
2. &lt;span class="gs"&gt;**Task 2: Docker Containerization &amp;amp; Local Verification**&lt;/span&gt;: Writing the multi-stage &lt;span class="sb"&gt;`Dockerfile`&lt;/span&gt;, building the container image via &lt;span class="sb"&gt;`docker build`&lt;/span&gt; / &lt;span class="sb"&gt;`gcloud builds submit`&lt;/span&gt;, and verifying &lt;span class="sb"&gt;`localhost:8080/healthz`&lt;/span&gt; returns &lt;span class="sb"&gt;`HTTP 200 OK`&lt;/span&gt;.
3. &lt;span class="gs"&gt;**Task 3: Terraform Cloud Run &amp;amp; Firestore Provisioning**&lt;/span&gt;: Writing &lt;span class="sb"&gt;`main.tf`&lt;/span&gt; (&lt;span class="sb"&gt;`google_cloud_run_v2_service`&lt;/span&gt; and &lt;span class="sb"&gt;`google_firestore_database`&lt;/span&gt;), running &lt;span class="sb"&gt;`terraform init &amp;amp;&amp;amp; terraform plan`&lt;/span&gt;, and applying infrastructure via &lt;span class="sb"&gt;`terraform apply -auto-approve`&lt;/span&gt;.
4. &lt;span class="gs"&gt;**Task 4: End-to-End Automated Grading Script Verification**&lt;/span&gt;: Executing &lt;span class="sb"&gt;`./run-lab-tests.sh`&lt;/span&gt; in the terminal and verifying all 5 integration assertions pass against the live &lt;span class="sb"&gt;`.run.app`&lt;/span&gt; endpoint.

&lt;span class="gu"&gt;## Milestones, Scores &amp;amp; Rubrics
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Task 1 (15 pts)**&lt;/span&gt;: Starter repository cloned and dependencies installed without missing package errors.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Task 2 (25 pts)**&lt;/span&gt;: Container image built and local health check (&lt;span class="sb"&gt;`200 OK`&lt;/span&gt;) verified in terminal or browser.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Task 3 (35 pts)**&lt;/span&gt;: &lt;span class="sb"&gt;`terraform apply`&lt;/span&gt; completes with &lt;span class="sb"&gt;`Apply complete! Resources: 4 added, 0 changed, 0 destroyed`&lt;/span&gt; and outputs the live Cloud Run URL.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Task 4 (25 pts)**&lt;/span&gt;: Automated test script outputs &lt;span class="sb"&gt;`SCORE: 100/100 (5/5 tests passed)`&lt;/span&gt;.

&lt;span class="gu"&gt;## Known Blockers &amp;amp; Technical Obstacles to Watch For
&lt;/span&gt;&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Port Mismatch (`PORT=8080`)**&lt;/span&gt;: Container failing Cloud Run startup probe because the Express server listened on port &lt;span class="sb"&gt;`3000`&lt;/span&gt; instead of &lt;span class="sb"&gt;`process.env.PORT`&lt;/span&gt;.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**IAM Permission Denied (`403`)**&lt;/span&gt;: &lt;span class="sb"&gt;`terraform apply`&lt;/span&gt; failing on Cloud Run IAM binding due to missing &lt;span class="sb"&gt;`roles/run.admin`&lt;/span&gt; or unenabled &lt;span class="sb"&gt;`run.googleapis.com`&lt;/span&gt; API.
&lt;span class="k"&gt;* &lt;/span&gt;&lt;span class="gs"&gt;**Region Mismatch**&lt;/span&gt;: Deploying Firestore in &lt;span class="sb"&gt;`us-central1`&lt;/span&gt; while Cloud Run is configured in &lt;span class="sb"&gt;`asia-east2`&lt;/span&gt;.

&lt;span class="gu"&gt;## Required Tool Actions
&lt;/span&gt;1. &lt;span class="gs"&gt;**Working Time**&lt;/span&gt;: Call &lt;span class="sb"&gt;`recordActualWorkingTime`&lt;/span&gt; with the total estimated active concentration minutes (clamped to lesson duration).
2. &lt;span class="gs"&gt;**Task Durations**&lt;/span&gt;: For each identified task above (&lt;span class="sb"&gt;`Task 1`&lt;/span&gt;, &lt;span class="sb"&gt;`Task 2`&lt;/span&gt;, &lt;span class="sb"&gt;`Task 3`&lt;/span&gt;, &lt;span class="sb"&gt;`Task 4`&lt;/span&gt;), call &lt;span class="sb"&gt;`recordTaskDuration`&lt;/span&gt; with &lt;span class="sb"&gt;`studentUid`&lt;/span&gt;, &lt;span class="sb"&gt;`classId`&lt;/span&gt;, &lt;span class="sb"&gt;`taskName`&lt;/span&gt;, and estimated &lt;span class="sb"&gt;`durationMinutes`&lt;/span&gt; spent on that task.
3. &lt;span class="gs"&gt;**Student Summary**&lt;/span&gt;: Call &lt;span class="sb"&gt;`recordLessonSummary`&lt;/span&gt; with a structured bulleted summary of their progress across each task, points earned (out of 100), blockers encountered, and engagement verdict.
4. &lt;span class="gs"&gt;**Final Response**&lt;/span&gt;: Output the exact same structured summary text provided to &lt;span class="sb"&gt;`recordLessonSummary`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the teacher runs &lt;strong&gt;Phase 3 (the Milestone Evaluation Map)&lt;/strong&gt; using that synthesized prompt, each Cloud Tasks worker invokes Genkit tools (&lt;code&gt;recordActualWorkingTime&lt;/code&gt;, &lt;code&gt;recordTaskDuration&lt;/code&gt;, and &lt;code&gt;recordLessonSummary&lt;/code&gt;) that populate &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoAnalysisJobs.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoAnalysisJobs.jsx&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt;—giving the lecturer a color-coded cohort progress matrix showing exactly how many minutes each student spent on Task 1, Task 2, Task 3, and Task 4, and where they got stuck!&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Diagram 3: 4-Tab Practical Tasks, Retroactive Post-Class Slicing &amp;amp; Deterministic Rubric Reconciliation
&lt;/h2&gt;

&lt;p&gt;While Map-Reduce-Map excels at exploratory cohort discovery, graded practical assignments and lab tests (&lt;code&gt;📋 Tasks &amp;amp; Homework&lt;/code&gt; tab) require a strict, reproducible rubric where every student is graded against an identical point breakdown, with timestamped visual proof and full teacher override authority.&lt;/p&gt;

&lt;p&gt;To support this, we engineered a &lt;strong&gt;4-Tab Practical Task &amp;amp; Rubric Grading System&lt;/strong&gt; across &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/extractTaskDemoSteps.js" rel="noopener noreferrer"&gt;&lt;code&gt;extractTaskDemoSteps.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/evaluateTaskSubmission.js" rel="noopener noreferrer"&gt;&lt;code&gt;evaluateTaskSubmission.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Teachers have &lt;strong&gt;two independent AI model selection control points&lt;/strong&gt; supporting &lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;🪶 Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Task Editor Modal (&lt;code&gt;#task-ai-model-select&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt;)&lt;/strong&gt;: Chooses the default AI model stored on &lt;code&gt;classes/{classId}/tasks/{taskId}.model&lt;/code&gt; and used by &lt;code&gt;extractTaskDemoSteps&lt;/code&gt; when watching a teacher's reference demonstration video to synthesize the step-by-step rubric.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grading Matrix Toolbar (&lt;code&gt;Grading AI Model&lt;/code&gt; selector in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt;)&lt;/strong&gt;: Allows the teacher to override the evaluation model on the fly when running single-student re-evaluations (&lt;code&gt;handleTriggerEvaluate&lt;/code&gt;) or batch cohort grading (&lt;code&gt;handleBatchEvaluatePending&lt;/code&gt; via &lt;code&gt;batchEvaluateTaskSubmissions&lt;/code&gt;). For example, a teacher can grade formative practice attempts with &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; and switch the toolbar selector to &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; for final high-stakes practical exam grading!&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet7h569d0o98en8b66ou.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet7h569d0o98en8b66ou.png" alt="4-Tab Practical Tasks, Retroactive Slicing &amp;amp; Deterministic Rubric Reconciliation" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Deep Dive into the Production Code: Rubric Extraction, Zero-Race Stream-Copy Slicing &amp;amp; Deterministic Score Reconciliation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Production Code 2: Multimodal Reference Video Rubric Synthesis (&lt;code&gt;extractTaskDemoSteps.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Instead of writing complex JSON rubrics by hand, an instructor can upload a 3-minute demonstration video of the solution (or pick an existing &lt;strong&gt;Teacher Lecture Recording&lt;/strong&gt; or paste a &lt;strong&gt;YouTube URL&lt;/strong&gt;) in Tab 2 of &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt; and click &lt;strong&gt;✨ Extract Steps &amp;amp; Rubric with AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/extractTaskDemoSteps.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/extractTaskDemoSteps.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;handleExtractTaskDemoSteps&lt;/code&gt; passes the video URI alongside any teacher marking notes to &lt;code&gt;generateWithResilience(generateConfig, preferredModel)&lt;/code&gt; and enforces a strict JSON schema containing &lt;code&gt;stepNumber&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;expectedEvidence&lt;/code&gt;, &lt;code&gt;gradingCriteria&lt;/code&gt;, and &lt;code&gt;points&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/extractTaskDemoSteps.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;handleExtractTaskDemoSteps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;promptGuidelines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;maxScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;model&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;=&amp;gt;&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;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getStorage&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket&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="n"&gt;name&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;isYouTube&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;demoVideoPath&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt;&lt;span class="n"&gt;youtu&lt;/span&gt;\&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;\&lt;span class="o"&gt;/|&lt;/span&gt;&lt;span class="n"&gt;youtube&lt;/span&gt;\&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;\&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt;&lt;span class="n"&gt;embed&lt;/span&gt;\&lt;span class="o"&gt;/|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;\&lt;span class="o"&gt;/|&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;\&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=|&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;\&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;+&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=|&lt;/span&gt;&lt;span class="n"&gt;live&lt;/span&gt;\&lt;span class="o"&gt;/&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="no"&gt;Z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9_&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;11&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="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resolvedVideoUri&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;demoVideoPath&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;isYouTube&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://'&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="n"&gt;resolvedVideoUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'gs://'&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="n"&gt;resolvedVideoUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;demoVideoPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;resolvedVideoUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`gs://${bucketName}/${demoVideoPath.replace(/^\/+/, '')}`&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'gemini-3.8-flash'&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;promptParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metaPrompt&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolvedVideoUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;promptParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resolvedVideoUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'video/mp4'&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="p"&gt;;&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;generateConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptParts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;responseMimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt;&lt;span class="p"&gt;)&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;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cleanJsonFence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&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="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sb"&gt;`Milestone ${idx + 1}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;expectedEvidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expectedEvidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;gradingCriteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gradingCriteria&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;20&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="p"&gt;)&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="k"&gt;return&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;calculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;preferredModel&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="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;Here is the &lt;code&gt;metaPrompt&lt;/code&gt; inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/extractTaskDemoSteps.js" rel="noopener noreferrer"&gt;&lt;code&gt;extractTaskDemoSteps.js&lt;/code&gt;&lt;/a&gt; (backed by &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/admin/prompts/rubrics/Practical%20Task%20Demo%20Step%20Extractor.md" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/rubrics/Practical Task Demo Step Extractor.md&lt;/code&gt;&lt;/a&gt;) that extracts a chronological JSON rubric from a teacher's demo video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are an expert technical educator and curriculum designer creating a structured grading rubric for a hands-on practical assignment or lab test.
Analyze the visual actions, commands, code editor steps, browser verifications, and results shown in the demonstration video.

Extract a structured, chronological step-by-step assessment rubric (between 3 and 8 distinct milestones).
For each milestone:
1. Provide a concise, clear title.
2. Describe the specific action or requirement (e.g. terminal command, file created, setting configured).
3. Specify the expected visual proof/evidence that an AI evaluator should search for in a student's screen recording (e.g. terminal output text, open ports, browser UI, green test checks).
4. Provide explicit marking / partial-credit criteria (how full marks vs. partial marks vs. 0 marks should be awarded).
5. Assign a point weight (total across all steps must equal 100 points).

You MUST respond strictly with valid JSON conforming to this schema:
{
  "title": "A concise, descriptive title for this practical task",
  "description": "A clear 2-3 sentence overview of what the student is required to reproduce",
  "suggestedMaxScore": 100,
  "steps": [
    {
      "stepNumber": 1,
      "title": "Short Step Title",
      "description": "Specific action performed by the student",
      "expectedEvidence": "Specific visual evidence, keywords, or UI elements on screen",
      "gradingCriteria": "Full marks if completed and verified; partial marks if configured with minor syntax errors",
      "points": 20
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Production Code 3: Eliminating the Post-Lesson Screenshot Deletion Race Condition with Ultrafast Stream-Copy Slicing (&lt;code&gt;slicePendingTaskSubmissions&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;A subtle race condition occurs during &lt;strong&gt;In-Class Practical Tasks&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At the end of a scheduled lesson, &lt;code&gt;processVideoJob.js&lt;/code&gt; compiles all raw screenshots from &lt;code&gt;screenshots/{classId}/{studentUid}/&lt;/code&gt; into the master class timelapse video (&lt;code&gt;videos/{classId}/{videoId}.mp4&lt;/code&gt;), and then &lt;strong&gt;deletes the raw screenshots&lt;/strong&gt; to save 80% of Cloud Storage costs.&lt;/li&gt;
&lt;li&gt;What if a student clicked &lt;strong&gt;Submit Task&lt;/strong&gt; right as class ended, or what if a teacher realized &lt;em&gt;after&lt;/em&gt; class that they forgot to create a task beforehand and wants to &lt;strong&gt;retroactively slice minutes &lt;code&gt;30:00&lt;/code&gt; to &lt;code&gt;75:00&lt;/code&gt;&lt;/strong&gt; from all 40 students' compiled class videos?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/processVideoJob.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/media_processing/processVideoJob.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;slicePendingTaskSubmissions&lt;/code&gt; solves both problems simultaneously. Instead of re-downloading raw screenshots, it maps the task's &lt;code&gt;[taskStartMs, taskEndMs]&lt;/code&gt; window (or tagged &lt;code&gt;activeTaskId&lt;/code&gt; frame indices) to exact 1-FPS video offsets (&lt;code&gt;startSec&lt;/code&gt;, &lt;code&gt;durSec&lt;/code&gt;) and slices &lt;code&gt;attempt_${attemptNumber}.mp4&lt;/code&gt; directly out of the compiled class MP4 using &lt;strong&gt;FFmpeg lossless stream-copying (&lt;code&gt;-c copy&lt;/code&gt;)&lt;/strong&gt; in less than 0.5 seconds!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/media_processing/processVideoJob.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;slicePendingTaskSubmissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;studentEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;classStartTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;captureInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;outputVideoPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;screenshots&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;retry&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Identify exact 1-FPS start frame index and duration from tagged screenshots or timestamp math&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;firstIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;screenshots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeTaskId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeTaskId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;toMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;taskStartMs&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;// ... (calculates startSec and durSec)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;taskClipPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpdir&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="sb"&gt;`${taskJobDoc.id}_clip.mp4`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputVideoPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStartTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;outputOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'-c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'copy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'-avoid_negative_ts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'make_zero'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'-movflags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+faststart'&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="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskClipPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'end'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Automatic fallback to ultrafast libx264 re-encode if keyframe boundary requires it&lt;/span&gt;
        &lt;span class="nf"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputVideoPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStartTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;startSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;outputOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'-c:v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'libx264'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-pix_fmt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yuv420p'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-preset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ultrafast'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-crf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'28'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-movflags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'+faststart'&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="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskClipPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'end'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&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;destinationPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`classes/${classId}/tasks/${taskId}/submissions/${studentUid}/attempt_${attemptNumber}.mp4`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retry&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskClipPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;destinationPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* metadata */&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="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;And if a teacher uses &lt;strong&gt;⏪ Post-Class Retroactive Slicing&lt;/strong&gt; inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt;, our retroactive slicing Cloud Function (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/extractRetroactiveTaskSubmissions.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/media_processing/extractRetroactiveTaskSubmissions.js&lt;/code&gt;&lt;/a&gt;) slices the specified &lt;code&gt;[startOffsetMinutes, durationMinutes]&lt;/code&gt; window across all 40 class videos in parallel and immediately pipelines the sliced clips into the Gemini grading queue!&lt;/p&gt;




&lt;h3&gt;
  
  
  Production Code 4: Deterministic Rubric Reconciliation &amp;amp; Mathematical Guardrails (&lt;code&gt;evaluateTaskSubmission.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Even frontier models like &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; are probabilistic token generators—we should &lt;strong&gt;never&lt;/strong&gt; trust raw LLM arithmetic for official academic gradebooks.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/evaluateTaskSubmission.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/evaluateTaskSubmission.js&lt;/code&gt;&lt;/a&gt; (mirrored on the frontend by &lt;code&gt;reconcileStepEvaluationsWithRubric&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt;), &lt;code&gt;normalizeRubricEvaluation&lt;/code&gt; acts as a deterministic post-processing barrier between Gemini's JSON output and Firestore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;1-to-1 Step Alignment&lt;/strong&gt;: It iterates over the authoritative &lt;code&gt;effectiveSteps&lt;/code&gt; array from the task definition, matching AI step results by &lt;code&gt;stepNumber&lt;/code&gt;, case-insensitive &lt;code&gt;title&lt;/code&gt;, or positional index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict &lt;code&gt;[0, maxPoints]&lt;/code&gt; Clamping&lt;/strong&gt;: Every step's &lt;code&gt;scoreAwarded&lt;/code&gt; is clamped via &lt;code&gt;Math.max(0, Math.min(maxPoints, ...))&lt;/code&gt; so the AI can never award negative points or exceed a step's point ceiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Defaulting Omitted Steps&lt;/strong&gt;: If a student stopped halfway through the lab and the LLM omitted Steps 4 and 5 from its JSON array, &lt;code&gt;normalizeRubricEvaluation&lt;/code&gt; deterministically populates Steps 4 and 5 with &lt;code&gt;scoreAwarded: 0&lt;/code&gt;, &lt;code&gt;status: 'missing'&lt;/code&gt;, and explicit feedback (&lt;code&gt;No evidence of "${stepTitle}" was observed in the submitted screen recording.&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact Bottom-Up Summation &amp;amp; Multi-Attempt Scoring Strategy&lt;/strong&gt;: &lt;code&gt;finalScore&lt;/code&gt; is computed strictly as the sum of reconciled &lt;code&gt;scoreAwarded&lt;/code&gt; values across all steps, and the student's root submission document automatically updates &lt;code&gt;bestScore&lt;/code&gt;, &lt;code&gt;latestScore&lt;/code&gt;, &lt;code&gt;averageScore&lt;/code&gt;, and &lt;code&gt;effectiveScore&lt;/code&gt; based on the task's configured scoring strategy (&lt;code&gt;highest&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt;, or &lt;code&gt;average&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, here is the strict rubric-driven evaluation prompt dispatched by &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/evaluateTaskSubmission.js" rel="noopener noreferrer"&gt;&lt;code&gt;evaluateTaskSubmission.js&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are an expert technical invigilator and automated practical exam evaluator.
Evaluate the student's continuous screen recording time-lapse video strictly against the following practical assignment rubric.

=== ASSIGNMENT DETAILS ===
Title: "{{taskData.title}}"
Overview: "{{taskData.description}}"
Total Possible Score: {{maxScore}} points

=== REQUIRED RUBRIC MILESTONES (STRICT MARK BREAKDOWN) ===
{{effectiveStepsJson}}

=== INSTRUCTIONS FOR RUBRIC-DRIVEN EVALUATION ===
1. Watch the student's screen actions chronologically from start to finish.
2. You MUST evaluate EVERY SINGLE milestone listed in REQUIRED RUBRIC MILESTONES above in exact order:
   &lt;span class="k"&gt;- &lt;/span&gt;Determine whether the step was "completed", "partial", "error", or "missing".
   &lt;span class="k"&gt;- &lt;/span&gt;Record the approximate "mm:ss" timestamp ("timestampInVideo") where the step was attempted or completed (or null if missing).
   &lt;span class="k"&gt;- &lt;/span&gt;Describe the concrete visual proof observed on the student's screen ("evidenceObserved", e.g., terminal command output, code written, browser state, or what was missing).
   &lt;span class="k"&gt;- &lt;/span&gt;Award marks ("scoreAwarded") strictly between 0 and the step's allocated "points" ("maxPoints") based on the step's "expectedEvidence" and "gradingCriteria".
   &lt;span class="k"&gt;- &lt;/span&gt;Provide a clear, detailed per-step explanation ("feedback") for the student and teacher explaining WHY those exact marks were awarded or deducted according to the rubric.
3. Highlight 2-3 specific technical strengths demonstrated by this student in the video.
4. Highlight any errors, warnings, configuration oversights, or suspicious anomalies (e.g. abrupt paste of whole codebase, unhandled exceptions).
5. Calculate "finalScore" as the exact sum of "scoreAwarded" across all rubric steps (out of {{maxScore}}).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is how &lt;code&gt;normalizeRubricEvaluation&lt;/code&gt; deterministically reconciles Gemini's JSON response against the task rubric:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: functions/ai_flows/evaluateTaskSubmission.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;normalizeRubricEvaluation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawEvaluation&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;effectiveSteps&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;maxScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;normalizedMaxScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxScore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;100&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;rawSteps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawEvaluation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stepResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;rawEvaluation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stepResults&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawEvaluation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rubricScores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;rawEvaluation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rubricScores&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="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reconciledSteps&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;effectiveSteps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;effectiveSteps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&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;defaultStepPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedMaxScore&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;effectiveSteps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;reconciledSteps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;effectiveSteps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;stepNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stepNumber&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&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;maxPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;defaultStepPoints&lt;/span&gt;&lt;span class="p"&gt;)&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;stepTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;criterion&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sb"&gt;`Step ${stepNum}`&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;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;rawSteps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;stepNum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
        &lt;span class="n"&gt;rawSteps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;stepTitle&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="nf"&gt;toLowerCase&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="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stepTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="nf"&gt;toLowerCase&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="n"&gt;rawSteps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;)&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;rawScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scoreAwarded&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;maxPoints&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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;// Strictly clamp scoreAwarded to [0, maxPoints]&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;scoreAwarded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawScore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawScore&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scoreAwarded&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxPoints&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;maxPoints&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scoreAwarded&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'partial'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'missing'&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="n"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stepNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stepTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;maxPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;scoreAwarded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;timestampInVideo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestampInVideo&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&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="n"&gt;evidenceObserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evidenceObserved&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rationale&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;explanation&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;
              &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="sb"&gt;`Completed all requirements for ${stepTitle}.`&lt;/span&gt;
              &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Incomplete or missing requirements for ${stepTitle}.`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;expectedEvidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expectedEvidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;gradingCriteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gradingCriteria&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&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="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Deterministic fallback if the AI omitted a rubric step&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stepNum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stepTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;maxPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scoreAwarded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'missing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timestampInVideo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evidenceObserved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Not observed in submission recording.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`No evidence of "${stepTitle}" was observed in the submitted screen recording.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expectedEvidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expectedEvidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;gradingCriteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rubricStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gradingCriteria&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&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="p"&gt;}&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;// Exact bottom-up summation of clamped step scores&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reconciledSteps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scoreAwarded&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&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;computedFinalScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedMaxScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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="k"&gt;return&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawEvaluation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;finalScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;computedFinalScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maxScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;normalizedMaxScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;completionPercentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computedFinalScore&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;normalizedMaxScore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;stepResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reconciledSteps&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Architectural Comparison: Choosing the Right Gemini Model for Video Intelligence &amp;amp; Rubric Grading
&lt;/h2&gt;

&lt;p&gt;Because instructors can select the AI model per job in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoLibrary.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoLibrary.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;#task-ai-model-select&lt;/code&gt;), and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;Grading AI Model&lt;/code&gt;), here is how our lecturers allocate models across different assessment workloads:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assessment Workload&lt;/th&gt;
&lt;th&gt;Recommended Model&lt;/th&gt;
&lt;th&gt;Why It Excels&lt;/th&gt;
&lt;th&gt;Configured Rate in &lt;code&gt;cost.js&lt;/code&gt; (Input / Output per 1M Tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Daily Formative Lab Practice &amp;amp; Self-Check Attempts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;🪶 Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast feedback after submission; lowest token rate for high-frequency self-checks.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;$0.30 / $2.50&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standard Weekly Coursework &amp;amp; Map-Reduce Cohort Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Balanced multimodal OCR on dense terminal logs and IDE split-panes.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;$0.75 / $3.75&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High-Stakes Practical Exams &amp;amp; Complex Multi-Step Rubric Synthesis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frontier visual reasoning across code diffs, architecture diagrams, and multi-terminal debugging.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;$2.00 / $10.00&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt;, teachers see every student's per-step pill breakdown (&lt;code&gt;S1: 30/30&lt;/code&gt;, &lt;code&gt;S2: 62/70&lt;/code&gt;) directly in the matrix table, can click any &lt;code&gt;[⏱️ 02:15]&lt;/code&gt; badge to seek the HTML5 video player to the exact frame where Gemini observed the evidence, override any individual step score or explanation with full audit trails (&lt;code&gt;isTeacherEdited: true&lt;/code&gt;), and export a multi-sheet &lt;strong&gt;Excel Gradebook (&lt;code&gt;.xlsx&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;Google Drive Archive&lt;/strong&gt; with one click.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next in Part 8?
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 8&lt;/strong&gt;, we move from individual lab screen recordings to &lt;strong&gt;face-to-face engineering capstone project consultations&lt;/strong&gt;—showing how we turned the project supervisor's smartphone into a &lt;strong&gt;Streamlined Single-Device Multimodal Supervision Studio&lt;/strong&gt; (continuous voice + zero-pause &lt;code&gt;0.1s&lt;/code&gt; photo snap + auto-sliced &lt;code&gt;720p&lt;/code&gt; mobile demo video) backed by self-healing Google Sheets weekly log sync, pre-meeting fake-log AI auditing, and a &lt;strong&gt;Two-Stage &lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; + &lt;code&gt;gemini-4-argon&lt;/code&gt; Cross-Examination Pipeline&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>video</category>
      <category>firebase</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Google AI Classroom Part 6: Low-Bandwidth Classroom Screen Broadcasting &amp; Single-Pass Whole-Lecture Subtitles with Gemini 4 Argon</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Wed, 11 Nov 2026 01:27:06 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-6-low-bandwidth-classroom-screen-broadcasting-single-pass-whole-lecture-1k0l</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-6-low-bandwidth-classroom-screen-broadcasting-single-pass-whole-lecture-1k0l</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Problem Statement: Breaking Two Hard Ceilings in Lecture Hall Broadcasting &amp;amp; Post-Lecture Publishing
&lt;/h2&gt;

&lt;p&gt;When broadcasting a live coding demonstration to a 30-to-80 student lab or up to a 600-seat multi-cohort lecture hall—and publishing the recorded 3-hour lecture with multilingual closed captions to YouTube Studio afterward—conventional web architectures hit two catastrophic walls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The WebRTC 6-Peer CPU Wall &amp;amp; Firebase Anonymous Auth 100/hr NAT IP Ceiling&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Peer-to-peer WebRTC screen sharing encodes a separate VP8/H.264 video stream per connected viewer ($O(N)$ CPU complexity), pegging the presenter's laptop CPU at 100% after just 6–8 students.&lt;/li&gt;
&lt;li&gt;Worse, when dozens of students in a lecture hall scan a live broadcast QR code while connected to the same campus Wi-Fi NAT gateway IP, calling &lt;code&gt;signInAnonymously()&lt;/code&gt; immediately triggers &lt;strong&gt;Firebase Auth's hard 100-signups-per-hour-per-IP rate limit&lt;/strong&gt; (&lt;code&gt;auth/too-many-requests&lt;/code&gt;), locking out attendees.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Chunked Speech-Preview VAD Cutoff &amp;amp; Timescale Drift Bugs&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Early speech-to-text preview models enforced a 45,000 audio token ceiling (~30 minutes) and mistook natural 10-second classroom pauses—while the lecturer writes code in VS Code or draws a diagram on the whiteboard—as end-of-speech (&lt;code&gt;finishReason: STOP&lt;/code&gt;), silently dropping the remaining 75% of the lecture's captions.&lt;/li&gt;
&lt;li&gt;Furthermore, feeding raw &lt;code&gt;MediaRecorder&lt;/code&gt; WebM Opus chunks into long-context multimodal models caused &lt;strong&gt;1.48×–1.68× timescale compression drift&lt;/strong&gt; (subtitles running faster than wall-clock video playback) and wasted &lt;strong&gt;90% of the token budget&lt;/strong&gt; on redundant video frames.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post, we walk through how we solved both problems using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;32×18 RGB Pixel-Delta Screen Broadcaster&lt;/strong&gt; (&lt;code&gt;&amp;lt;2%&lt;/code&gt; presenter CPU) paired with &lt;strong&gt;Zero-Auth Firestore PIN Security Rules&lt;/strong&gt; (&lt;code&gt;/live/:classId?pin=XXXX&lt;/code&gt;) that scale effortlessly from daily 30-student labs up to 600-student multi-cohort lecture halls.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Crash-Tolerant 1-Minute Segmented Recorder&lt;/strong&gt; backed by IndexedDB recovery and a &lt;strong&gt;Cloud Run FFmpeg Merger&lt;/strong&gt; guarded by a strict &lt;strong&gt;5-Point Pre-Deletion Verification Gate&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Single-Pass Audio-Only Whole-Lecture Subtitle &amp;amp; YouTube Studio Pipeline&lt;/strong&gt; powered by &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt; with 48kHz CBR MP3 normalization, linear timescale calibration, and a Chromium zero-duration cue guard.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/oy4Fvszjb-w" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Architectural Deep-Dive: Why This Architecture Scales ($O(1)$ vs. $O(N)$ Complexity)
&lt;/h2&gt;

&lt;p&gt;In a programming lecture, the presenter's screen (slides, IDE code, terminal output) needs crisp &lt;strong&gt;1080p / 1440p text legibility&lt;/strong&gt;, not 60 FPS motion video. And when the lecturer talks over a slide for 45 seconds without scrolling, re-uploading identical frames wastes bandwidth and compute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 1: Why Serverless Screen Broadcast Scales — WebRTC $O(N)$ CPU Collapse vs. $O(1)$ Firestore Fanout
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F99hb6nvimagtt9dy43e3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F99hb6nvimagtt9dy43e3.png" alt="Why Serverless Screen Broadcast Scales: WebRTC O(N) Collapse vs. O(1) Firestore Fanout" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why WebRTC Mesh Fails in Classrooms vs. How Our $O(1)$ Design Solves It
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The WebRTC Mesh $O(N)$ Trap&lt;/strong&gt;: In peer-to-peer WebRTC, the presenter's browser must establish an independent peer connection and video encoder pipeline for each connected student. For $N$ students, the presenter's CPU and upstream bandwidth scale as $O(N)$. On typical laptops, CPU load hits ~75% at 8 peers and locks up at 12–15 peers with dropped frames and audio glitching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our Serverless Single-Write $O(1)$ Presenter Workload&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;The teacher's browser runs an inline Web Worker timer and a 32×18 thumbnail canvas to compute pixel deltas.&lt;/li&gt;
&lt;li&gt;If the screen is static, 0 frames are uploaded. When slide or code changes, it encodes &lt;strong&gt;one single JPEG frame&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It writes to &lt;strong&gt;exactly one Firestore document&lt;/strong&gt;: &lt;code&gt;classes/{classId}/screenBroadcast/liveFrame&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The presenter's machine workload is strictly $O(1)$—taking &lt;code&gt;&amp;lt;2%&lt;/code&gt; CPU whether 5 students or 600 students are watching.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Firestore Real-Time Fanout&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Google Cloud's distributed Firestore infrastructure handles the fanout to all student &lt;code&gt;onSnapshot()&lt;/code&gt; listeners.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Auth Campus NAT Gate&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Standard Firebase Auth anonymous sign-ins share the campus Wi-Fi public IP and quickly hit the 100 signups/hr per-IP limit.&lt;/li&gt;
&lt;li&gt;We validate a 4-digit session PIN directly inside &lt;code&gt;firestore.rules&lt;/code&gt; without invoking Firebase Auth, allowing entire student cohorts to connect simultaneously without authentication throttling.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Architectural Boundary &amp;amp; Sizing&lt;/strong&gt;: This pipeline is intentionally designed for &lt;strong&gt;slide and code demonstration at 0.5–1 FPS (1 frame every 1–3s)&lt;/strong&gt; where text sharpness is paramount. It is not intended for 30 FPS video streaming. For higher education computer labs (30–80 students) and peak joint multi-cohort lecture halls (up to 600 seats), this zero-infrastructure serverless pattern delivers rock-solid stability without maintaining expensive media servers. Beyond 600 seats (e.g. multi-thousand public MOOCs or stadium keynotes), an institution would transition to CDN-backed HLS/DASH media distribution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementation Walkthrough: 32×18 RGB Pixel Diffing, Hardware &lt;code&gt;ImageCapture&lt;/code&gt;, &amp;amp; 950 KB Payload Guard
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherScreenBroadcast.js" rel="noopener noreferrer"&gt;&lt;code&gt;web-app/src/hooks/useTeacherScreenBroadcast.js&lt;/code&gt;&lt;/a&gt;, our broadcaster solves four gnarly browser compositor bugs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inline Web Worker Tick Loop&lt;/strong&gt;: When a lecturer switches focus from Chrome to VS Code or PowerPoint, Chromium throttles standard main-thread &lt;code&gt;setInterval&lt;/code&gt; timers to 1 tick per minute. We instantiate an inline &lt;code&gt;Blob&lt;/code&gt; Web Worker (&lt;code&gt;worker.postMessage({ action: 'start', interval })&lt;/code&gt;) whose ticks are completely immune to background tab throttling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1000ms &lt;code&gt;ImageCapture.grabFrame()&lt;/code&gt; Watchdog &amp;amp; GPU Texture Release&lt;/strong&gt;: We read frames directly from the hardware media track via &lt;code&gt;reusableImageCapture.grabFrame()&lt;/code&gt;. However, on Windows/macOS, &lt;code&gt;grabFrame()&lt;/code&gt; can hang indefinitely when the desktop compositor has zero damaged rectangles. We race &lt;code&gt;grabFrame()&lt;/code&gt; against a &lt;code&gt;1000ms&lt;/code&gt; timeout—falling back to an attached &lt;code&gt;1920x1080&lt;/code&gt; offscreen &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element—and always call &lt;code&gt;frameSource.close()&lt;/code&gt; in &lt;code&gt;finally&lt;/code&gt; so &lt;code&gt;ImageBitmap&lt;/code&gt; GPU textures never leak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;32×18 Downsampled RGB Delta&lt;/strong&gt;: Before encoding a high-resolution JPEG, we draw the frame onto a tiny &lt;code&gt;32×18&lt;/code&gt; canvas (&lt;code&gt;576 pixels&lt;/code&gt;) and compare the &lt;code&gt;R, G, B&lt;/code&gt; channels against &lt;code&gt;lastDiffDataRef.current&lt;/code&gt;. We only encode and upload a full-resolution frame if more than 2 pixels changed by $&amp;gt;10$ intensity units (or every &lt;code&gt;2000ms&lt;/code&gt; as a keep-alive heartbeat).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Firestore 1 MB Ceiling Guard&lt;/strong&gt;: Firestore documents have a strict &lt;code&gt;1,048,576 byte&lt;/code&gt; limit. If a complex 1440p IDE screenshot produces a Base64 JPEG string exceeding &lt;code&gt;950,000 characters&lt;/code&gt;, we dynamically step down the JPEG quality and canvas scale (&lt;code&gt;0.85×&lt;/code&gt;) before calling &lt;code&gt;setDoc&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From web-app/src/hooks/useTeacherScreenBroadcast.js&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Draw to low-res 32x18 thumbnail canvas to check RGB pixel delta&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;diffCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diffCanvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;willReadFrequently&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="n"&gt;diffCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frameSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&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;currentDiffData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diffCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getImageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hasChanged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;force&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;lastDiffDataRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hasChanged&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;lastDiffDataRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;diffPixels&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;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;currentDiffData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentDiffData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastDiffDataRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentDiffData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastDiffDataRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentDiffData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastDiffDataRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;diffPixels&lt;/span&gt;&lt;span class="o"&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;diffPixels&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;hasChanged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Heartbeat emission every 2s so terminal cursors and newly joined viewers never freeze&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hasChanged&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;frameStatsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastEmittedTime&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;hasChanged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;hasChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... [Draw to targetWidth x targetHeight captureCanvas with bicubic smoothing] ...&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentPreset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;jpegDataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;captureCanvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image/jpeg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Guard against Firestore 1MB document limit (1,048,576 bytes)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;MAX_FIRESTORE_PAYLOAD_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;950000&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;jpegDataUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;MAX_FIRESTORE_PAYLOAD_LEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;jpegDataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;captureCanvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image/jpeg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&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;jpegDataUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;MAX_FIRESTORE_PAYLOAD_LEN&lt;/span&gt;&lt;span class="p"&gt;)&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;safeCanvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'canvas'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;safeCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;safeCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;)&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;safeCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;safeCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2d'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;safeCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imageSmoothingEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;safeCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imageSmoothingQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'high'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;safeCtx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captureCanvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safeCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;safeCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;jpegDataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;safeCanvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image/jpeg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.78&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;frameSeqRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`classes/${classId}/screenBroadcast/liveFrame`&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="n"&gt;frameData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jpegDataUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;frameSeq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;targetWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;targetHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serverTimestamp&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;lastDiffDataRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentDiffData&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;h3&gt;
  
  
  Architectural Decision: Why In-Document Base64 Over Cloud Storage Bucket Links
&lt;/h3&gt;

&lt;p&gt;A frequent architectural question is: &lt;em&gt;why write Base64 image payloads directly to a single Firestore document (&lt;code&gt;liveFrame&lt;/code&gt;) instead of uploading &lt;code&gt;.jpg&lt;/code&gt; files to a Google Cloud Storage bucket and broadcasting the download link?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While Cloud Storage is the standard choice for large assets and long recordings, four critical real-world requirements led us to choose in-document Base64 for live screen broadcasts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sub-Second Real-Time Push vs. Multi-Hop HTTP Roundtrips&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;With Firestore Base64&lt;/strong&gt;: The teacher's browser pushes a single &lt;code&gt;setDoc()&lt;/code&gt; call over an existing, persistent bidirectional WebSocket/gRPC channel. All connected student listeners receive the raw image data immediately in a single push (~200–400 ms latency).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With Cloud Storage&lt;/strong&gt;: The teacher would need to (1) upload the JPEG blob via &lt;code&gt;uploadBytes()&lt;/code&gt;, (2) wait for upload confirmation, (3) write the storage URL to Firestore, and (4) every student browser would receive the metadata and then fire an &lt;em&gt;additional&lt;/em&gt; HTTP GET request to download the image from the bucket. This doubles or triples latency (~1,000–1,500 ms) and introduces network race conditions where frame #42 might finish downloading after frame #43 over student mobile Wi-Fi.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-Grained Dynamic Security Rules (Cross-Checking PINs &amp;amp; Enrollment)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;firestore.rules&lt;/code&gt;, we can dynamically cross-check whether the student is currently enrolled in &lt;code&gt;classes/{classId}&lt;/code&gt;, or if a guest's 4-digit PIN matches the active lecture session document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firebase Storage rules cannot query Firestore documents&lt;/strong&gt; (there is no &lt;code&gt;get()&lt;/code&gt; in &lt;code&gt;storage.rules&lt;/code&gt;). Storage rules have no way to verify whether a public PIN in a URL matches a dynamic session document in Firestore, nor can they verify real-time class roster membership without a dedicated backend token broker.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant, Atomic Session Revocation&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;The moment an instructor clicks &lt;strong&gt;Stop Broadcast&lt;/strong&gt;, Firestore sets &lt;code&gt;isBroadcasting: false&lt;/code&gt;. &lt;code&gt;firestore.rules&lt;/code&gt; instantly denies further reads to all listeners across the entire cohort.&lt;/li&gt;
&lt;li&gt;In contrast, Storage public URLs (&lt;code&gt;getDownloadURL&lt;/code&gt;) contain persistent bearer tokens (&lt;code&gt;?alt=media&amp;amp;token=...&lt;/code&gt;) that remain readable on Google Cloud CDN caches even after the lecture ends, creating potential privacy leaks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral Single-Document In-Place Overwrite &amp;amp; Zero Long-Term Database Storage&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never Accumulates Historical Frame Documents&lt;/strong&gt;: During a broadcast, frames are &lt;em&gt;never&lt;/em&gt; appended as a growing collection of documents. Every new frame calls &lt;code&gt;setDoc()&lt;/code&gt; on the exact same single document path (&lt;code&gt;classes/{classId}/screenBroadcast/liveFrame&lt;/code&gt;), &lt;strong&gt;completely overwriting the previous frame in-place&lt;/strong&gt;. Whether a lecture runs for 15 minutes or 3 hours, the total Firestore storage footprint is bounded to &lt;strong&gt;at most 1 frame (~150–350 KB)&lt;/strong&gt; at any moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immediate &lt;code&gt;null&lt;/code&gt; Wipe on Broadcast Stop&lt;/strong&gt;: The instant the lecturer stops sharing, &lt;code&gt;stopBroadcast()&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherScreenBroadcast.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherScreenBroadcast.js&lt;/code&gt;&lt;/a&gt; wipes &lt;code&gt;frameData&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;—leaving &lt;strong&gt;0 bytes of image data stored in Firestore long-term&lt;/strong&gt; and avoiding the tens of thousands of orphaned &lt;code&gt;.jpg&lt;/code&gt; files that sequential Cloud Storage uploads (&lt;code&gt;frame_001.jpg&lt;/code&gt;, &lt;code&gt;frame_002.jpg&lt;/code&gt;) would leave behind:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From web-app/src/hooks/useTeacherScreenBroadcast.js — Wiping frame payload on stop&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;liveFrameDocRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`classes/${classId}/screenBroadcast/liveFrame`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;liveFrameDocRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;frameData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;endedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serverTimestamp&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 FinOps &amp;amp; Cost Breakdown: Firestore Document Fanout at Classroom Scale
&lt;/h3&gt;

&lt;p&gt;Because each frame overwrites the single &lt;code&gt;liveFrame&lt;/code&gt; document in-place and is wiped to &lt;code&gt;null&lt;/code&gt; when the session ends, &lt;strong&gt;long-term Firestore disk storage cost is $0.00&lt;/strong&gt;. Runtime billing is strictly limited to &lt;strong&gt;document operations&lt;/strong&gt; ($0.18 per 100K writes, $0.06 per 100K reads in &lt;code&gt;asia-east2&lt;/code&gt;) and &lt;strong&gt;network egress&lt;/strong&gt; (~$0.12 per GiB). Here is the empirical cost model for a typical &lt;strong&gt;1-hour lecture&lt;/strong&gt; (~500 emitted frames, leveraging 32×18 pixel-delta suppression when slides are static):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload Scale&lt;/th&gt;
&lt;th&gt;Active Attendees&lt;/th&gt;
&lt;th&gt;Firestore Writes (~500 Frames)&lt;/th&gt;
&lt;th&gt;Firestore Reads&lt;/th&gt;
&lt;th&gt;Network Egress (~450 KB/frame)&lt;/th&gt;
&lt;th&gt;Total Google Cloud Cost (1-Hour Session)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical Computer Lab&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30 Students&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 writes ($0.0009)&lt;/td&gt;
&lt;td&gt;15,000 reads ($0.009)&lt;/td&gt;
&lt;td&gt;~6.75 GB (Within 10 GB Free Tier)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.01 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standard Lecture Hall&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60 Students&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 writes ($0.0009)&lt;/td&gt;
&lt;td&gt;30,000 reads ($0.018)&lt;/td&gt;
&lt;td&gt;~13.5 GB (~$0.42 after free tier)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.44 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-Cohort Joint Lecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;150 Students&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 writes ($0.0009)&lt;/td&gt;
&lt;td&gt;75,000 reads ($0.045)&lt;/td&gt;
&lt;td&gt;~33.7 GB (~$2.84)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$2.89 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peak Auditorium / Keynote&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;600 Students&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;500 writes ($0.0009)&lt;/td&gt;
&lt;td&gt;300,000 reads ($0.180)&lt;/td&gt;
&lt;td&gt;~135 GB (~$15.00)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$15.18 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Why Serverless Wins Over Dedicated Media Streaming Servers (SFU)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traditional WebRTC SFU / Media Server&lt;/strong&gt;: Hosting dedicated media servers (e.g. Janus, Mediasoup, or LiveKit clusters on 2–4 &lt;code&gt;c2-standard-4&lt;/code&gt; Cloud Compute VMs) incurs a fixed baseline cost of &lt;strong&gt;$250 – $500 USD per month&lt;/strong&gt; 24/7—even during nights, weekends, and semester breaks when no classes take place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our Serverless Firestore Pattern&lt;/strong&gt;: When no lecture is broadcasting, infrastructure costs drop strictly to &lt;strong&gt;$0.00&lt;/strong&gt;. For daily classroom teaching (30–60 students), a complete 1-hour broadcast costs &lt;strong&gt;less than half a dollar&lt;/strong&gt;, scaling up to ~$15 for an occasional 600-student symposium without provisioning a single server.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Bypassing the 100/hr NAT Ceiling with Zero-Auth Firestore PIN Rules
&lt;/h3&gt;

&lt;p&gt;To let multi-cohort student audiences join &lt;code&gt;/live/:classId?pin=XXXX&lt;/code&gt; simultaneously from the same campus Wi-Fi NAT IP—and allow speakers to share the QR code in advance—we store both &lt;code&gt;publicPin&lt;/code&gt; and &lt;code&gt;savedPublicPin&lt;/code&gt; in &lt;code&gt;classes/{classId}/screenBroadcast/session&lt;/code&gt; (&lt;code&gt;configurePublicAccess&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherScreenBroadcast.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherScreenBroadcast.js&lt;/code&gt;&lt;/a&gt;) and validate the PIN directly inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/firestore.rules" rel="noopener noreferrer"&gt;&lt;code&gt;firestore.rules&lt;/code&gt;&lt;/a&gt; with &lt;strong&gt;zero Firebase Auth calls&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From firestore.rules&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isPublicBroadcastActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sessionPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;screenBroadcast&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'isPublic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
     &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'publicPin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'savedIsPublic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
     &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'savedPublicPin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&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="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;screenBroadcast&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;docId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isPublicBroadcastActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isTeacher&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="nf"&gt;isTeacherInClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isStudentInClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isTeacher&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="nf"&gt;isTeacherInClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&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="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;screenBroadcastViewers&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;studentUid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isTeacher&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="nf"&gt;isTeacherInClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isStudentInClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&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="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;isPublicBroadcastActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;screenBroadcast&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'publicPin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;screenBroadcast&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'savedPublicPin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&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="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Crash-Tolerant 1-Minute Segmented Recording &amp;amp; The 5-Point Cloud Run FFmpeg Safety Gate
&lt;/h2&gt;

&lt;p&gt;Recording a 3-hour lecture into a single in-memory browser &lt;code&gt;Blob&lt;/code&gt; is a ticking time bomb: if the browser tab crashes or the laptop battery dies at minute 179, the entire 3-hour recording is lost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 2: Resilient 1-Minute Rolling Ingestion &amp;amp; 5-Point Pre-Deletion Safety Gate
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useLectureRecorder.js" rel="noopener noreferrer"&gt;&lt;code&gt;useLectureRecorder.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/mergeLectureRecordings.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/media_processing/mergeLectureRecordings.js&lt;/code&gt;&lt;/a&gt;, we record rolling &lt;strong&gt;1-minute self-contained &lt;code&gt;.webm&lt;/code&gt; segments&lt;/strong&gt; backed by an IndexedDB crash-recovery queue (&lt;code&gt;lectureRecoveryDb.js&lt;/code&gt;), and merge them post-lecture inside a &lt;code&gt;2 vCPU, 2GiB RAM&lt;/code&gt; Cloud Run container guarded by a &lt;strong&gt;5-Point Pre-Deletion Integrity Gate&lt;/strong&gt;:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubuahp7q95jf9dszf22y.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubuahp7q95jf9dszf22y.png" alt="Resilient 1-Minute Rolling Ingestion &amp;amp; 5-Point Cloud Run FFmpeg Safety Gate" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code: Mixed Audio Alignment, 48kHz CBR MP3 Extraction, &amp;amp; Pre-Deletion Gate
&lt;/h3&gt;

&lt;p&gt;What happens if a lecturer starts recording their screen with the microphone unplugged for the first two 1-minute clips, and plugs in their USB headset at minute 3? A standard &lt;code&gt;ffmpeg -f concat -c copy&lt;/code&gt; command will either fail or slide the audio from minute 3 backward to minute 0, ruining lip/slide sync for the entire 3-hour video!&lt;/p&gt;

&lt;p&gt;Here is how &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/media_processing/mergeLectureRecordings.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/media_processing/mergeLectureRecordings.js&lt;/code&gt;&lt;/a&gt; aligns mixed audio/no-audio clips using &lt;code&gt;lavfi anullsrc&lt;/code&gt;, extracts a normalized 48kHz CBR MP3 for Gemini, and enforces the Pre-Deletion Integrity Gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/media_processing/mergeLectureRecordings.js&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Stream alignment across clips: if some clips have audio and others do not,&lt;/span&gt;
&lt;span class="c1"&gt;// pad silent clips with a 48kHz stereo Opus track so FFmpeg concat preserves sync.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;clipsWithAudio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;downloadedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;currentAudioChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;anyHasAudio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clipsWithAudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&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;allHaveAudio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clipsWithAudio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&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;alignedFiles&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anyHasAudio&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;allHaveAudio&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ffmpegRunner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;downloadedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;downloadedFiles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;clipsWithAudio&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;paddedPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`clip_${i}_padded.webm`&lt;/span&gt;&lt;span class="p"&gt;)&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;padRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawnSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ffmpeg_static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'-i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'-f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lavfi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'anullsrc=r=48000:cl=stereo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'-c:v'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'copy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-c:a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'libopus'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-shortest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paddedPath&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="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;padRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedPath&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="n"&gt;alignedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paddedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;continue&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="n"&gt;alignedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;alignedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downloadedFiles&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;// 2. Concatenate videos via stream copy (-c copy) with monotonic DTS/PTS generation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;combinedVideoPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'combined_lecture.webm'&lt;/span&gt;&lt;span class="p"&gt;)&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;concatCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ffmpeg&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="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;concatListPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inputOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'-f concat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-safe 0'&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="nf"&gt;outputOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'-c copy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-avoid_negative_ts make_zero'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-fflags +genpts'&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="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combinedVideoPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;currentFfmpegRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;concatCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Extract normalized 48kHz Constant Bitrate MP3 for Gemini speech recognition&lt;/span&gt;
&lt;span class="c1"&gt;// Eliminates MediaRecorder WebM timestamp resets &amp;amp; prevents 1.48x subtitle drift!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;combinedMp3Path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'combined_audio_normalized.mp3'&lt;/span&gt;&lt;span class="p"&gt;)&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;audioNormalizedCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combinedVideoPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;noVideo&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="nf"&gt;audioCodec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'libmp3lame'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audioBitrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'128k'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audioChannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audioFrequency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;outputOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'-avoid_negative_ts make_zero'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-fflags +genpts'&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="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;combinedMp3Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;currentFfmpegRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audioNormalizedCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... [Uploads lecture.webm &amp;amp; lecture_audio_normalized.mp3 and commits Firestore doc] ...&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Pre-Deletion Integrity Verification Gate:&lt;/span&gt;
&lt;span class="c1"&gt;// If ANY check fails, abort immediately so intermediate 1-min segments are NEVER deleted!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;destVideoFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destVideoPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;destExists&lt;/span&gt;&lt;span class="p"&gt;]&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;destVideoFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;destExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Integrity check failed: merged video '${destVideoPath}' does not exist in Cloud Storage. Segment deletion aborted.`&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;]&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;destVideoFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;remoteSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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="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;remoteSize&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;remoteSize&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Integrity check failed: merged video is suspiciously small (${remoteSize} bytes). Segment deletion aborted.`&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationSeconds&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;videoStats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Integrity check failed: merged video has 0 duration and invalid size. Segment deletion aborted.'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Single-Pass Audio-Only Whole-Lecture Subtitles with Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Why send 1.2 GB of video frames into a multimodal LLM when closed captions and YouTube chapter markers come entirely from the lecturer's voice?&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/ClassManagement.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ClassManagement.jsx&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/LectureRecordingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;LectureRecordingsView.jsx&lt;/code&gt;&lt;/a&gt;, instructors can select their preferred lecture intelligence model—&lt;strong&gt;🧠 Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;⚡ Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;, Default)&lt;/strong&gt;, or &lt;strong&gt;🚀 Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt; (&lt;code&gt;SUPPORTED_LECTURE_AI_MODELS&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/config.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/config.js&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 3: Two-Stage Whole-Lecture Audio Pipeline, Timescale Calibration &amp;amp; YouTube Studio Packaging
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy68gs1vb6fn0br7fbtv8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy68gs1vb6fn0br7fbtv8.png" alt="Single-Pass Whole-Lecture Audio Subtitles &amp;amp; YouTube Studio Packaging with Gemini 4 Argon" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why We Split Whole-Lecture Subtitling into Stage 1 (Single-Pass Audio STT) + Stage 2 (Batched Translation)
&lt;/h3&gt;

&lt;p&gt;When we initially asked the model to transcribe a 2-hour lecture &lt;em&gt;and&lt;/em&gt; translate every sentence inline into 4 languages within a single JSON response, the output exceeded &lt;code&gt;65,536 completion tokens&lt;/code&gt; (&lt;code&gt;maxOutputTokens&lt;/code&gt;) around minute 70, truncating the JSON!&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/processLectureSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/processLectureSubtitles.js&lt;/code&gt;&lt;/a&gt;, we architected a &lt;strong&gt;Two-Stage Pipeline&lt;/strong&gt; that handles up to &lt;strong&gt;9-hour audio recordings&lt;/strong&gt; without ever hitting the 65,536 output token ceiling:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stage 1 — Single-Pass Whole-Audio Verbatim Transcription &amp;amp; Chaptering&lt;/strong&gt;: We pass the normalized 48kHz CBR MP3 (&lt;code&gt;gs://.../lecture_audio_normalized.mp3&lt;/code&gt;) to &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; with &lt;code&gt;maxOutputTokens: 65536&lt;/code&gt; and &lt;code&gt;thinkingConfig: { thinkingBudget: 0 }&lt;/code&gt;. Because Stage 1 outputs &lt;em&gt;only&lt;/em&gt; the &lt;code&gt;original&lt;/code&gt; spoken cues and 4–10 YouTube chapter milestones, a full 3-hour lecture takes only ~18,000 output tokens and never cuts off on whiteboard pauses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage 2 — Controlled-Concurrency Batched Translation&lt;/strong&gt;: We slice the transcribed segments into batches of &lt;code&gt;50 sentences&lt;/code&gt; (&lt;code&gt;BATCH_SIZE = 50&lt;/code&gt;, &lt;code&gt;CONCURRENCY = 3&lt;/code&gt;) and translate each target language independently via &lt;code&gt;buildLectureTranslationPrompt&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Solving Three Production Media &amp;amp; Subtitle Failure Modes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Fixing Long-Audio Timescale Drift (&lt;code&gt;normalizeLectureAudio&lt;/code&gt; &amp;amp; &lt;code&gt;calibrateSubtitleTimeline&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;When Gemini processes long raw WebM Opus recordings in a single pass, WebM timestamp resets and internal audio framing cause the model's emitted timestamps to compress linearly (running ~1.48× to 1.68× faster than wall-clock video playback).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: First, &lt;code&gt;normalizeLectureAudio&lt;/code&gt; transcodes the audio track into a constant-bitrate 48kHz MP3 (&lt;code&gt;-c:a libmp3lame -b:a 128k -ar 48000&lt;/code&gt;). Second, &lt;code&gt;calibrateSubtitleTimeline&lt;/code&gt; compares the highest segment timestamp (&lt;code&gt;rawMaxEnd&lt;/code&gt;) against the FFmpeg-probed &lt;code&gt;totalDuration&lt;/code&gt;; if &lt;code&gt;driftRatio &amp;gt; 1.05&lt;/code&gt;, it linearly stretches all segment &lt;code&gt;start&lt;/code&gt;/&lt;code&gt;end&lt;/code&gt; timestamps and chapter offsets into 1:1 synchronization with the video player:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/ai_flows/processLectureSubtitles.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calibrateSubtitleTimeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;chapters&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;totalDuration&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="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="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chapters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;driftRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;rawMaxEnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&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="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;rawMaxEnd&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;rawMaxEnd&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="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chapters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;driftRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;driftRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;totalDuration&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;rawMaxEnd&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;driftRatio&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;1.05&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="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chapters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;driftRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;calibratedSegments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;driftRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;driftRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&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;calibratedChapters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chapters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;chapters&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalDuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeSeconds&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;driftRatio&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="p"&gt;}&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calibratedSegments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chapters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calibratedChapters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;driftRatio&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. The Chromium &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; Zero-Duration Cue Drop Guard (&lt;code&gt;buildWebVTT&lt;/code&gt; &amp;amp; &lt;code&gt;buildSRT&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Occasionally, an LLM emits a subtitle cue where &lt;code&gt;end == start&lt;/code&gt; (e.g., &lt;code&gt;start: 142.5, end: 142.5&lt;/code&gt;). Both Chromium's native HTML5 &lt;code&gt;&amp;lt;track&amp;gt;&lt;/code&gt; parser and YouTube Studio's &lt;code&gt;.srt&lt;/code&gt; ingestion engine silently discard any cue where &lt;code&gt;end &amp;lt;= start&lt;/code&gt;!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: In &lt;code&gt;buildWebVTT&lt;/code&gt; and &lt;code&gt;buildSRT&lt;/code&gt;, we enforce a strict duration floor (&lt;code&gt;end &amp;gt; start&lt;/code&gt;) proportional to the character length of the cue (&lt;code&gt;Math.max(1.8, Math.min(5.0, text.length * 0.25))&lt;/code&gt;), clamped to the start of the next segment:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/ai_flows/processLectureSubtitles.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildWebVTT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;languageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'original'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vtt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WEBVTT\n\n'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;languageKey&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'original'&lt;/span&gt;
        &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;languageKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;text&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&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="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&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="c1"&gt;// Guard against 0 or negative duration cues that Chromium &amp;amp; YouTube discard:&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;end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&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;nextSeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&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;nextStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nextSeg&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextSeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Infinity&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;minDisplayDur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&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="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextStart&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;nextStart&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;minDisplayDur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;minDisplayDur&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="n"&gt;vtt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sb"&gt;`${idx + 1}\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;vtt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sb"&gt;`${formatTimestampToVTT(start)} --&amp;gt; ${formatTimestampToVTT(end)}\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;vtt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sb"&gt;`${text}\n\n`&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="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vtt&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;h4&gt;
  
  
  3. Fixing Unseekable &lt;code&gt;MediaRecorder&lt;/code&gt; WebM Videos (&lt;code&gt;ensureIndexedLectureVideo&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Files streamed directly from browser &lt;code&gt;MediaRecorder&lt;/code&gt; lack an EBML &lt;code&gt;SeekHead&lt;/code&gt;, &lt;code&gt;Cues&lt;/code&gt; index table, and container &lt;code&gt;Duration&lt;/code&gt; header—causing HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; players to display &lt;code&gt;0:00 / Infinity&lt;/code&gt; and refuse to seek when a student clicks a chapter timestamp.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: &lt;code&gt;ensureIndexedLectureVideo&lt;/code&gt; runs a fast, zero-transcoding FFmpeg container remux (&lt;code&gt;ffmpeg -y -i raw.webm -c copy indexed.webm&lt;/code&gt;) and stamps &lt;code&gt;hasCuesIndex: 'true'&lt;/code&gt; into the Cloud Storage metadata so every lecture video seeks instantaneously.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Architectural Comparison: Legacy Video Broadcasting vs. Pixel-Delta &amp;amp; Audio-First Lecture Studio
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Architecture / Pipeline Stage&lt;/th&gt;
&lt;th&gt;Traditional WebRTC / Raw Video Approach&lt;/th&gt;
&lt;th&gt;Our Gemini 4 / 3.8 Lecture Studio Architecture&lt;/th&gt;
&lt;th&gt;Architectural Advantage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live Screen Broadcast Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Peer-to-peer WebRTC video encoding per viewer&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32×18 RGB Pixel-Delta + Firestore Single-Doc Fanout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Constant presenter workload regardless of viewer count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Campus Wi-Fi Student Onboarding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocked at 100 users/hr by Anonymous Auth NAT IP limit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero-Auth PIN Rules (&lt;code&gt;/live/:classId?pin=XXXX&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bypasses shared campus NAT IP throttling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lecture Segment Merging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full video transcoding on every merge&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero-Transcode &lt;code&gt;ffmpeg -f concat -c copy&lt;/code&gt; + 5-Point Safety Gate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stream-copy concat without re-encoding video frames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multimodal Input Payload to Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uploading full composite MP4/WebM video frames&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Extracted 48kHz CBR MP3 Audio (&lt;code&gt;normalizeLectureAudio&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eliminates redundant video frame tokens for speech STT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Whole-Lecture Subtitle &amp;amp; Chapter Generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single monolithic prompt with video&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Stage 1 Audio-Only STT + Stage 2 Batched Translation (50 cues/batch)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prevents output token truncation on multi-hour lectures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Drive Archival&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proxying video buffers through Cloud Functions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Direct Browser $\rightarrow$ Google Drive OAuth Resumable Upload&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00 GCP Cloud Function Egress&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  6. Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Broadcast Slides &amp;amp; Code via Pixel-Delta Frames, Not Mesh WebRTC&lt;/strong&gt;: A &lt;code&gt;32×18&lt;/code&gt; RGB pixel-diffing canvas driven by an inline Web Worker timer and gated by Zero-Auth Firestore PIN rules scales 1080p/1440p classroom screen broadcasting across multi-seat cohorts at &lt;code&gt;&amp;lt;2%&lt;/code&gt; presenter CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Delete Raw Segments Without a Multi-Point Verification Gate&lt;/strong&gt;: Recording in 1-minute rolling segments with IndexedDB recovery protects against browser crashes, while our 5-point Cloud Run FFmpeg check ensures intermediate clips are never purged unless the merged master video is verified healthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send Pure Normalized 48kHz CBR MP3 to Gemini, Never Video Frames&lt;/strong&gt;: Stripping video frames saves 90% of multimodal input tokens, while normalizing to 48kHz Constant Bitrate MP3 (&lt;code&gt;normalizeLectureAudio&lt;/code&gt;) and running &lt;code&gt;calibrateSubtitleTimeline&lt;/code&gt; eliminates the 1.48×–1.68× Opus timescale compression drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple Verbatim Audio STT from Batched Multilingual Translation&lt;/strong&gt;: Using &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; or &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; (&lt;code&gt;thinkingBudget: 0&lt;/code&gt;, &lt;code&gt;maxOutputTokens: 65536&lt;/code&gt;) for single-pass verbatim audio transcription + chaptering first, followed by 50-sentence batched translation and a zero-duration cue guard (&lt;code&gt;end &amp;gt; start&lt;/code&gt;), guarantees 100% complete &lt;code&gt;.vtt&lt;/code&gt; and YouTube Studio &lt;code&gt;.srt&lt;/code&gt; packages for lectures up to 9 hours long.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>googlecloud</category>
      <category>serverless</category>
      <category>gemini</category>
      <category>firebase</category>
    </item>
    <item>
      <title>Why Trucks Keep Hitting 11-Foot-8 Bridges: How I Built BridgeSpan AI with Gemma 4 &amp; Google ADK to Stop Them</title>
      <dc:creator>Robina Mirbahar</dc:creator>
      <pubDate>Tue, 10 Nov 2026 21:15:12 +0000</pubDate>
      <link>https://dev.to/gde/why-trucks-keep-hitting-11-foot-8-bridges-how-i-built-bridgespan-ai-with-gemma-4-google-adk-to-18bi</link>
      <guid>https://dev.to/gde/why-trucks-keep-hitting-11-foot-8-bridges-how-i-built-bridgespan-ai-with-gemma-4-google-adk-to-18bi</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Robina Mirbahar&lt;br&gt;&lt;br&gt;
&lt;em&gt;AI/ML Google Developer Expert (GDE) | Google Cloud Champion Innovator | WTM Ambassador&lt;/em&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Mobile-Responsive App:&lt;/strong&gt; &lt;a href="https://bridgespan-ai-mvhoucpfp2n5o25dewb2hi.streamlit.app/" rel="noopener noreferrer"&gt;bridgespan-ai-mvhoucpfp2n5o25dewb2hi.streamlit.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Open-Source Repo:&lt;/strong&gt; &lt;a href="https://github.com/RobinaMirbahar/bridgespan-ai" rel="noopener noreferrer"&gt;github.com/RobinaMirbahar/bridgespan-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle Open Dataset (4,809 Bridges):&lt;/strong&gt; &lt;a href="https://www.kaggle.com/datasets/robinamirbahar/bridgespan-global-bridge-strike-load-scour" rel="noopener noreferrer"&gt;BridgeSpan Bridge Strike, Load &amp;amp; Scour Dataset&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companion Kaggle Starter Notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/robinamirbahar/bridgespan-ai-2024-fhwa-nbi-scour-starter" rel="noopener noreferrer"&gt;BridgeSpan AI | 2024 FHWA NBI &amp;amp; Scour Starter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Everyone on the internet has watched viral videos of box trucks and semi-trailers getting their roofs peeled back like sardine cans by Durham's notorious &lt;strong&gt;11-Foot-8+8 ("Can Opener")&lt;/strong&gt; rail trestle, Boston's &lt;strong&gt;Storrow Drive&lt;/strong&gt;, or Melbourne's &lt;strong&gt;Montague Street 3.0 m overpass&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Behind the memes sits a massive, expensive civil-engineering failure mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the US and UK alone, &lt;strong&gt;thousands of low-clearance bridge strikes&lt;/strong&gt; occur every year, halting commuter rail corridors above and costing millions in steel girder repairs.&lt;/li&gt;
&lt;li&gt;Far less visible on camera, &lt;strong&gt;underwater hydraulic pier scour&lt;/strong&gt; during flash-flood surges is the &lt;strong&gt;#1 cause of highway bridge collapse in the United States&lt;/strong&gt;, accounting for over 50% of structural failures tracked in the &lt;a href="https://www.fhwa.dot.gov/bridge/nbi.cfm" rel="noopener noreferrer"&gt;US DOT FHWA National Bridge Inventory (Item 113)&lt;/a&gt; and &lt;a href="https://www.fhwa.dot.gov/engineering/hydraulics/library_arc.cfm?pub_number=17" rel="noopener noreferrer"&gt;HEC-18 Evaluating Scour at Bridges&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Meanwhile, heavy mobile cranes (&lt;code&gt;42 t&lt;/code&gt;) and loaded semi-trailers (&lt;code&gt;34.5 t&lt;/code&gt;) regularly cross historic trusses or stone-arch parkways rated for &lt;code&gt;15 t&lt;/code&gt; to &lt;code&gt;22.5 t&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ask a standard LLM navigation chatbot &lt;em&gt;"Can my 13'-6\" truck clear that 3.89 m overpass?"&lt;/em&gt; or &lt;em&gt;"Is Chain Bridge safe right now?"&lt;/em&gt;, a single unit-conversion hallucination or ignored river-gauge surge turns into a physical catastrophe.&lt;/p&gt;

&lt;p&gt;To solve this properly, I built and open-sourced &lt;strong&gt;BridgeSpan AI&lt;/strong&gt;, a mobile-responsive global fleet and infrastructure safety platform powered by &lt;a href="https://ai.google.dev/gemma/docs" rel="noopener noreferrer"&gt;Google Gemma 4 (open weights)&lt;/a&gt;, the &lt;a href="https://google.github.io/adk-docs/" rel="noopener noreferrer"&gt;Google Agent Development Kit (ADK)&lt;/a&gt;, &lt;a href="https://project-osrm.org/docs/v5.24.0/api/" rel="noopener noreferrer"&gt;OSRM heavy-truck routing&lt;/a&gt;, and a &lt;strong&gt;4,809-span real-world dataset&lt;/strong&gt; assembled from the &lt;a href="https://www.fhwa.dot.gov/bridge/nbi/ascii.cfm" rel="noopener noreferrer"&gt;2024 FHWA National Bridge Inventory&lt;/a&gt;, &lt;a href="https://wiki.openstreetmap.org/wiki/Overpass_API" rel="noopener noreferrer"&gt;OpenStreetMap Overpass API&lt;/a&gt;, &lt;a href="https://waterservices.usgs.gov/" rel="noopener noreferrer"&gt;USGS Water Services&lt;/a&gt;, and &lt;a href="https://global-flood.emergency.copernicus.eu/" rel="noopener noreferrer"&gt;Copernicus GloFAS&lt;/a&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcw2ecude790pqhqpplbg.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcw2ecude790pqhqpplbg.jpg" alt="BridgeSpan AI Live Mobile-Responsive Console" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Raw LLMs Fail on Physical Bridge Safety
&lt;/h2&gt;

&lt;p&gt;When I tested unguarded prompts on real bridge-dispatch scenarios, three failure patterns appeared immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Imperial (&lt;code&gt;ft-in&lt;/code&gt;) vs. Metric (&lt;code&gt;m&lt;/code&gt;) Arithmetic Traps:&lt;/strong&gt; North American trailers declare height in feet and inches (&lt;code&gt;13'-6"&lt;/code&gt; = &lt;code&gt;4.115 m&lt;/code&gt;), while urban underpasses in Europe, Australia, Azerbaijan, and Pakistan post signs in meters (&lt;code&gt;3.95 m&lt;/code&gt; or &lt;code&gt;3.0 m&lt;/code&gt;). Safe civil clearance also requires a mandatory &lt;strong&gt;&lt;code&gt;0.15 m&lt;/code&gt; (&lt;code&gt;6 in&lt;/code&gt;) dynamic suspension bounce and asphalt-overlay buffer&lt;/strong&gt;. An LLM that compares &lt;code&gt;4.11 m&lt;/code&gt; against &lt;code&gt;4.15 m&lt;/code&gt; without the bounce buffer approves a roof strike at highway speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent Structural Overload:&lt;/strong&gt; A &lt;code&gt;12'-0"&lt;/code&gt; mobile crane (&lt;code&gt;42.0 metric tons&lt;/code&gt;) easily clears a &lt;code&gt;14'-2"&lt;/code&gt; historic steel truss vertically, so height-only checks wave it through even when the span's &lt;strong&gt;FHWA Item 064 Operating Rating&lt;/strong&gt; is capped at &lt;code&gt;22.5 metric tons&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blindness to Live River Pier Scour:&lt;/strong&gt; During heavy rain or monsoon surges, a bridge deck looks dry and unobstructed on GPS maps while fast-moving water erodes riverbed sediment from beneath &lt;strong&gt;FHWA Item 113 scour-critical&lt;/strong&gt; pier footings.&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  System Architecture: Gemma 4 + Deterministic 4-Gate Civil Interlocks
&lt;/h2&gt;

&lt;p&gt;In safety-critical physical engineering, &lt;strong&gt;an LLM should orchestrate tools and explain telemetry, never hold unchecked authority over structural limits.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────┐
│      BridgeSpan AI Mobile-Responsive Fleet Safety Command UI         │
│   8 Global Corridors (US • AU • AZ • PK) + Dark Leaflet OSRM Map     │
└──────────────────────────────────┬───────────────────────────────────┘
                                   │
                                   ▼
┌──────────────────────────────────────────────────────────────────────┐
│            Google ADK LlmAgent (Gemma 4 via LiteLLM)                 │
│  1. get_bridge_structural_profile() -&amp;gt; Clearance, Item 064, Item 113 │
│  2. get_live_river_scour_and_wind() -&amp;gt; USGS 15-min + GloFAS Gusts    │
│  3. compute_safe_bridge_dispatch()  -&amp;gt; Hard Civil Interlock Engine   │
└──────────────────────────────────┬───────────────────────────────────┘
                                   │
                                   ▼
┌──────────────────────────────────────────────────────────────────────┐
│          4-Gate Deterministic Civil Safety Interlocks (Python)       │
│  • GATE 1 (Clearance): parse_height_m + 0.15m bounce &amp;gt; posted_m      │
│  • GATE 2 (Axle Mass): gross_tons &amp;gt; FHWA Item 064 operating_tons     │
│  • GATE 3 (Pier Scour): Item 113 critical &amp;amp; surge &amp;gt;= +1.80m -&amp;gt; LOCK  │
│  • GATE 4 (Crosswind): high-sided &amp;amp; gust &amp;gt;= 65 km/h -&amp;gt; CLAMP 50km/h  │
└──────────────────────────────────┬───────────────────────────────────┘
                                   │
                                   ▼
┌──────────────────────────────────────────────────────────────────────┐
│     Live Overhead Highway LED Gantry Sign + Safe OSRM Truck Bypass   │
└──────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. Killing the #1 Cause of Bridge Strikes: Deterministic &lt;code&gt;ft-in&lt;/code&gt; to &lt;code&gt;m&lt;/code&gt; Parsing
&lt;/h2&gt;

&lt;p&gt;Instead of letting an LLM convert &lt;code&gt;"13'-6\""&lt;/code&gt; or &lt;code&gt;"12'4\""&lt;/code&gt; in token space, &lt;code&gt;bridgespan_agent/guardrails.py&lt;/code&gt; parses every Imperial and Metric notation deterministically and adds the &lt;code&gt;0.15 m&lt;/code&gt; (&lt;code&gt;6 in&lt;/code&gt;) dynamic suspension bounce margin before evaluating clearance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="no"&gt;DYNAMIC_BOUNCE_BUFFER_M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;  &lt;span class="c1"&gt;# Mandatory 6-inch suspension bounce &amp;amp; repaving buffer&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_height_to_meters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"""Deterministically parse '13'-6\"', '13ft 6in', '3.89 m', or '389 cm' into meters."""&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_height&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;imp_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"^\s*(\d+(?:\.\d+)?)\s*(?:'|ft|feet|foot)\s*[-\s]*(\d+(?:\.\d+)?)?\s*(?:\"|''|in|inch|inches)?\s*$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&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="n"&gt;imp_match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;feet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imp_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;inches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imp_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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="n"&gt;imp_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inches&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Invalid inches ({inches}) in {raw_height!r}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feet&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;12.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;inches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.0254&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Metric meters / centimeters handling ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a driver declares &lt;code&gt;13'-6"&lt;/code&gt; (&lt;code&gt;4.115 m&lt;/code&gt;) approaching Durham's Gregson Street trestle (&lt;code&gt;12'-4"&lt;/code&gt; / &lt;code&gt;3.76 m&lt;/code&gt;), the guardrail computes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Effective Margin = 3.76 m - (4.115 m + 0.15 m bounce) = -0.505 m (STRIKE PREVENTED)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gate 1 trips immediately (&lt;code&gt;DIVERT_OVERHEIGHT_STRIKE_PREVENTED&lt;/code&gt;), updates the overhead LED highway gantry sign (&lt;code&gt;OVERHEIGHT 13'-6" (4.12m) -&amp;gt; DIVERT RIGHT NOW&lt;/code&gt;), and reroutes the truck onto the verified &lt;strong&gt;OSRM high-clearance arterial bypass&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Enforcing Axle Mass, Pier Flood-Scour &amp;amp; Crosswind Rollover Clamps
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;evaluate_bridge_interlocks()&lt;/code&gt;, all four civil-engineering invariants execute in strict safety priority order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scour_tripped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# Item 113 scour-critical pier + &amp;gt;= 1.80 m river stage surge&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CLOSE_BRIDGE_SCOUR_INTERLOCK"&lt;/span&gt;
    &lt;span class="n"&gt;use_detour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;height_tripped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="c1"&gt;# Vehicle height + 0.15 m bounce &amp;gt; posted clearance&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DIVERT_OVERHEIGHT_STRIKE_PREVENTED"&lt;/span&gt;
    &lt;span class="n"&gt;use_detour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;weight_tripped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="c1"&gt;# Gross vehicle tons &amp;gt; FHWA Item 064 operating rating&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DIVERT_OVERWEIGHT_LOAD_LIMIT"&lt;/span&gt;
    &lt;span class="n"&gt;use_detour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;wind_tripped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="c1"&gt;# High-sided trailer + &amp;gt;= 65 km/h deck gust (e.g., Baku Khazri)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PROCEED_CLAMP_SPEED_50KMH"&lt;/span&gt;
    &lt;span class="n"&gt;use_detour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;approved_speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CLEAR_PRIMARY_ROUTE"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also attached a &lt;a href="https://google.github.io/adk-docs/callbacks/" rel="noopener noreferrer"&gt;Google ADK &lt;code&gt;before_tool_callback&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;bridge_adk_before_tool_guardrail&lt;/code&gt;) directly to the &lt;code&gt;LlmAgent&lt;/code&gt; so out-of-bounds tool arguments are sanitized before tool execution, and every final response is validated against the &lt;a href="https://docs.pydantic.dev/latest/" rel="noopener noreferrer"&gt;Pydantic v2&lt;/a&gt; &lt;code&gt;BridgeDispatchAdvisory&lt;/code&gt; schema.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Freight Decarbonization Impact (Carbone 4 &amp;amp; Sustainable Logistics Alignment)
&lt;/h2&gt;

&lt;p&gt;Preventing bridge strikes and unscheduled corridor shutdowns is also a measurable freight-emissions win. As documented in heavy-transport decarbonization frameworks by &lt;a href="https://www.carbone4.com/en" rel="noopener noreferrer"&gt;Carbone 4 (Freight &amp;amp; Heavy Mobility Decarbonization)&lt;/a&gt; and the &lt;a href="https://www.smartfreightcentre.org/en/how-to-implement-items/what-is-glec-framework/" rel="noopener noreferrer"&gt;Smart Freight Centre GLEC Framework (ISO 14083)&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single low-clearance bridge strike triggers &lt;strong&gt;multi-hour highway and rail gridlock&lt;/strong&gt;, forcing hundreds of diesel heavy-duty vehicles (&lt;code&gt;~0.85 to 1.10 kg CO2e per vehicle-km&lt;/code&gt;) into stop-and-go idling.&lt;/li&gt;
&lt;li&gt;Pre-trip deterministic OSRM routing onto a verified &lt;code&gt;+1.07 km&lt;/code&gt; high-clearance arterial bypass eliminates catastrophic emergency backtracks (&lt;code&gt;15+ km&lt;/code&gt; urban reversals), cranes, and steel girder replacement embodied carbon.&lt;/li&gt;
&lt;li&gt;Pairing &lt;strong&gt;scale-to-zero serverless compute&lt;/strong&gt; on &lt;a href="https://cloud.google.com/run/docs" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt; with compact &lt;strong&gt;open-weights Gemma 4&lt;/strong&gt; models minimizes cloud inference energy overhead when no active dispatch queries are running.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Assembling a 100% Real Global Dataset (Zero Synthetic Toy Rows)
&lt;/h2&gt;

&lt;p&gt;Rather than inventing fake bridges, I built an automated multi-source ingestion pipeline (&lt;code&gt;collect_bridgespan_dataset.py&lt;/code&gt;) pulling from &lt;strong&gt;5 authoritative public APIs&lt;/strong&gt; and published both the dataset and a runnable starter notebook on Kaggle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/datasets/robinamirbahar/bridgespan-global-bridge-strike-load-scour" rel="noopener noreferrer"&gt;BridgeSpan Bridge Strike, Load &amp;amp; Scour Dataset&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starter Notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/robinamirbahar/bridgespan-ai-2024-fhwa-nbi-scour-starter" rel="noopener noreferrer"&gt;BridgeSpan AI | 2024 FHWA NBI &amp;amp; Scour Starter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Records&lt;/th&gt;
&lt;th&gt;Real Public Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fhwa_nbi_2024_hazard_bridges.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Official 2024 US DOT &lt;strong&gt;FHWA National Bridge Inventory&lt;/strong&gt; (&lt;code&gt;NC&lt;/code&gt;, &lt;code&gt;NY&lt;/code&gt;, &lt;code&gt;MA&lt;/code&gt;, &lt;code&gt;PA&lt;/code&gt;, &lt;code&gt;DC&lt;/code&gt;, &lt;code&gt;MD&lt;/code&gt;) with exact clearances, tonnage ratings &amp;amp; Item 113 scour codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;osm_global_low_clearance_bridges.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;309&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;OpenStreetMap Overpass API&lt;/strong&gt; low-clearance spans across the US, UK, Australia, Pakistan, and Azerbaijan with verbatim &lt;code&gt;maxheight&lt;/code&gt; tags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;usgs_live_river_scour_telemetry.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,436&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;USGS NWIS&lt;/strong&gt; 15-minute instantaneous river stage (&lt;code&gt;00065&lt;/code&gt;) &amp;amp; discharge (&lt;code&gt;00060&lt;/code&gt;) time series&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;global_glofas_flood_and_wind_daily.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;360&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Copernicus GloFAS&lt;/strong&gt; river discharge + &lt;strong&gt;Open-Meteo&lt;/strong&gt; 10m crosswind gusts &amp;amp; rainfall across 8 global corridors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bridgespan_dispatch_benchmark_80.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verified 80-scenario heavy-truck benchmark with real &lt;strong&gt;OSRM&lt;/strong&gt; primary vs. safe bypass distances (&lt;code&gt;8/8&lt;/code&gt; &lt;code&gt;pytest&lt;/code&gt; suite verifies 100% agreement)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Designing a Mobile-Responsive Fleet Safety UI
&lt;/h2&gt;

&lt;p&gt;Commercial dispatchers work on wide desktop monitors, while field supervisors and truck drivers check routes on phones. Taking inspiration from modern fleet telematics platforms, I designed &lt;code&gt;app.py&lt;/code&gt; to be intuitive and mobile-responsive out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1-Tap Scenario Presets &amp;amp; Clean Vehicle Profiles:&lt;/strong&gt; Test a &lt;code&gt;13'-6"&lt;/code&gt; semi-trailer, &lt;code&gt;42 t&lt;/code&gt; mobile crane, Potomac flood-scour surge, or &lt;code&gt;78 km/h&lt;/code&gt; Baku &lt;em&gt;Khazri&lt;/em&gt; crosswind gale in a single tap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At-a-Glance Progress Meters &amp;amp; Overhead Highway LED Gantry Sign:&lt;/strong&gt; Visualizes exact clearance margin, axle load utilization, river surge, and OSRM bypass distance without raw debug JSON clutter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyless Dark OpenStreetMap + OSRM Vector Map:&lt;/strong&gt; Renders blocked bridges as dashed red lines and verified safe heavy-truck detours as bright emerald routes with zero paid map API keys required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it live right in your browser or on your phone: &lt;strong&gt;&lt;a href="https://bridgespan-ai-mvhoucpfp2n5o25dewb2hi.streamlit.app/" rel="noopener noreferrer"&gt;bridgespan-ai-mvhoucpfp2n5o25dewb2hi.streamlit.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Run Locally or Deploy Scale-to-Zero on Google Cloud Run
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/RobinaMirbahar/bridgespan-ai.git
&lt;span class="nb"&gt;cd&lt;/span&gt; bridgespan-ai
pip install -r requirements.txt

&lt;span class="c"&gt;# Verify all 80 benchmark scenarios &amp;amp; unit-conversion guardrails&lt;/span&gt;
pytest -v

&lt;span class="c"&gt;# Launch the mobile-responsive operator console&lt;/span&gt;
streamlit run app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploying to &lt;a href="https://cloud.google.com/run/docs/quickstarts/deploy-container" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt; with scale-to-zero (&lt;code&gt;$0&lt;/code&gt; idle cost) takes a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud run deploy bridgespan-ai \
  --source . \
  --region us-central1 \
  --allow-unauthenticated \
  --min-instances 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Official Documentation &amp;amp; Standards Referenced
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemma 4 Official Documentation:&lt;/strong&gt; &lt;a href="https://ai.google.dev/gemma/docs" rel="noopener noreferrer"&gt;ai.google.dev/gemma/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Agent Development Kit (ADK) Docs &amp;amp; Callbacks:&lt;/strong&gt; &lt;a href="https://google.github.io/adk-docs/" rel="noopener noreferrer"&gt;google.github.io/adk-docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Run Serverless Containers:&lt;/strong&gt; &lt;a href="https://cloud.google.com/run/docs" rel="noopener noreferrer"&gt;cloud.google.com/run/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;US DOT FHWA National Bridge Inventory (NBI) Coding Guide:&lt;/strong&gt; &lt;a href="https://www.fhwa.dot.gov/bridge/nbi.cfm" rel="noopener noreferrer"&gt;fhwa.dot.gov/bridge/nbi.cfm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FHWA HEC-18 Evaluating Scour at Bridges:&lt;/strong&gt; &lt;a href="https://www.fhwa.dot.gov/engineering/hydraulics/library_arc.cfm?pub_number=17" rel="noopener noreferrer"&gt;fhwa.dot.gov/engineering/hydraulics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USGS National Water Information System (NWIS) Instantaneous Values API:&lt;/strong&gt; &lt;a href="https://waterservices.usgs.gov/" rel="noopener noreferrer"&gt;waterservices.usgs.gov&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copernicus Emergency Management Service (GloFAS) &amp;amp; Open-Meteo Flood API:&lt;/strong&gt; &lt;a href="https://open-meteo.com/en/docs/flood-api" rel="noopener noreferrer"&gt;open-meteo.com/en/docs/flood-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenStreetMap Overpass API (&lt;code&gt;maxheight&lt;/code&gt; / &lt;code&gt;maxweight&lt;/code&gt; tags):&lt;/strong&gt; &lt;a href="https://wiki.openstreetmap.org/wiki/Key:maxheight" rel="noopener noreferrer"&gt;wiki.openstreetmap.org/wiki/Key:maxheight&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OSRM HTTP Routing Engine API:&lt;/strong&gt; &lt;a href="https://project-osrm.org/docs/v5.24.0/api/" rel="noopener noreferrer"&gt;project-osrm.org/docs/v5.24.0/api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carbone 4 Low-Carbon Freight &amp;amp; Infrastructure Resilience:&lt;/strong&gt; &lt;a href="https://www.carbone4.com/en" rel="noopener noreferrer"&gt;carbone4.com/en&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Freight Centre GLEC Framework (ISO 14083):&lt;/strong&gt; &lt;a href="https://www.smartfreightcentre.org/en/how-to-implement-items/what-is-glec-framework/" rel="noopener noreferrer"&gt;smartfreightcentre.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Explore All Project Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Web App:&lt;/strong&gt; &lt;a href="https://bridgespan-ai-mvhoucpfp2n5o25dewb2hi.streamlit.app/" rel="noopener noreferrer"&gt;bridgespan-ai-mvhoucpfp2n5o25dewb2hi.streamlit.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/RobinaMirbahar/bridgespan-ai" rel="noopener noreferrer"&gt;github.com/RobinaMirbahar/bridgespan-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle Dataset:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/datasets/robinamirbahar/bridgespan-global-bridge-strike-load-scour" rel="noopener noreferrer"&gt;kaggle.com/datasets/robinamirbahar/bridgespan-global-bridge-strike-load-scour&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle Starter Notebook:&lt;/strong&gt; &lt;a href="https://www.kaggle.com/code/robinamirbahar/bridgespan-ai-2024-fhwa-nbi-scour-starter" rel="noopener noreferrer"&gt;kaggle.com/code/robinamirbahar/bridgespan-ai-2024-fhwa-nbi-scour-starter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>googlecloud</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>Google AI Classroom Part 5: Real-Time Multilingual Live Subtitles &amp; Hong Kong Code-Switching with Gemini 4 / 3.8 &amp; On-Device Gemma 4 E2B</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Tue, 10 Nov 2026 14:25:13 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-5-real-time-multilingual-live-subtitles-hong-kong-code-switching-with-11ap</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-5-real-time-multilingual-live-subtitles-hong-kong-code-switching-with-11ap</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Problem Statement: Why Standard Speech-to-Text Breaks in Bilingual Technical Classrooms
&lt;/h2&gt;

&lt;p&gt;In bilingual university and polytechnic classrooms across Hong Kong and Asia, lecturers rarely speak monolingual formal prose. Instead, we naturally &lt;strong&gt;code-switch&lt;/strong&gt; between colloquial &lt;strong&gt;Cantonese&lt;/strong&gt;, &lt;strong&gt;English&lt;/strong&gt;, and &lt;strong&gt;domain-specific technical terminology&lt;/strong&gt; within a single breath:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"大家留意呢個 React component，如果你喺 &lt;code&gt;useEffect&lt;/code&gt; 入面唔記得 clean up 個 WebSocket listener，就會搞到 memory leak，甚至令個 Docker container OOM crash。"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we tested standard commercial speech-to-text (STT) and machine translation pipelines on live Hong Kong lectures, they failed in three ways that actively confused students:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Homophone Hallucination&lt;/strong&gt;: Generic STT engines phonetically force English programming keywords (&lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;DynamoDB&lt;/code&gt;, &lt;code&gt;idempotent&lt;/code&gt;) into nonsensical Chinese characters (e.g., turning &lt;code&gt;useState&lt;/code&gt; into &lt;em&gt;"要死"&lt;/em&gt; or &lt;code&gt;Docker&lt;/code&gt; into &lt;em&gt;"多卡"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colloquial Cantonese Calques&lt;/strong&gt;: Standard translation APIs struggle with spoken Cantonese particles and idioms (&lt;em&gt;"呢個"&lt;/em&gt;, &lt;em&gt;"點解"&lt;/em&gt;, &lt;em&gt;"搞掂"&lt;/em&gt;, &lt;em&gt;"睇下"&lt;/em&gt;), producing broken English or unnatural literal Mandarin translations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loss of Cross-Discipline Context&lt;/strong&gt;: The word &lt;em&gt;"port"&lt;/em&gt; means a TCP/UDP network port in Computer Science, a venous access port in Nursing &amp;amp; Healthcare, and a maritime harbor in Logistics. Without discipline-aware context and rolling conversational history, utterance-by-utterance translation loses pronoun referents (&lt;em&gt;"it"&lt;/em&gt;, &lt;em&gt;"this hook"&lt;/em&gt;) and domain accuracy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve this for diverse international and mainland/local student cohorts, we engineered a &lt;strong&gt;Dual-Engine Real-Time Multilingual Live Subtitle Pipeline&lt;/strong&gt; supporting &lt;strong&gt;9 simultaneous target languages&lt;/strong&gt; (&lt;code&gt;zh-Hant&lt;/code&gt;, &lt;code&gt;zh-Hans&lt;/code&gt;, &lt;code&gt;en&lt;/code&gt;, &lt;code&gt;ja&lt;/code&gt;, &lt;code&gt;ko&lt;/code&gt;, &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;, &lt;code&gt;vi&lt;/code&gt;). Instructors can switch seamlessly between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server Mode (Recommended · Cross-Platform)&lt;/strong&gt;: Powered by &lt;strong&gt;Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, or &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; via Genkit (&lt;code&gt;translateTeacherSpeech&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/subtitleFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/subtitleFlows.js&lt;/code&gt;&lt;/a&gt;) with &lt;code&gt;thinkingBudget: 0&lt;/code&gt; for sub-second multi-language structured JSON generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Mode ($0.00 Cloud Cost · Desktop WebGPU/WASM)&lt;/strong&gt;: Powered by on-device &lt;strong&gt;LiteRT-LM &lt;code&gt;Gemma 4 E2B&lt;/code&gt; (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;)&lt;/strong&gt; running inside a dedicated Web Worker (&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertGemma.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertGemma.worker.js&lt;/code&gt;&lt;/a&gt;) with automatic cloud fallback for zero dropped captions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/r1dZXo-2lU4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architectural Deep-Dive: Dual-Engine Live Subtitle Pipeline
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/subtitles/TeacherSubtitleControlModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TeacherSubtitleControlModal.jsx&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt;, instructors can toggle between &lt;strong&gt;Server Mode&lt;/strong&gt; and &lt;strong&gt;Client Mode&lt;/strong&gt;, change their spoken source language (&lt;code&gt;zh-HK&lt;/code&gt;, &lt;code&gt;en-US&lt;/code&gt;, &lt;code&gt;zh-CN&lt;/code&gt;), or add/remove target languages on the fly without restarting their microphone stream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 1: Dual-Engine Live Subtitle Architecture (Cloud Gemini 4/3.8/3.5 vs. $0.00 Edge Gemma 4 E2B)
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzq8gyef5mr27vphsjnci.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzq8gyef5mr27vphsjnci.png" alt="Dual-Engine Live Subtitle Architecture: Cloud Gemini 4/3.8/3.5 vs. Edge Gemma 4 E2B" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 How the Pipeline Captures and Translates Mixed Languages (Code-Switching)
&lt;/h3&gt;

&lt;p&gt;A common question when designing multilingual live subtitles for bilingual classrooms is: &lt;em&gt;how does the system actually handle mixed languages without breaking the ASR or the translation engine?&lt;/em&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwzbrcfmpex66gcc7e38.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftwzbrcfmpex66gcc7e38.png" alt="Code-Switching &amp;amp; Mixed-Language Live Subtitle Pipeline: Ingestion &amp;amp; LLM Translation" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our architecture splits this problem into two decoupled stages:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Ingestion: Capturing Spoken Mixed Speech (Cantonese + English Technical Terms)
&lt;/h4&gt;

&lt;p&gt;In Hong Kong and international polytechnics, teachers speak Cantonese while using English for technical concepts (&lt;code&gt;"大家留意呢個 React component，如果你喺 useEffect 入面唔記得 clean up 個 WebSocket listener..."&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary Desktop Path (Web Speech API &lt;code&gt;zh-HK&lt;/code&gt;)&lt;/strong&gt;: In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt;, Chromium's &lt;code&gt;SpeechRecognition&lt;/code&gt; is initialized with &lt;code&gt;recognition.lang = 'zh-HK'&lt;/code&gt;. Google's Hong Kong speech recognition acoustic models natively support conversational Cantonese interleaved with English technical loanwords and acronyms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio Fallback Path (LiteRT Whisper &amp;amp; Cloud Gemini)&lt;/strong&gt;: When Web Speech is unavailable (e.g. specialized browsers or VAD silence flushes), 16kHz PCM audio buffers are transcribed via on-device &lt;strong&gt;LiteRT Whisper Web Worker&lt;/strong&gt; or &lt;strong&gt;Firebase AI Logic (&lt;code&gt;transcribeAudioWithFirebaseAI&lt;/code&gt;)&lt;/strong&gt;. The prompt instructs the engine to transcribe verbatim and retain technical vocabulary rather than forcing homophone substitutions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Translation: Why LLM-Driven Translation Replaces Rule-Based MT for Code-Switching
&lt;/h4&gt;

&lt;p&gt;Traditional machine translation engines (such as standard MT APIs) fail on code-switched technical speech because they expect grammatically formal, monolingual source text. When fed mixed Cantonese-English sentences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They mistranslate colloquial Cantonese grammatical particles (&lt;em&gt;"呢個"&lt;/em&gt;, &lt;em&gt;"點解"&lt;/em&gt;, &lt;em&gt;"搞掂"&lt;/em&gt;, &lt;em&gt;"睇下"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;They attempt to literally translate programming identifiers (&lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;Docker&lt;/code&gt;, &lt;code&gt;Kubernetes&lt;/code&gt;) into Chinese characters.&lt;/li&gt;
&lt;li&gt;They lose pronoun referents (&lt;em&gt;"佢"&lt;/em&gt; / &lt;em&gt;"it"&lt;/em&gt;) across short, choppy speech bursts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To overcome this, our system forwards the raw bilingual transcript to an LLM (&lt;strong&gt;Cloud Gemini 3.5/3.8/4&lt;/strong&gt; or &lt;strong&gt;On-Device Gemma 4 E2B&lt;/strong&gt;) configured with tailored system instructions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Source Identification&lt;/strong&gt;: Tells the model that the source is &lt;code&gt;zh-HK&lt;/code&gt; containing colloquial Cantonese with English technical code-switching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preservation of Technical Terms&lt;/strong&gt;: Explicitly instructs the model to preserve programming keywords, variable names, framework identifiers, and CLI commands in their original technical English form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colloquial-to-Formal Transformation&lt;/strong&gt;: For Traditional Chinese (&lt;code&gt;zh-Hant&lt;/code&gt;), transforms spoken Cantonese grammar into standard formal written Chinese (書面語); for English (&lt;code&gt;en&lt;/code&gt;), translates the colloquial framing into fluent, idiomatic English rather than word-for-word calques.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pronoun &amp;amp; Context Resolution via Rolling History&lt;/strong&gt;: Injects the preceding 4 speech cues as read-only context (&lt;code&gt;historyText&lt;/code&gt;), allowing the LLM to understand what &lt;em&gt;"佢"&lt;/em&gt; or &lt;em&gt;"this hook"&lt;/em&gt; refers to from the previous sentence.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Implementation Walkthrough: Code-Switching Translation with Cloud Gemini &amp;amp; Edge Gemma 4 E2B
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Server Engine: &lt;code&gt;translateTeacherSpeech&lt;/code&gt; with Rolling History &amp;amp; &lt;code&gt;thinkingBudget: 0&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When translating live speech in a lecture theatre, latency is paramount: if subtitles lag more than 1.5 seconds behind the lecturer's voice, students lose synchronization with the slide pointer.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/subtitleFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/subtitleFlows.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;translateTeacherSpeech&lt;/code&gt; minimizes multi-language translation latency while preserving technical terminology by combining three techniques:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Thinking Fast Path (&lt;code&gt;thinkingConfig: { thinkingBudget: 0 }&lt;/code&gt;)&lt;/strong&gt;: Disables chain-of-thought reasoning tokens for direct lexical translation while keeping &lt;code&gt;temperature: 0.1&lt;/code&gt; for deterministic technical terminology preservation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling Conversational History (&lt;code&gt;historyText&lt;/code&gt;)&lt;/strong&gt;: Passes the lecturer's preceding 4 utterances (&lt;code&gt;historyBufferRef.current.slice(-4)&lt;/code&gt;) strictly as read-only context so the model resolves pronouns (&lt;em&gt;"if we omit it"&lt;/em&gt; $\rightarrow$ &lt;em&gt;"if we omit the dependency array"&lt;/em&gt;) without re-translating old sentences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilient Multi-Model Routing (&lt;code&gt;generateWithResilience&lt;/code&gt;)&lt;/strong&gt;: Supports &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; (default high-frequency workhorse), &lt;code&gt;gemini-3.8-flash&lt;/code&gt;, and &lt;code&gt;gemini-4-argon&lt;/code&gt; (&lt;code&gt;gemini-4-argon&lt;/code&gt;) with automatic region/model failover and per-class FinOps quota enforcement (&lt;code&gt;checkQuota&lt;/code&gt; &amp;amp; &lt;code&gt;logJob&lt;/code&gt;).
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/ai_flows/subtitleFlows.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;translateTeacherSpeech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;teacherUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;teacherEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sourceLang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'zh-HK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;targetLangs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'zh-Hant'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;historyText&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="n"&gt;model&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&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;targetModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;AI_MODEL&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;trimmedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;trimmedText&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isSubtitleNoiseOrRefusal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trimmedText&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceLang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;translations&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;targetModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;validTargets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetLangs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;targetLangs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;targetLangs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'string'&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'zh-Hant'&lt;/span&gt;&lt;span class="p"&gt;]&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;targetDesc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validTargets&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sb"&gt;`${code} (${SUPPORTED_SUBTITLE_LANGUAGES[code] || code})`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&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;domainContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="s1"&gt;'General Classroom Instruction'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;historyBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;historyText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;historyText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&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;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;historyText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="s1"&gt;''&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&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;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;historyBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`\nPreceding Speech History (for conversational context, pronoun resolution, and terminology continuity only; DO NOT translate this section):\n${lines.map((l) =&amp;gt; `&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"${l}"&lt;/span&gt;&lt;span class="sb"&gt;`).join('\n')}\n`&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`You are a real-time lecture subtitle translator for higher education.
Context / Subject Matter: ${domainContext}
Spoken Source Language: ${sourceLang} (May include colloquial Cantonese and English code-switching)
Target Language(s) to produce: ${targetDesc}
${historyBlock}
Current Speech to Translate:
"${trimmedText}"

Guidelines:
1. FULL TRANSLATION MANDATORY: Translate the complete sentence and meaning accurately and naturally into each requested target language.
2. For English ("en"): Translate all spoken Chinese/Cantonese phrases into natural fluent English. NEVER leave untranslated Chinese characters or colloquialisms in the English translation.
3. For Simplified Chinese ("zh-Hans"): Translate Cantonese colloquialisms into standard written Simplified Chinese.
4. For Traditional Chinese ("zh-Hant"): Translate into standard written Traditional Chinese (書面語).
5. Technical Terms: Preserve domain-specific technical keywords, programming syntax, standard formula names, and brand names (e.g. "useEffect", "Firestore", "Docker", "Python", "Kubernetes") in their original technical spelling without unnatural literal translations.
6. Translate ONLY the "Current Speech to Translate", using the Preceding Speech History solely to infer context, resolve pronouns, and maintain terminology continuity.
7. Return a JSON mapping with the exact target language codes as keys.`&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;translationsSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;string&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="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;string&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="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;thinkingConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;thinkingBudget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;detectedSourceLang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;string&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="nf"&gt;optional&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="n"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translationsSchema&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="p"&gt;,&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;targetModel&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... [Normalizes language keys and logs exact token usage to classes/{classId}/aiCosts] ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 Client Engine: $0.00 On-Device &lt;code&gt;Gemma 4 E2B&lt;/code&gt; in Web Worker with Streaming CacheStorage
&lt;/h3&gt;

&lt;p&gt;For instructors running 3-hour daily lab sessions on desktop workstations who want &lt;strong&gt;$0.00 cloud AI expenditure&lt;/strong&gt;, we integrated &lt;strong&gt;Gemma 4 E2B (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;)&lt;/strong&gt; via &lt;code&gt;@litert-lm/core&lt;/code&gt; inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertGemma.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;web-app/src/workers/litertGemma.worker.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When the teacher clicks &lt;strong&gt;Download &amp;amp; Load Local Gemma 4 (~1.9 GB)&lt;/strong&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/subtitles/TeacherSubtitleControlModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TeacherSubtitleControlModal.jsx&lt;/code&gt;&lt;/a&gt;, the worker streams the model weights with real-time progress reporting and caches the binary in the browser's persistent &lt;code&gt;CacheStorage&lt;/code&gt; (&lt;code&gt;webai-litert-gemma-v1&lt;/code&gt;), so subsequent lectures load from local SSD in under 2 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From web-app/src/workers/litertGemma.worker.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildGemmaTranslationPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sourceLang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'zh-HK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;targetLangs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;courseContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sourceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;langNames&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sourceLang&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;sourceLang&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;targetList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetLangs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sb"&gt;`- "${code}": ${langNames[code] || code}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;)&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;domainContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;courseContext&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="s1"&gt;'higher education and classroom instruction'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`&amp;lt;start_of_turn&amp;gt;user
You are an expert real-time lecture translation assistant for ${domainContext}.
Translate the following spoken classroom transcript from ${sourceName} into the requested target languages:
${targetList}

CRITICAL RULES:
1. FULL TRANSLATION MANDATORY: Translate the entire meaning into each requested target language. For English ("en"), translate all spoken Chinese/Cantonese phrases into fluent natural English.
2. For Simplified Chinese ("zh-Hans" / "zh-CN"), translate into standard written Simplified Chinese. For Traditional Chinese ("zh-Hant"), translate into standard written Traditional Chinese.
3. Preserve discipline-specific terminology, proper nouns, formula/variable names, and standard technical abbreviations (e.g. "useEffect", "Docker", "Python") in their original technical form.
4. Return strictly a single valid JSON object mapping each target language code to its translated text.

Spoken transcript:
"${transcript}"
&amp;lt;end_of_turn&amp;gt;
&amp;lt;start_of_turn&amp;gt;model
`&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;Crucially, in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt;, if on-device Gemma 4 E2B is still initializing, times out (&amp;gt;10s), or omits one of the requested target languages in its JSON output, &lt;code&gt;translateTranscript&lt;/code&gt; &lt;strong&gt;automatically backfills only the missing language keys&lt;/strong&gt; via the Cloud &lt;code&gt;translateTeacherSpeech&lt;/code&gt; function—guaranteeing zero dropped captions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From web-app/src/hooks/useTeacherLiveSubtitles.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;result&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;gemmaPromise&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;gemmaTranslations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;||&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;missingLangs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetLanguages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;gemmaTranslations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missingLangs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gemmaTranslations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Hybrid Partial Fallback: Only request missing languages from Cloud Gemini!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;callTranslate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpsCallable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'translateTeacherSpeech'&lt;/span&gt;&lt;span class="p"&gt;)&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callTranslate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sourceLang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;speechLanguage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;targetLangs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;missingLangs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;courseContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customPromptText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;historyText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;recentHistoryStrings&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="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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gemmaTranslations&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="o"&gt;||&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Discipline Glossary Injection &amp;amp; Zero-Restart Dynamic Prompt Propagation
&lt;/h2&gt;

&lt;p&gt;A single university deployment spans multiple faculties—from &lt;strong&gt;Computer Science &amp;amp; Software Engineering&lt;/strong&gt; to &lt;strong&gt;Healthcare, Nursing &amp;amp; Medical Sciences&lt;/strong&gt;, &lt;strong&gt;Business &amp;amp; Corporate Finance (&lt;code&gt;EBITDA&lt;/code&gt;, &lt;code&gt;IFRS&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;Electrical &amp;amp; Mechanical Engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 2: Discipline Glossary Injection &amp;amp; Zero-Restart Dynamic Prompt Propagation
&lt;/h3&gt;

&lt;p&gt;When an instructor switches the &lt;strong&gt;Subject Domain&lt;/strong&gt; or edits custom prompt instructions inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/subtitles/TeacherSubtitleControlModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TeacherSubtitleControlModal.jsx&lt;/code&gt;&lt;/a&gt; (or &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/ClassManagement.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ClassManagement.jsx&lt;/code&gt;&lt;/a&gt;), the update propagates to all active inference workers and student screens via Firestore &lt;code&gt;onSnapshot&lt;/code&gt; with &lt;strong&gt;0ms audio interruption&lt;/strong&gt;:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj5hnqo7oiy7dsw9d7lo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frj5hnqo7oiy7dsw9d7lo.png" alt="Discipline Glossary Injection &amp;amp; Zero-Restart Dynamic Prompt Propagation" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Production Prompt Engineering Gallery: Code-Switching &amp;amp; Multi-Discipline Samples
&lt;/h3&gt;

&lt;p&gt;To help educators and AI engineers adapt our subtitle pipeline to their own universities, below are the exact &lt;strong&gt;production prompt templates and assembled payloads&lt;/strong&gt; from &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/subtitleFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/subtitleFlows.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/constants/promptRegistry.js" rel="noopener noreferrer"&gt;&lt;code&gt;web-app/src/constants/promptRegistry.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/tree/main/admin/prompts/translations" rel="noopener noreferrer"&gt;&lt;code&gt;admin/prompts/translations/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sample 1: Full Assembled Server-Mode Prompt (&lt;code&gt;translateTeacherSpeech&lt;/code&gt;) with 4-Turn Rolling History &amp;amp; 9-Language JSON Output
&lt;/h4&gt;

&lt;p&gt;When a lecturer speaks in Hong Kong Cantonese mixed with English technical terms, &lt;code&gt;translateTeacherSpeech&lt;/code&gt; injects the 4 most recent utterances (&lt;code&gt;historyText&lt;/code&gt;) strictly for &lt;strong&gt;pronoun resolution&lt;/strong&gt; (&lt;em&gt;"佢"&lt;/em&gt; / &lt;em&gt;"it"&lt;/em&gt; $\rightarrow$ &lt;code&gt;useEffect cleanup function&lt;/code&gt;) and &lt;strong&gt;terminology continuity&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a real-time lecture subtitle translator for higher education.
Context / Subject Matter: Cloud Native Architecture, React Hooks &amp;amp; Container Orchestration (ITP4124)
Spoken Source Language: zh-HK (May include colloquial Cantonese and English code-switching)
Target Language(s) to produce: zh-Hant (繁體中文 (Traditional Chinese)), zh-Hans (简体中文 (Simplified Chinese)), en (English), ja (日本語 (Japanese)), ko (한국어 (Korean)), es (Español (Spanish)), fr (Français (French)), de (Deutsch (German)), vi (Tiếng Việt (Vietnamese))

Preceding Speech History (for conversational context, pronoun resolution, and terminology continuity only; DO NOT translate this section):
- "頭先我哋起咗個 Firestore onSnapshot listener 去 subscribe 個 liveSubtitles document。"
- "大家留意下，當個 student component unmount 嗰陣，個 WebSocket connection 其實仲未斷㗎。"
- "如果你喺 useEffect 入面唔記得 return 個 cleanup function 呢，"

Current Speech to Translate:
"佢就會一直食住個 browser memory，搞到 memory leak 甚至令個 container OOM crash 囉！"

Guidelines:
1. FULL TRANSLATION MANDATORY: Translate the complete sentence and meaning accurately and naturally into each requested target language.
2. For English ("en"): Translate all spoken Chinese/Cantonese phrases into natural fluent English. NEVER leave untranslated Chinese characters or colloquialisms in the English translation.
3. For Simplified Chinese ("zh-Hans"): Translate Cantonese colloquialisms into standard written Simplified Chinese.
4. For Traditional Chinese ("zh-Hant"): Translate into standard written Traditional Chinese (書面語).
5. Technical Terms: Preserve domain-specific technical keywords, programming syntax, standard formula names, and brand names (e.g. "useEffect", "Firestore", "Docker", "Python", "Kubernetes") in their original technical spelling without unnatural literal translations.
6. Translate ONLY the "Current Speech to Translate", using the Preceding Speech History solely to infer context, resolve pronouns (e.g. "it", "they", "this"), and maintain terminology continuity.
7. Return a JSON mapping with the exact target language codes as keys.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Structured JSON Response (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; / &lt;code&gt;gemini-4-argon&lt;/code&gt; with &lt;code&gt;thinkingBudget: 0&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"detectedSourceLang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"zh-HK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"translations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"zh-Hant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"它便會持續佔用瀏覽器記憶體，導致 memory leak，甚至令該 container 發生 OOM crash！"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"zh-Hans"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"它就会一直占用浏览器内存，导致 memory leak，甚至让该 container 发生 OOM crash！"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"It will keep consuming browser memory, causing a memory leak or even triggering a container OOM crash!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"ja"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ブラウザのメモリを消費し続け、memory leakを引き起こし、さらにはcontainerのOOM crashを招いてしまいます！"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"ko"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"브라우저 메모리를 계속 점유하여 memory leak을 유발하고, 심지어 container OOM crash까지 발생시킬 수 있습니다!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"es"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"¡Seguirá consumiendo memoria del navegador, provocando un memory leak o incluso un OOM crash del container!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"fr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Il continuera à consommer la mémoire du navigateur, provoquant un memory leak voire un OOM crash du container !"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"de"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Es belegt weiterhin Browser-Speicher, was zu einem memory leak oder sogar einem OOM crash des containers führt!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"vi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Nó sẽ tiếp tục chiếm dụng bộ nhớ trình duyệt, gây ra memory leak hoặc thậm chí làm container bị OOM crash!"&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;h4&gt;
  
  
  Sample 2: Cantonese-English Code-Switching Lecture Translator (&lt;code&gt;admin/prompts/translations/Cantonese-English Code-Switching Lecture Translator.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Used when instructors want explicit transformation of Hong Kong spoken modal particles (&lt;em&gt;㗎, 啦, 喎, 囉, 呢個, 點解, 搞掂&lt;/em&gt;) into formal written Chinese (書面語) and idiomatic English:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Cantonese-English Code-Switching Lecture Translator
&lt;/span&gt;
You are a bilingual simultaneous interpreter specializing in university classroom lectures delivered in colloquial Cantonese mixed with English technical jargon (Hong Kong higher education classroom context).

&lt;span class="gu"&gt;## Input Context
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Spoken Language: {{spokenLanguage}} (Spoken Cantonese / Hong Kong Colloquial Cantonese with English code-switching)
&lt;span class="k"&gt;- &lt;/span&gt;Target Subtitle Language: {{targetLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Subject Domain: {{courseContext}}

&lt;span class="gu"&gt;## Spoken Speech Transcript
&lt;/span&gt;"""
{{speechText}}
"""

&lt;span class="gu"&gt;## Translation Guidelines
&lt;/span&gt;1. &lt;span class="gs"&gt;**Handle Code-Switching and Loanwords**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Spoken Cantonese modal particles (e.g., 㗎, 啦, 喎, 囉, 呀, 嘅) and spoken colloquialisms (e.g., 呢個, 佢哋, 點解, 咁樣, 睇下) must be transformed into fluent, written grammar.
   &lt;span class="k"&gt;- &lt;/span&gt;For Traditional Chinese (&lt;span class="sb"&gt;`zh-TW`&lt;/span&gt; / &lt;span class="sb"&gt;`zh-HK`&lt;/span&gt;) target: Translate into standard formal written Chinese (書面語), while retaining English technical terms where standard in Hong Kong industry.
   &lt;span class="k"&gt;- &lt;/span&gt;For English (&lt;span class="sb"&gt;`en`&lt;/span&gt;) target: Provide clean, idiomatic English explanations without Cantonese grammatical calques.
2. &lt;span class="gs"&gt;**Technical Keyword Preservation**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Retain industry acronyms, software names, and specialized domain terms in standard English (e.g., &lt;span class="sb"&gt;`pipeline`&lt;/span&gt;, &lt;span class="sb"&gt;`database migration`&lt;/span&gt;, &lt;span class="sb"&gt;`props`&lt;/span&gt;, &lt;span class="sb"&gt;`API endpoint`&lt;/span&gt;, &lt;span class="sb"&gt;`pull request`&lt;/span&gt;).
3. &lt;span class="gs"&gt;**Subtitling Pacing**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Provide crisp, compact sentence segments that synchronize well with real-time video captions.

&lt;span class="gu"&gt;## Output Format
&lt;/span&gt;Return ONLY the translated sentence(s). No explanations, no pinyin/jyutping, and no markdown quotes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sample 3: Healthcare &amp;amp; Medical Sciences Clinical Lecture Translator (&lt;code&gt;admin/prompts/translations/Healthcare &amp;amp; Medical Sciences Clinical Lecture Translator.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Used by nursing and health-science faculties so clinical dosages (&lt;code&gt;mg/kg&lt;/code&gt;, &lt;code&gt;b.i.d.&lt;/code&gt;, &lt;code&gt;q8h&lt;/code&gt;) and Hospital Authority (HA) medical abbreviations (&lt;code&gt;BP&lt;/code&gt;, &lt;code&gt;SpO2&lt;/code&gt;, &lt;code&gt;ECG&lt;/code&gt;, &lt;code&gt;PRN&lt;/code&gt;, &lt;code&gt;STAT&lt;/code&gt;) are never mistranslated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Healthcare &amp;amp; Medical Sciences Clinical Lecture Translator
&lt;/span&gt;
You are a certified medical interpreter and clinical lecture subtitle specialist assisting healthcare, nursing, and medical sciences students.

&lt;span class="gu"&gt;## Input Context
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Spoken Language: {{spokenLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Target Subtitle Language: {{targetLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Academic Discipline: Healthcare, Nursing &amp;amp; Clinical Sciences

&lt;span class="gu"&gt;## Spoken Speech Transcript
&lt;/span&gt;"""
{{speechText}}
"""

&lt;span class="gu"&gt;## Translation Guidelines
&lt;/span&gt;1. &lt;span class="gs"&gt;**Medical &amp;amp; Pharmacological Accuracy**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Strictly preserve drug generic and brand names, pharmacological classifications, anatomical structures (ICD / MeSH standard), dosages (&lt;span class="sb"&gt;`mg/kg`&lt;/span&gt;, &lt;span class="sb"&gt;`mcg`&lt;/span&gt;, &lt;span class="sb"&gt;`b.i.d.`&lt;/span&gt;, &lt;span class="sb"&gt;`q8h`&lt;/span&gt;), and clinical abbreviations (&lt;span class="sb"&gt;`BP`&lt;/span&gt;, &lt;span class="sb"&gt;`SpO2`&lt;/span&gt;, &lt;span class="sb"&gt;`ECG`&lt;/span&gt;, &lt;span class="sb"&gt;`PRN`&lt;/span&gt;, &lt;span class="sb"&gt;`STAT`&lt;/span&gt;).
   &lt;span class="k"&gt;- &lt;/span&gt;Do not approximate or mislabel clinical measurements or contraindications.
2. &lt;span class="gs"&gt;**Clinical Purity**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;If translating into Chinese: Use standard Hong Kong Hospital Authority (HA) / international clinical Chinese terminology.
   &lt;span class="k"&gt;- &lt;/span&gt;If translating into English: Use standard GMC / AMA medical English.
3. &lt;span class="gs"&gt;**Pacing**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Keep subtitle segments readable in under 2 seconds per line.

&lt;span class="gu"&gt;## Output Format
&lt;/span&gt;Return ONLY the translated clinical sentence without surrounding commentary or quotation marks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sample 4: Business &amp;amp; Financial Accounting Lecture Translator (&lt;code&gt;admin/prompts/translations/Business &amp;amp; Financial Accounting Lecture Translator.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Used in corporate finance and accounting lectures to preserve &lt;code&gt;IFRS&lt;/code&gt;, &lt;code&gt;HKFRS&lt;/code&gt;, &lt;code&gt;EBITDA&lt;/code&gt;, &lt;code&gt;WACC&lt;/code&gt;, and &lt;code&gt;CAPEX&lt;/code&gt; distinctions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Business &amp;amp; Financial Accounting Lecture Translator
&lt;/span&gt;
You are an expert financial translator and business academic interpreter.

&lt;span class="gu"&gt;## Input Context
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Spoken Language: {{spokenLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Target Subtitle Language: {{targetLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Academic Discipline: Business, Finance &amp;amp; Accounting

&lt;span class="gu"&gt;## Spoken Speech Transcript
&lt;/span&gt;"""
{{speechText}}
"""

&lt;span class="gu"&gt;## Translation Guidelines
&lt;/span&gt;1. &lt;span class="gs"&gt;**Financial &amp;amp; Accounting Terminology Preservation**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Preserve accounting standards (&lt;span class="sb"&gt;`IFRS`&lt;/span&gt;, &lt;span class="sb"&gt;`GAAP`&lt;/span&gt;, &lt;span class="sb"&gt;`HKFRS`&lt;/span&gt;), financial metrics (&lt;span class="sb"&gt;`EBITDA`&lt;/span&gt;, &lt;span class="sb"&gt;`ROE`&lt;/span&gt;, &lt;span class="sb"&gt;`CAPEX`&lt;/span&gt;, &lt;span class="sb"&gt;`WACC`&lt;/span&gt;, &lt;span class="sb"&gt;`DCF`&lt;/span&gt;, &lt;span class="sb"&gt;`P/E ratio`&lt;/span&gt;), and balance sheet classifications.
   &lt;span class="k"&gt;- &lt;/span&gt;Do not confuse revenue, net profit, gross margin, or cash flow terminologies.
2. &lt;span class="gs"&gt;**Corporate &amp;amp; Economic Terminology**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Use standard corporate finance and capital markets nomenclature for target languages.
3. &lt;span class="gs"&gt;**Pacing &amp;amp; Conciseness**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Present translated subtitles cleanly for continuous multi-speaker business presentations and case studies.

&lt;span class="gu"&gt;## Output Format
&lt;/span&gt;Return ONLY the translated sentence(s). No markdown formatting, bullet points, or extraneous text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sample 5: Engineering &amp;amp; Applied Sciences Lecture Translator (&lt;code&gt;admin/prompts/translations/Engineering &amp;amp; Applied Sciences Lecture Translator.md&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Used in electrical, civil, and mechanical engineering labs to preserve SI units (&lt;code&gt;kW&lt;/code&gt;, &lt;code&gt;kV&lt;/code&gt;, &lt;code&gt;pF&lt;/code&gt;, &lt;code&gt;MHz&lt;/code&gt;), dimensional tolerances (&lt;code&gt;±0.05mm&lt;/code&gt;), and CAD/BIM terminology:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;# Engineering &amp;amp; Applied Sciences Lecture Translator
&lt;/span&gt;
You are an expert technical translator specializing in engineering, manufacturing, and applied sciences lectures.

&lt;span class="gu"&gt;## Input Context
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Spoken Language: {{spokenLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Target Subtitle Language: {{targetLanguage}}
&lt;span class="k"&gt;- &lt;/span&gt;Academic Discipline: Engineering &amp;amp; Construction

&lt;span class="gu"&gt;## Spoken Speech Transcript
&lt;/span&gt;"""
{{speechText}}
"""

&lt;span class="gu"&gt;## Translation Guidelines
&lt;/span&gt;1. &lt;span class="gs"&gt;**Engineering Precision**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Strictly preserve SI metric units, electrical notations (&lt;span class="sb"&gt;`kW`&lt;/span&gt;, &lt;span class="sb"&gt;`kV`&lt;/span&gt;, &lt;span class="sb"&gt;`pF`&lt;/span&gt;, &lt;span class="sb"&gt;`MHz`&lt;/span&gt;), tolerance symbols (&lt;span class="sb"&gt;`±0.05mm`&lt;/span&gt;), mechanical formulas, and CAD/BIM terminology.
   &lt;span class="k"&gt;- &lt;/span&gt;Retain standard international engineering terms in English when used in global engineering practice.
2. &lt;span class="gs"&gt;**Clarity**&lt;/span&gt;:
   &lt;span class="k"&gt;- &lt;/span&gt;Ensure safety protocols, machinery operational procedures, and mathematical steps are translated with zero ambiguity.

&lt;span class="gu"&gt;## Output Format
&lt;/span&gt;Return ONLY the direct translated subtitle text without markdown or commentary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sample 6: Client-Mode On-Device &lt;code&gt;Gemma 4 E2B&lt;/code&gt; Prompt &amp;amp; Multimodal Audio Fallback STT Prompt
&lt;/h4&gt;

&lt;p&gt;When running in &lt;strong&gt;Client Mode&lt;/strong&gt; (&lt;code&gt;litertGemma.worker.js&lt;/code&gt;), or when local Web Speech API / WASM Whisper encounters low-confidence audio and falls back to &lt;code&gt;transcribeAudioWithFirebaseAI&lt;/code&gt; (&lt;code&gt;Real-Time Speech-to-Text Transcriber.md&lt;/code&gt;), the exact prompts are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. On-Device LiteRT-LM Gemma 4 E2B Turn Prompt (web-app/src/workers/litertGemma.worker.js)
&amp;lt;start_of_turn&amp;gt;user
You are an expert real-time lecture translation assistant for Cloud Computing &amp;amp; DevOps.
Translate the following spoken classroom transcript from Cantonese (Hong Kong) into the requested target languages:
- "zh-Hant": Traditional Chinese
- "en": English
- "ja": Japanese

CRITICAL RULES:
1. FULL TRANSLATION MANDATORY: Translate the entire meaning into each requested target language. For English ("en"), translate all spoken Chinese/Cantonese phrases into fluent natural English.
2. For Simplified Chinese ("zh-Hans" / "zh-CN"), translate into standard written Simplified Chinese. For Traditional Chinese ("zh-Hant"), translate into standard written Traditional Chinese.
3. Preserve discipline-specific terminology, proper nouns, formula/variable names, and standard technical abbreviations (e.g. "useEffect", "Docker", "Python") in their original technical form.
4. Return strictly a single valid JSON object mapping each target language code to its translated text.

Spoken transcript:
"大家用 terraform apply 之前，記住行一次 terraform plan 睇下個 state diff 先。"
&amp;lt;end_of_turn&amp;gt;
&amp;lt;start_of_turn&amp;gt;model
{"zh-Hant":"大家在使用 terraform apply 之前，請記得先執行一次 terraform plan 查看 state diff。","en":"Before running terraform apply, everyone remember to run terraform plan first to inspect the state diff.","ja":"terraform apply を実行する前に、まず terraform plan を一度実行して state diff を確認してください。"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 2. Multimodal Audio Fallback Verbatim STT Prompt (transcribeAudioWithFirebaseAI)
You are a real-time classroom speech-to-text transcriber for Cloud Computing &amp;amp; DevOps.
The spoken language is zh-HK (with mixed domain and technical terms).
Transcribe the spoken audio verbatim. Keep discipline-specific terms, proper nouns, and technical vocabulary accurate for Cloud Computing &amp;amp; DevOps.
CRITICAL RULES:
- Output ONLY the exact words spoken in the audio.
- If the audio contains only silence, background noise, breathing, or no clear human speech, return an EMPTY string "".
- NEVER ask for audio, NEVER explain that no speech was detected, and NEVER add notes, quotes, or commentary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Edge Cases &amp;amp; Production Failure Modes We Solved
&lt;/h2&gt;

&lt;p&gt;Building a demo speech-to-text app takes an afternoon; making it survive 3-hour lectures across Windows, macOS, iPadOS, and Android hardware in noisy lecture halls required solving five subtle production bugs:&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 3: Out-of-Tab Microphone Silence Watchdog &amp;amp; Mobile iOS Jetsam OOM Guard
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm808xldr71w5ntqqf670.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm808xldr71w5ntqqf670.png" alt="Out-of-Tab Microphone Watchdog &amp;amp; Mobile iOS Jetsam OOM Guard" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Case 1: The Mobile iOS WebKit 1.9 GB &lt;code&gt;Jetsam OOM&lt;/code&gt; Crash Guard
&lt;/h3&gt;

&lt;p&gt;iOS and iPadOS browsers enforce a strict per-tab WebContent memory ceiling (~1.4 GB–2.0 GB). If an instructor opens the Teacher Dashboard on an iPad Pro and the browser attempts to auto-download and instantiate the ~1.9 GB &lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt; model in WASM/WebGPU memory, iOS WebKit immediately kills and reloads the page (&lt;code&gt;Jetsam OOM&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;isMobileOrIosBrowser()&lt;/code&gt; detects iOS, Android, and iPadOS (&lt;code&gt;navigator.platform === 'MacIntel' &amp;amp;&amp;amp; navigator.maxTouchPoints &amp;gt; 1&lt;/code&gt;) and automatically locks &lt;code&gt;engineMode&lt;/code&gt; to &lt;code&gt;'server'&lt;/code&gt;, while on desktop browsers Gemma 4 E2B is never downloaded automatically—it requires an explicit teacher click on &lt;code&gt;loadGemmaModel()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Edge Case 2: Whisper Silence Hallucinations (&lt;code&gt;"Thank you for watching"&lt;/code&gt;, &lt;code&gt;"♪"&lt;/code&gt;, Foreign Script Artifacts)
&lt;/h3&gt;

&lt;p&gt;When a lecturer pauses for 5 seconds to write on the whiteboard, &lt;code&gt;whisper-tiny&lt;/code&gt; frequently hallucinates YouTube training artifacts such as &lt;code&gt;"Thank you."&lt;/code&gt;, &lt;code&gt;"Thanks for watching."&lt;/code&gt;, &lt;code&gt;"[BLANK_AUDIO]"&lt;/code&gt;, or random Korean/Cyrillic/Arabic characters due to background HVAC fan noise. Worse, if a non-empty hallucination string is returned, a naive pipeline treats it as valid speech and skips the Cloud AI audio transcription fallback!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: Both client (&lt;code&gt;isWhisperNoiseArtifact&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt;) and server (&lt;code&gt;isSubtitleNoiseOrRefusal&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/subtitleFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/subtitleFlows.js&lt;/code&gt;&lt;/a&gt;) strip these exact hallucination patterns and out-of-script Unicode blocks (&lt;code&gt;[\uAC00-\uD7AF\u0600-\u06FF...]&lt;/code&gt;) &lt;em&gt;before&lt;/em&gt; deciding whether to invoke &lt;code&gt;transcribeAudioWithFirebaseAI&lt;/code&gt; or publish to Firestore:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From web-app/src/hooks/useTeacherLiveSubtitles.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isWhisperNoiseArtifact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speechLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'zh-HK'&lt;/span&gt;&lt;span class="p"&gt;)&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;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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="o"&gt;/^&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="p"&gt;]&lt;/span&gt;&lt;span class="o"&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="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;♪&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;♪&lt;span class="o"&gt;*|&lt;/span&gt;&lt;span class="n"&gt;thank&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt;\&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;?|&lt;/span&gt;&lt;span class="n"&gt;thanks&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;watching&lt;/span&gt;\&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;?|&lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;\&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;$&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trimmed&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;please&lt;/span&gt; &lt;span class="n"&gt;provide&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="n"&gt;recording&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;speech&lt;/span&gt; &lt;span class="n"&gt;detected&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;clear&lt;/span&gt; &lt;span class="n"&gt;speech&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;inaudible&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ambient&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;nothing&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;transcribe&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;blank_audio&lt;/span&gt;&lt;span class="o"&gt;|^&lt;/span&gt;&lt;span class="n"&gt;transcription&lt;/span&gt;\&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;*:/&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;trimmed&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&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;normalizedLang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speechLanguage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'zh-HK'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedLang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zh'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;normalizedLang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'en'&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;// Reject unexpected Hangul, Arabic, Hiragana/Katakana, Thai, or Cyrillic noise tokens&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;\&lt;span class="n"&gt;uAC00&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;\&lt;span class="n"&gt;uD7AF&lt;/span&gt;\&lt;span class="n"&gt;u1100&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;\&lt;span class="n"&gt;u11FF&lt;/span&gt;\&lt;span class="n"&gt;u0600&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;\&lt;span class="n"&gt;u06FF&lt;/span&gt;\&lt;span class="n"&gt;u3040&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;\&lt;span class="n"&gt;u30FF&lt;/span&gt;\&lt;span class="n"&gt;u0E00&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;\&lt;span class="n"&gt;u0E7F&lt;/span&gt;\&lt;span class="n"&gt;u0400&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;\&lt;span class="n"&gt;u04FF&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trimmed&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;h3&gt;
  
  
  Edge Case 3: Out-of-Order Async Network Responses &amp;amp; Parallel STT Race Conditions
&lt;/h3&gt;

&lt;p&gt;When running hybrid Web Speech API + WASM Whisper VAD in parallel, two race conditions happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Web Speech API finalizes an utterance in 150ms, and 4 seconds later the local WASM Whisper worker finishes processing the same PCM buffer and overwrites the newer subtitle.&lt;/li&gt;
&lt;li&gt;Utterance #14 (a long 25-word sentence) takes 800ms to translate in the cloud, while Utterance #15 (a short 3-word phrase) finishes in 300ms. When Utterance #14's HTTP response arrives late, it overwrites Utterance #15 on student screens!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Whenever &lt;code&gt;recognition.onresult&lt;/code&gt; emits a final transcript, it sets &lt;code&gt;lastSpeechRecFinalAt = Date.now()&lt;/code&gt; and calls &lt;code&gt;clearPendingVadBuffer()&lt;/code&gt;, suppressing any trailing Whisper VAD flush within 2.5 seconds.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;publishSubtitle&lt;/code&gt;, we assign a monotonically increasing sequence number &lt;code&gt;const seq = ++seqCounterRef.current&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; awaiting &lt;code&gt;translateTranscript()&lt;/code&gt;. When the translation resolves, if &lt;code&gt;seq &amp;lt; latestPublishedSeqRef.current&lt;/code&gt;, we discard the stale write from touching &lt;code&gt;liveSubtitles/current&lt;/code&gt; while still preserving it in &lt;code&gt;recentHistory&lt;/code&gt; sorted by &lt;code&gt;seq&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Edge Case 4: iOS Safari WebKit &lt;code&gt;AudioContext.resume()&lt;/code&gt; Deadlock &amp;amp; Zero-Gain Pull Node
&lt;/h3&gt;

&lt;p&gt;In Chrome, Safari, and iOS WebKit, calling &lt;code&gt;await audioCtx.resume()&lt;/code&gt; inside an async &lt;code&gt;getUserMedia&lt;/code&gt; callback when transient user activation has expired returns a &lt;strong&gt;permanently pending Promise&lt;/strong&gt; that deadlocks audio setup forever. Furthermore, on iOS WebKit, a &lt;code&gt;MediaStreamAudioSourceNode&lt;/code&gt; connected only to an &lt;code&gt;AnalyserNode&lt;/code&gt; or &lt;code&gt;AudioWorklet&lt;/code&gt; will &lt;strong&gt;refuse to pull PCM frames&lt;/strong&gt; unless the graph terminates at &lt;code&gt;audioCtx.destination&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/subtitles/TeacherSubtitleControlModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TeacherSubtitleControlModal.jsx&lt;/code&gt;&lt;/a&gt;, we never &lt;code&gt;await audioCtx.resume()&lt;/code&gt;; instead we fire-and-forget &lt;code&gt;tryResumeAudioCtx()&lt;/code&gt; alongside passive &lt;code&gt;pointerdown&lt;/code&gt;/&lt;code&gt;keydown&lt;/code&gt; listeners, and connect the analyser to &lt;code&gt;audioCtx.destination&lt;/code&gt; through a &lt;strong&gt;0-gain silent &lt;code&gt;GainNode&lt;/code&gt; (&lt;code&gt;silentGain.gain.value = 0&lt;/code&gt;)&lt;/strong&gt; so WebKit pulls hardware audio frames without causing speaker feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Edge Case 5: The Wireless Mic Battery Trap (&lt;code&gt;isMicSilentWarning&lt;/code&gt; Watchdog)
&lt;/h3&gt;

&lt;p&gt;Lecturers frequently minimize the browser and spend 40 minutes coding in VS Code or presenting fullscreen PowerPoint slides. If their wireless lapel microphone battery dies or they accidentally hit the hardware mute switch on their headset, they have no idea that student captions have stopped.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Our Fix&lt;/strong&gt;: A 2-second watchdog in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useTeacherLiveSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;useTeacherLiveSubtitles.js&lt;/code&gt;&lt;/a&gt; monitors both hardware track events (&lt;code&gt;primaryAudioTrack.muted&lt;/code&gt;, &lt;code&gt;enabled === false&lt;/code&gt;, &lt;code&gt;readyState === 'ended'&lt;/code&gt;) and RMS voice activity timestamps (&lt;code&gt;lastVoiceActivityAtRef.current&lt;/code&gt;). If the hardware track is muted for $\ge 10\text{s}$ or zero speech energy is detected for $\ge 45\text{s}$, &lt;code&gt;isMicSilentWarning&lt;/code&gt; triggers a high-visibility alert and native OS notification on top of fullscreen PowerPoint.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Architectural Comparison: Server Gemini Models vs. Edge Gemma 4 E2B
&lt;/h2&gt;

&lt;p&gt;Here is how the dual-engine subtitle options compare across execution mode, model configuration, and device requirements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Translation Engine&lt;/th&gt;
&lt;th&gt;Model Identifier&lt;/th&gt;
&lt;th&gt;Generation Configuration&lt;/th&gt;
&lt;th&gt;Browser Memory Footprint&lt;/th&gt;
&lt;th&gt;Cloud Token Cost (&lt;code&gt;cost.js&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server Mode (Default Workhorse)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;temperature: 0.1&lt;/code&gt;, &lt;code&gt;thinkingBudget: 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Minimal (Stateless HTTPS Callable)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$0.30&lt;/code&gt; / &lt;code&gt;$2.50&lt;/code&gt; per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server Mode (High-Nuance Flash)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;temperature: 0.1&lt;/code&gt;, &lt;code&gt;thinkingBudget: 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Minimal (Stateless HTTPS Callable)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$0.75&lt;/code&gt; / &lt;code&gt;$3.75&lt;/code&gt; per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server Mode (Flagship Reasoning)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;thinkingBudget: 0&lt;/code&gt; (Legacy sampling keys stripped)&lt;/td&gt;
&lt;td&gt;Minimal (Stateless HTTPS Callable)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$2.00&lt;/code&gt; / &lt;code&gt;$10.00&lt;/code&gt; per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client Mode (Edge WebGPU/WASM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;On-Device Web Worker + Hybrid Partial Cloud Fallback&lt;/td&gt;
&lt;td&gt;~1.9 GB (Cached in &lt;code&gt;CacheStorage&lt;/code&gt;, Desktop Only)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0.00&lt;/strong&gt; (for all locally translated languages)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decouple STT from Multilingual Translation&lt;/strong&gt;: Running lightweight hybrid STT on the edge (Web Speech API + LiteRT Whisper + noise artifact filtering) and sending clean text strings with a 4-turn rolling history to &lt;code&gt;translateTeacherSpeech&lt;/code&gt; eliminates server audio-slicing overhead and keeps latency under 500ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;thinkingBudget: 0&lt;/code&gt; for Real-Time Captioning&lt;/strong&gt;: When invoking &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Gemini 3.8 Flash&lt;/strong&gt;, or &lt;strong&gt;Gemini 3.5 Flash-Lite&lt;/strong&gt; for live subtitles, setting &lt;code&gt;thinkingConfig: { thinkingBudget: 0 }&lt;/code&gt; and &lt;code&gt;temperature: 0.1&lt;/code&gt; delivers flagship lexical accuracy without reasoning-token latency spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid Edge + Cloud Beats Pure Edge&lt;/strong&gt;: On-device &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; reduces cloud FinOps cost to $0.00 on desktop laptops, while automatic partial fallback to &lt;code&gt;translateTeacherSpeech&lt;/code&gt; ensures mobile iPads never crash from WebKit memory limits and no target language is ever left blank.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard Every Browser Audio Quirk&lt;/strong&gt;: Non-blocking &lt;code&gt;AudioContext.resume()&lt;/code&gt;, 0-gain destination nodes on WebKit, Android &lt;code&gt;SpeechRecognizer&lt;/code&gt; mic-seizure avoidance, and monotonic sequence numbers (&lt;code&gt;seqCounterRef&lt;/code&gt;) are essential for rock-solid classroom reliability.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>speech</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Zero Trust GKE Control Plane Access with DNS Endpoints</title>
      <dc:creator>Azidin Shairi</dc:creator>
      <pubDate>Tue, 10 Nov 2026 08:18:33 +0000</pubDate>
      <link>https://dev.to/gde/zero-trust-gke-control-plane-access-with-dns-endpoints-1fba</link>
      <guid>https://dev.to/gde/zero-trust-gke-control-plane-access-with-dns-endpoints-1fba</guid>
      <description>&lt;h2&gt;
  
  
  Secure Your GKE Control Plane: Migrating from IP-Based Authorized Networks to IAM-Based DNS Endpoint&amp;nbsp;Access
&lt;/h2&gt;

&lt;p&gt;The GKE DNS-based endpoint provides a cluster-unique FQDN (Fully Qualified Domain Name) for the Kubernetes control plane, allowing for simplified, IAM-based access control instead of relying on static IP-based authorized networks. This guide outlines the steps to enable this feature and transition your access methods.&lt;/p&gt;

&lt;p&gt;The DNS-Based Endpoint offers several advantages over the IP-Based Endpoint with Authorized Networks for accessing Google Cloud clusters. While IP-based access relies on source IP CIDR blocks and often requires bastion hosts or VPN for private clusters, the DNS-based approach uses Google Cloud IAM and is accessible from any network that can reach Google Cloud APIs. This provides a more flexible and dynamic access control model, integrated with IAM and VPC Service Controls for enhanced security, compared to the primarily network-layer security of IP-based endpoints.&lt;/p&gt;




&lt;p&gt;This guide walks through the steps to enable the DNS endpoint on an existing GKE cluster and migrate the access to it. We'll use a GKE cluster named &lt;code&gt;gke-test-cluster-1&lt;/code&gt;, deployed in the GCP Finland region, zone &lt;code&gt;europe-north1-a&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Enable the DNS-Based Endpoint
&lt;/h3&gt;

&lt;p&gt;You can enable the DNS-based endpoint on an existing GKE Standard or Autopilot cluster using the gcloud CLI.&lt;/p&gt;

&lt;p&gt;First, let's review the GKE Control Plane configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters describe gke-test-cluster-1 \
--zone europe-north1-a --format=&lt;span class="s2"&gt;"yaml(controlPlaneEndpointsConfig)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;controlPlaneEndpointsConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dnsEndpointConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;allowExternalTraffic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;enableK8sCertsViaDns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;enableK8sTokensViaDns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gke-CLUSTER_ID-PROJECT.europe-north1-a.gke.goog&lt;/span&gt;
  &lt;span class="na"&gt;ipEndpointsConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;authorizedNetworksConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;cidrBlocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cidrBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALLOWED_IP_ADDR&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;office-network&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;gcpPublicCidrsAccessEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;enablePublicEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;privateEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PRV_IP_ADDR&lt;/span&gt;
    &lt;span class="na"&gt;publicEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PUB_IP_ADDR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the current GKE Control Plane configuration. The IP-based Endpoint is enabled for both Public and Private IPs and Authorized Network is also enabled allowing "office-network" IP addresses.&lt;br&gt;
The GKE DNS Endpoint FQDN follows this format:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gke-CLUSTER_ID-PROJECT.europe-north1-a.gke.goog&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, it is currently disabled.&lt;/p&gt;

&lt;p&gt;So let's update the cluster to enable DNS-based access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters update gke-test-cluster-1 \
--zone europe-north1-a --enable-dns-access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may take a few minutes for the GKE Control Plane to update with DNS Endpoint.&lt;br&gt;
Once the operation is completed, you can run &lt;code&gt;gcloud&lt;/code&gt; container clusters describe command again and should see &lt;code&gt;allowExternalTraffic&lt;/code&gt; is enabled for &lt;code&gt;dnsEndpointConfig&lt;/code&gt;&amp;nbsp;.&lt;/p&gt;

&lt;p&gt;Now, both IP-based and DNS-based endpoints are enabled. Both coexist at this point while we work on the next steps migrating the access from IP-based to DNS-based.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Configure IAM Access
&lt;/h3&gt;

&lt;p&gt;Access to the DNS endpoint is controlled by IAM. The specific IAM permission required for DNS-based access is &lt;code&gt;container.clusters.connect&lt;/code&gt;&lt;br&gt;
The permission &lt;code&gt;container.clusters.connect&lt;/code&gt; is included in many predefined IAM roles such as "Kubernetes Engine Admin" and "Kubernetes Engine Developer". It's most likely your IAM user already has the permission through existing IAM roles, otherwise you were not able to update the GKE cluster in the previous step.&lt;/p&gt;

&lt;p&gt;For other users however, your fellow developers, CI/CD pipelines and such might use other IAM roles or custom roles. In this case you need to update those roles to include the IAM permission.&lt;/p&gt;

&lt;p&gt;As an example here if you need to add another GKE admin user, you can grant the IAM role as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud projects add-iam-policy-binding &lt;span class="nv"&gt;$PROJECT&lt;/span&gt; \
--member=&lt;span class="s2"&gt;"user:$GKE_ADMIN"&lt;/span&gt; --role=&lt;span class="s2"&gt;"roles/container.admin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Test and Migrate Client Access
&lt;/h3&gt;

&lt;p&gt;Now that the GKE Control Plane DNS Endpoint is enabled and IAM roles are configured, it's time to update clients to use the FQDN.&lt;br&gt;
To make use of the DNS Endpoint in kubectl you need to refresh the kubeconfig file by fetching the new GKE cluster's credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters get-credentials gke-test-cluster-1 \
--zone europe-north1-a --dns-endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take note of the &lt;code&gt;--dns-endpoint&lt;/code&gt; parameter. This updates the kubeconfig and replaces the configuration for accessing your GKE Control Plane using FQDN instead of public IP address.&lt;br&gt;
You can view the kubeconfig configuration using command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;kubectl config view --minify&lt;/span&gt;
&lt;span class="na"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://gke-CLUSTER_ID-PROJECT.europe-north1-a.gke.goog&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gke_PROJECT_europe-north1-a_gke-test-cluster-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run your favourite &lt;code&gt;kubectl&lt;/code&gt; command as usual like &lt;code&gt;kubectl get namespaces&lt;/code&gt;. If the command executed successfully it means our setup now works correctly.&lt;/p&gt;

&lt;p&gt;Once you verified it's working, it's time to update all your CI/CD tools, pipelines to include &lt;code&gt;--dns-endpoint&lt;/code&gt; parameter when fetching GKE cluster credentials, and inform your fellow GKE admins and developers to refresh their GKE cluster credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Disable IP-Based Authorized Networks
&lt;/h3&gt;

&lt;p&gt;After you have successfully migrated all clients to using DNS Endpoint you can safely remove the IP-based authorized network along with the IP-based Endpoint. This helps keep your GKE clusters configuration clean.&lt;/p&gt;

&lt;p&gt;There are 2 options here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Disabling the Public IP Endpoint which removes the Public IP address from your GKE Control Plane.&lt;/li&gt;
&lt;li&gt;Disable IP-based Endpoint completely which removes both Public and Private IP Endpoint. If you still have some other dependency on the Private IP Endpoint, you can just remove the Public IP Endpoint which improves your Control Plane security by not exposing it directly to the public.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Please note that the DNS Endpoint also works from your VPC.&lt;br&gt;
Nonetheless to disable the Public IP Endpoint while keeping the Private IP Endpoint you can update the GKE cluster as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters update gke-test-cluster-1 \
--zone europe-north1-a --enable-private-endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To disable IP-based Endpoint completely you can disable both Public IP and Private IP Endpoints as follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud container clusters update gke-test-cluster-1 \
--zone europe-north1-a --no-enable-ip-access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your GKE Control Plane Endpoint config will look like this after the clean up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;controlPlaneEndpointsConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dnsEndpointConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;allowExternalTraffic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;enableK8sCertsViaDns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;enableK8sTokensViaDns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gke-CLUSTER_ID-PROJECT.europe-north1-a.gke.goog&lt;/span&gt;
  &lt;span class="na"&gt;ipEndpointsConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;With GKE Control Plane access now managed by IAM, you can use VPC Service Controls to add Context-Aware Access and further secure your GKE clusters.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>security</category>
      <category>kubernetes</category>
      <category>gke</category>
    </item>
    <item>
      <title>Google AI Classroom Part 4: Defeating Proxy Attendance &amp; Lab Keyloggers with WebAuthn Passkeys, Rotating HMAC QRs &amp; GCIP Blocking Functions</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Tue, 10 Nov 2026 07:25:07 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-4-defeating-proxy-attendance-lab-keyloggers-with-webauthn-passkeys-2gc0</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-4-defeating-proxy-attendance-lab-keyloggers-with-webauthn-passkeys-2gc0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  1. Problem Statement: The Proxy Account Sharing, "Fake Screen-Recording" &amp;amp; Keylogger Crisis in University Computer Labs &amp;amp; 200-Seat Lecture Theatres
&lt;/h2&gt;

&lt;p&gt;When running hands-on programming classes and invigilated practical assessments across physical computer labs and 200-seat lecture halls at the Hong Kong Institute of Information Technology (HKIIT / VTC), our engineering team encountered three real-world identity and physical-presence exploits that traditional username-and-password authentication and campus IP checks cannot stop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The "Give My Password to a Classmate" Proxy Lab Exploit (Fake Video Playback &amp;amp; Proxy Bingo)&lt;/strong&gt;: Even when campus CIDR IP restrictions (&lt;code&gt;checkipaddress&lt;/code&gt;) block off-campus logins, an absent student simply texts their email and password to a friend sitting inside the computer lab. That single classmate sits in front of two adjacent lab workstations (or opens a second browser profile), logs into the absent student's account from a legitimate lab IP, &lt;strong&gt;starts screen sharing, and plays a pre-recorded coding video on loop in full-screen&lt;/strong&gt; to fake live lab activity! Whenever a Classroom Bingo active-presence quiz pops up on that second monitor, the classmate simply reaches over and clicks an answer on their friend's behalf.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "WhatsApp Proxy Attendance" Relay in 200-Seat Lecture Halls&lt;/strong&gt;: When instructors project a static attendance QR code or 4-digit check-in code on the lecture theatre screen, a student sitting in the front row snaps a photo and forwards it to a 150-member WhatsApp or Discord group. Within five seconds, absent students still in bed—or sitting in the campus cafeteria—mark themselves "present."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Shared Lab PC Keylogger Risk&lt;/strong&gt;: Public university computer labs are high-turnover environments. Typing institutional passwords—especially an instructor's privileged account that can trigger high-quota &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; cohort-wide video analysis or modify exam policies—on untrusted shared USB keyboards exposes faculty and students to both hardware inline keyloggers and resident TSR malware.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why is a password or campus IP check completely insufficient here? Because &lt;strong&gt;a password can be texted over WhatsApp in 2 seconds, and a lab PC next to your friend shares the exact same campus subnet IP—whereas a student will never hand over their personal unlocked smartphone (&lt;code&gt;Apple Secure Enclave&lt;/code&gt; / &lt;code&gt;Android Titan M2&lt;/code&gt; with Face ID, personal WhatsApp, and mobile banking) to a classmate for 3 hours!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To defeat all three exploits simultaneously without requiring dedicated biometric turnstiles or proprietary native mobile apps, we engineered a zero-trust, hardware-bound identity layer built on three pillars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FIDO2 / WebAuthn Hardware-Bound Mobile Passkeys (&lt;code&gt;@simplewebauthn/server&lt;/code&gt;)&lt;/strong&gt; enforcing a strict &lt;strong&gt;1-Phone = 1-Student Hardware Lock&lt;/strong&gt;: One physical smartphone can only ever be bound to one student account (&lt;code&gt;credentialID&lt;/code&gt; + &lt;code&gt;deviceFingerprint&lt;/code&gt;). Even if a classmate logs into a second lab PC using a friend's shared password and plays a fake screen recording, they &lt;strong&gt;cannot pass the Mobile Passkey Bingo challenge or the Lab PC Passkey Enforcement Gate&lt;/strong&gt; because their own phone is already cryptographically locked to their own student UID!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Identity Platform (GCIP) Blocking Functions&lt;/strong&gt; (&lt;code&gt;beforeUserCreated&lt;/code&gt; &amp;amp; &lt;code&gt;beforeUserSignedIn&lt;/code&gt;) enforcing a &lt;strong&gt;4-Tier Deterministic Role Hierarchy&lt;/strong&gt; and schedule-aware &lt;strong&gt;Campus Lab CIDR IP Gating&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15-Second Rotating HMAC-SHA256 QR Codes&lt;/strong&gt; for both &lt;strong&gt;1.8-second Lecture Hall Biometric Roll Call&lt;/strong&gt; and &lt;strong&gt;Zero-Keystroke Passwordless Lab PC Login&lt;/strong&gt;, paired with a &lt;strong&gt;3-Tier Emergency Bypass &amp;amp; Audit System&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/CUL5JWWRi0I" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architectural Deep-Dive: 4-Tier Hybrid Role Resolution &amp;amp; Schedule-Aware CIDR Gating
&lt;/h2&gt;

&lt;p&gt;On multi-campus educational domains, distinguishing faculty from students at the exact moment of registration is critical. In our platform, a user with &lt;code&gt;role: 'teacher'&lt;/code&gt; can orchestrate &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; multimodal grading jobs, inspect live student screen feeds, and reset hardware passkeys, whereas &lt;code&gt;role: 'student'&lt;/code&gt; is strictly scoped to submitting their own telemetry and viewing authorized course materials. An accidental privilege escalation is both a data-privacy violation and a FinOps risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 1: 4-Tier Deterministic Role Hierarchy &amp;amp; GCIP Blocking Gate
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/auth_triggers/userManagement.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/auth_triggers/userManagement.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/auth_triggers/ipRestriction.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/auth_triggers/ipRestriction.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/auth_triggers/config.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/auth_triggers/config.js&lt;/code&gt;&lt;/a&gt;, our GCIP blocking functions intercept every signup and sign-in request &lt;em&gt;before&lt;/em&gt; a Firebase JWT is ever minted:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg4wecp0cjzgadedhtqr2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg4wecp0cjzgadedhtqr2.png" alt="4-Tier Deterministic Role Resolution &amp;amp; GCIP Blocking Gate" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Walkthrough: Multi-Domain Role Derivation &amp;amp; Pre-Enrolled Roster Linking
&lt;/h3&gt;

&lt;p&gt;Why do we need four tiers instead of a simple &lt;code&gt;@vtc.edu.hk&lt;/code&gt; suffix check? Because in real polytechnic and university environments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visiting industry lecturers or external examiners may be pre-enrolled by a course coordinator in &lt;code&gt;classes/{classId}.teacherEmails&lt;/code&gt; before they ever log in for the first time (&lt;strong&gt;Tier 1&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Some institutions share a single apex domain (&lt;code&gt;@school.edu&lt;/code&gt;) for both staff and students, distinguishing them only by username pattern (e.g., 9-digit student IDs vs. alphabetic staff handles—&lt;strong&gt;Tier 2&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;In hierarchical domains (&lt;code&gt;@stu.vtc.edu.hk&lt;/code&gt; vs. &lt;code&gt;@vtc.edu.hk&lt;/code&gt;), a naive &lt;code&gt;.endsWith('vtc.edu.hk')&lt;/code&gt; check would accidentally classify &lt;code&gt;@stu.vtc.edu.hk&lt;/code&gt; as a teacher unless student subdomains take strict precedence (&lt;strong&gt;Tier 3&lt;/strong&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the production implementation from &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/config.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/config.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/auth_triggers/userManagement.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/auth_triggers/userManagement.js&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/ai_flows/config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deriveUserRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@'&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="kc"&gt;null&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;cleanEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="nf"&gt;toLowerCase&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;atIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleanEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;)&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;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleanEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;atIndex&lt;/span&gt;&lt;span class="p"&gt;)&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;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleanEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;atIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;matchesTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetDomain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;targetDomain&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;targetDomain&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;targetDomain&lt;/span&gt;&lt;span class="p"&gt;)&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;isStudentDomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;STUDENT_EMAIL_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matchesTarget&lt;/span&gt;&lt;span class="p"&gt;)&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;isTeacherDomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TEACHER_EMAIL_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matchesTarget&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isStudentDomain&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isTeacherDomain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Tier 2: Username Regex check takes precedence if configured&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STUDENT_USERNAME_REGEX&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;STUDENT_USERNAME_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&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="s1"&gt;'student'&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="no"&gt;TEACHER_USERNAME_REGEX&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;TEACHER_USERNAME_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&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="s1"&gt;'teacher'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Tier 4 Guard: Check for same-domain or wildcard overlap&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;exactStudentDomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;STUDENT_EMAIL_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&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;exactTeacherDomain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TEACHER_EMAIL_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;d&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;exactStudentDomain&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;exactTeacherDomain&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="no"&gt;DEFAULT_TO_STUDENT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'student'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Tier 3: Subdomain hierarchy (student subdomains win over parent teacher domains,&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g., stu.vtc.edu.hk wins over vtc.edu.hk)&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;isStudentDomain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'student'&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;isTeacherDomain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_TO_STUDENT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'student'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;And during &lt;code&gt;beforeUserCreated&lt;/code&gt;, we combine Tier 1 pre-enrollment checks with automatic profile hydration from the central &lt;code&gt;studentDirectory&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/auth_triggers/userManagement.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/auth_triggers/userManagement.js&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/auth_triggers/userManagement.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleBeforeUserCreatedLogic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customDeps&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;currentDb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customDeps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;db&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;currentDeriveRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customDeps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deriveUserRole&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;deriveUserRole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'invalid-argument'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Email is required to sign up.'&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;classesRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'classes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Tier 1: Check if this email was pre-registered as a teacher in any existing class&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;teacherPreEnrollSnapshot&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;classesRef&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'teacherEmails'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'array-contains'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;isPreEnrolledTeacher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;teacherPreEnrollSnapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;derivedRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isPreEnrolledTeacher&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;currentDeriveRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;derivedRole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'invalid-argument'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Please use a valid institutional email address.'&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;// Return custom claims directly into the newly minted GCIP JWT&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;customClaims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;derivedRole&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Schedule-Aware Campus Lab CIDR Gating (&lt;code&gt;checkipaddress&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;During scheduled practical exams, we want to prevent remote students from logging into a class session from outside the physical computer lab subnet, &lt;em&gt;without&lt;/em&gt; blocking students from reviewing their course materials from home in the evening.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/auth_triggers/ipRestriction.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/auth_triggers/ipRestriction.js&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;checkipaddress&lt;/code&gt; (&lt;code&gt;beforeUserSignedIn&lt;/code&gt;) evaluates the student's enrolled classes against their timezone-aware &lt;code&gt;schedule.timeSlots&lt;/code&gt; using &lt;code&gt;date-fns-tz&lt;/code&gt; and validates &lt;code&gt;event.ipAddress&lt;/code&gt; against the class's IPv4 CIDR ranges (&lt;code&gt;ip-address&lt;/code&gt; &lt;code&gt;Address4.isInSubnet()&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/auth_triggers/ipRestriction.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;checkipaddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;beforeUserSignedIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;FUNCTION_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'512MiB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&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;ipAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipAddress&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ipAddress&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="c1"&gt;// Teachers are never blocked by student lab IP restrictions&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customClaims&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customClaims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;studentProfileSnap&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'studentProfiles'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;studentProfileSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;studentProfileSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;classSnapshots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;studentProfileSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="n"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'classes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;allRestrictions&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;classSnapshots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;classData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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;// Only enforce IP restrictions if the class is currently in an active scheduled time slot&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDuringScheduledTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipRestrictions&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;allRestrictions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;allRestrictions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ipRestrictions&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="p"&gt;}&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;allRestrictions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Outside class hours -&amp;gt; allow home study access!&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;uniqueRestrictions&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allRestrictions&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ipAllowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uniqueRestrictions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restriction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;restriction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Address4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ipAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInSubnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Address4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;restriction&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="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ipAddress&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;restriction&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="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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ipAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'permission-denied'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'Login is only permitted from a school network during scheduled class times.'&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="p"&gt;}&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;h2&gt;
  
  
  3. FIDO2 WebAuthn Hardware Lock: Enforcing &lt;code&gt;1-Phone = 1-Student&lt;/code&gt; to Stop Password-Sharing &amp;amp; Fake Lab Recordings
&lt;/h2&gt;

&lt;p&gt;Even when IP restrictions keep traffic inside the campus Wi-Fi or lab subnet, how do we stop Student A from logging into an adjacent lab PC with Student B's shared password, playing a pre-recorded screen video to fake lab work, and clicking Bingo answers for Student B?&lt;/p&gt;

&lt;p&gt;The root flaw of password-based lab sessions is that &lt;strong&gt;credentials are zero-cost to share&lt;/strong&gt;: Student B can give their password to Student A without losing access to anything. By contrast, &lt;strong&gt;no student will surrender their personal physical smartphone (and their Face ID / fingerprint) to a classmate for an entire morning.&lt;/strong&gt; And when a class enables &lt;strong&gt;Mobile Passkey Bingo (&lt;code&gt;mobile_passkey&lt;/code&gt;)&lt;/strong&gt; or the &lt;strong&gt;Passkey Enforcement Gate (&lt;code&gt;PasskeyEnforcementGate.jsx&lt;/code&gt;)&lt;/strong&gt;, every workstation must be unlocked and periodically verified by scanning a per-student challenge QR code with the student's &lt;strong&gt;paired hardware passkey phone&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To prevent Student A from registering both their own account &lt;em&gt;and&lt;/em&gt; Student B's account on Student A's smartphone, we implemented a strict &lt;strong&gt;1-Phone = 1-Student Hardware Lock&lt;/strong&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/passkeyFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/passkeyFlows.js&lt;/code&gt;&lt;/a&gt; using &lt;code&gt;@simplewebauthn/server&lt;/code&gt;. When a student pairs their smartphone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We require &lt;code&gt;authenticatorAttachment: 'platform'&lt;/code&gt;, forcing the mobile browser to generate an asymmetric ECDSA P-256 keypair inside the phone's hardware &lt;strong&gt;Apple Secure Enclave&lt;/strong&gt; (iOS Face ID / Touch ID) or &lt;strong&gt;Android Titan M2&lt;/strong&gt; module—non-exportable and immune to cloning.&lt;/li&gt;
&lt;li&gt;During &lt;code&gt;handleVerifyPasskeyRegistration&lt;/code&gt;, we check two orthogonal hardware identifiers across the entire &lt;code&gt;studentPasskeys&lt;/code&gt; collection:

&lt;ul&gt;
&lt;li&gt;The cryptographic WebAuthn &lt;code&gt;credentialID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The persistent hardware &lt;code&gt;deviceFingerprint&lt;/code&gt; (protecting against a student creating two different browser profiles, using Chrome + Safari, or clearing cookies on the same physical phone to register a classmate's account).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Once paired, students &lt;strong&gt;cannot self-unlink&lt;/strong&gt; their smartphone. Unbinding a phone requires an instructor to invoke &lt;code&gt;handleResetStudentPasskey&lt;/code&gt;, which records the previous &lt;code&gt;credentialID&lt;/code&gt;, &lt;code&gt;deviceModel&lt;/code&gt;, and teacher justification into an append-only &lt;code&gt;passkeyAuditLogs&lt;/code&gt; collection. As a result, a classmate sitting at two lab monitors with a shared password and a looping screen recording is immediately caught on the very first hardware Passkey check!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Production Code: Hardware Collision Detection in &lt;code&gt;passkeyFlows.js&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/ai_flows/passkeyFlows.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleVerifyPasskeyRegistration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;pairingToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;attestationResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;clientRpId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;deviceModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;deviceFingerprint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;auth&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... [Challenge lookup &amp;amp; verifyRegistrationResponse via @simplewebauthn/server] ...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registrationInfo&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;credentialID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credential&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="c1"&gt;// =========================================================================&lt;/span&gt;
  &lt;span class="c1"&gt;// CRITICAL SECURITY ENFORCEMENT: 1 Phone = 1 Student Hardware Lock&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Check persistent deviceFingerprint: A phone cannot be shared across multiple students!&lt;/span&gt;
  &lt;span class="c1"&gt;//    EXCEPTION: Teachers and whitelisted testing accounts can share devices across roles.&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Check WebAuthn credentialID: Credential cannot be shared across multiple students!&lt;/span&gt;
  &lt;span class="c1"&gt;// =========================================================================&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;isIncomingWhitelisted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;userRole&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isPasskeySharingWhitelisted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;studentEmail&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;deviceFingerprint&lt;/span&gt;&lt;span class="p"&gt;)&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;existingDeviceSnap&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;db&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'studentPasskeys'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deviceFingerprint'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deviceFingerprint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;existingDeviceSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for&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;doc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;existingDeviceSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;docs&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;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;)&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;existingData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&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="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&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;boundEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existingData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;studentEmail&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;doc&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;existingRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existingData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;deriveUserRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boundEmail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'student'&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;isExistingWhitelisted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;existingRole&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'teacher'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isPasskeySharingWhitelisted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boundEmail&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isIncomingWhitelisted&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isExistingWhitelisted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="s1"&gt;'already-exists'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="sb"&gt;`Hardware Lock: This physical phone is already bound to student account (${boundEmail}). Each mobile phone can only be used by one student.`&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="p"&gt;}&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;existingSnap&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;db&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'studentPasskeys'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'credentialID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;existingSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&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;doc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;existingSnap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;docs&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;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s1"&gt;'already-exists'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s1"&gt;'This physical mobile device is already registered to another user. Each phone can only be paired with one user account.'&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Persist hardware public key, signature counter (replay protection), and fingerprint&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`studentPasskeys/${studentUid}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;studentEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credentialID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credentialPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deviceFingerprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deviceFingerprint&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="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;counter&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="n"&gt;deviceModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deviceModel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'Mobile Device'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transports&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'internal'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;registeredAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FieldValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serverTimestamp&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="p"&gt;}&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Defeating WhatsApp Relay Attacks: 15-Second Rotating HMAC-SHA256 Projector QRs
&lt;/h2&gt;

&lt;p&gt;In a 200-seat lecture hall where students do not sit at fixed lab desktops, how do we verify attendance in under 2 seconds while defeating photo-forwarding in WhatsApp group chats?&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 2: 15-Second Rotating HMAC Projector QR &amp;amp; Challenge Creation Tolerance
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2k2qte848rnaj9xji21i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2k2qte848rnaj9xji21i.png" alt="1-Phone = 1-Student FIDO2 Lock &amp;amp; 15s Rotating HMAC Projector QR" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Case Solved: The "Mid-Biometric QR Rotation" Race Condition
&lt;/h3&gt;

&lt;p&gt;When we first tested 15-second rotating QR codes with 150 students in a lecture theatre, we hit a subtle race condition:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A student scans the projector QR code at second &lt;code&gt;t = 14.2s&lt;/code&gt; of window &lt;code&gt;W_1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getLecturePasskeyAuthOptions&lt;/code&gt; validates &lt;code&gt;W_1&lt;/code&gt; and triggers the phone's Face ID / fingerprint prompt.&lt;/li&gt;
&lt;li&gt;Because the student's hands are damp or they adjust their glasses, their biometric scan takes &lt;code&gt;4.5 seconds&lt;/code&gt;—completing at &lt;code&gt;t = 18.7s&lt;/code&gt; (now in window &lt;code&gt;W_2&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If we shorten the grace window to block WhatsApp forwarding, a naive token check during &lt;code&gt;verifyLecturePasskeyAuth&lt;/code&gt; rejects the legitimate in-room student because the projector QR rotated while Face ID was scanning!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We solved this in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/passkeyFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/passkeyFlows.js&lt;/code&gt;&lt;/a&gt; with &lt;strong&gt;Two-Phase Challenge Creation Tolerance&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1 (&lt;code&gt;handleGetLecturePasskeyAuthOptions&lt;/code&gt;)&lt;/strong&gt;: When the camera scans the QR code, the server immediately validates the HMAC token using &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt; and writes a cryptographically random 2-minute WebAuthn challenge document (&lt;code&gt;bingoRecords/{bingoId}/challenges/{challengeId}&lt;/code&gt;) stamped with &lt;code&gt;createdAt: FieldValue.serverTimestamp()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2 (&lt;code&gt;handleVerifyLecturePasskeyAuth&lt;/code&gt;)&lt;/strong&gt;: When the phone submits the signed hardware assertion a few seconds later, we verify whether the token is currently valid &lt;strong&gt;OR&lt;/strong&gt; was valid at &lt;code&gt;challengeCreatedAtMillis&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From functions/ai_flows/passkeyFlows.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeLectureQrToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeInterval&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&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;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`lecture_qr_bingo_${timeInterval}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hex'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidLectureQrToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="n"&gt;rotationIntervalMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_LECTURE_QR_ROTATION_INTERVAL_MS&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;sessionSecret&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;16&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&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;intervalMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rotationIntervalMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rotationIntervalMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&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;currentInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;intervalMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="no"&gt;LECTURE_QR_GRACE_INTERVALS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&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;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeLectureQrToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentInterval&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&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;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="n"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Inside handleVerifyLecturePasskeyAuth:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;challengeCreatedAtMillis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;challengeData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toMillis&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'function'&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;challengeData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toMillis&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;isTokenCurrentlyValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isValidLectureQrToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;bingoData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tokenToVerify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="n"&gt;rotationIntervalMs&lt;/span&gt;
&lt;span class="p"&gt;)&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;isTokenValidAtChallengeCreation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isValidLectureQrToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;bingoData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sessionSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tokenToVerify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;challengeCreatedAtMillis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;rotationIntervalMs&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isTokenCurrentlyValid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isTokenValidAtChallengeCreation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'invalid-argument'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'The scanned QR token has expired. Please scan the current code on the screen.'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees that an in-room student who scanned a live QR code never fails if the projector rotates while their biometric prompt is open, whereas a remote student receiving a forwarded WhatsApp screenshot after the grace window cannot even initiate Phase 1.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Keylogger-Proof Shared Lab PC Login &amp;amp; 3-Tier Emergency Bypass
&lt;/h2&gt;

&lt;p&gt;How do teachers and students sign into untrusted shared lab workstations without touching the physical keyboard?&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 3: Zero-Keystroke Lab PC Login &amp;amp; 3-Tier Emergency Recovery
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26cdgwgoy7vwq5gtimns.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26cdgwgoy7vwq5gtimns.png" alt="Keylogger-Proof Shared Lab PC Login &amp;amp; 3-Tier Emergency Recovery" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-Keystroke Custom Token Exchange (&lt;code&gt;handleVerifyDesktopLoginPasskey&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;When a teacher or student walks up to a lab PC and clicks &lt;strong&gt;&lt;code&gt;📱 Scan QR Code&lt;/code&gt;&lt;/strong&gt;, the workstation calls &lt;code&gt;initiateDesktopLoginSession&lt;/code&gt;, creating an ephemeral document at &lt;code&gt;loginSessions/{sessionId}&lt;/code&gt; with a 256-bit &lt;code&gt;sessionSecret&lt;/code&gt; and listening to it via Firestore &lt;code&gt;onSnapshot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the user scans the PC monitor with their paired phone and completes Face ID, &lt;code&gt;handleVerifyDesktopLoginPasskey&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verifies the rotating desktop QR HMAC token (&lt;code&gt;desktop_login_qr_${timeInterval}&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Looks up the hardware &lt;code&gt;credentialID&lt;/code&gt; in &lt;code&gt;studentPasskeys&lt;/code&gt; and verifies the cryptographic signature and replay counter (&lt;code&gt;verification.authenticationInfo.newCounter&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Dynamically resolves the user's role (&lt;code&gt;teacher&lt;/code&gt; vs. &lt;code&gt;student&lt;/code&gt;) and mints a short-lived Firebase Custom Token via &lt;code&gt;getAuth().createCustomToken(studentUid, { role: detectedRole })&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Writes &lt;code&gt;{ status: 'authorized', customToken }&lt;/code&gt; to &lt;code&gt;loginSessions/{sessionId}&lt;/code&gt;. The desktop browser immediately calls &lt;code&gt;signInWithCustomToken(auth, customToken)&lt;/code&gt;—signing the instructor into the &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; command center with &lt;strong&gt;zero keystrokes typed on the lab keyboard&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Solving Real-World Classroom Hardware Realities: The 3-Tier Emergency Bypass
&lt;/h3&gt;

&lt;p&gt;In a cohort of 150 students, every single week at least two students will arrive with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dead phone battery (&lt;code&gt;0%&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;A cracked camera lens that cannot focus on a QR code, or&lt;/li&gt;
&lt;li&gt;A non-GMS Android handset (e.g., certain Huawei / HarmonyOS or custom ROM devices) that lacks Google Credential Manager support for WebAuthn passkeys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If security blocks teaching, instructors will turn security off. We engineered three frictionless, fully audited recovery tiers in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/passkeyFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/passkeyFlows.js&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier A — 1-Click Podium Bypass (&lt;code&gt;handleApproveTeacherPasskeyBypass&lt;/code&gt;)&lt;/strong&gt;: The student clicks &lt;em&gt;"Request Teacher Bypass"&lt;/em&gt; on their lab PC (&lt;code&gt;handleRequestTeacherPasskeyBypass&lt;/code&gt;), entering their desk number. A live toast appears on the instructor's podium dashboard; one click grants a 180-minute lesson-scoped bypass in &lt;code&gt;classes/{classId}/studentProperties/{uid}.passkeyBypass&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier B — Aisle Walk-Around 6-Digit PIN (&lt;code&gt;handleVerifyTeacherPasskeyBypassPin&lt;/code&gt;)&lt;/strong&gt;: While walking the lab aisles, the instructor can type their daily 6-digit emergency PIN directly onto the student's screen to unlock attendance for that session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier C — Non-GMS Device Exemption &amp;amp; Phone Replacement (&lt;code&gt;handleResetStudentPasskey&lt;/code&gt;)&lt;/strong&gt;: For students who buy a new phone or use a non-GMS device, the teacher can reset their hardware binding or toggle a roster exemption—with every action permanently recorded in &lt;code&gt;passkeyAuditLogs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Security &amp;amp; Performance Benchmarks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Authentication &amp;amp; Presence Flow&lt;/th&gt;
&lt;th&gt;Median End-to-End Latency&lt;/th&gt;
&lt;th&gt;Keystrokes on Shared Lab PC&lt;/th&gt;
&lt;th&gt;WhatsApp Photo Relay Vulnerable?&lt;/th&gt;
&lt;th&gt;Proxy Account Sharing Vulnerable?&lt;/th&gt;
&lt;th&gt;Cloud Cost per 100 Verifications&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Legacy Static QR / Password Form&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~12.4 s&lt;/td&gt;
&lt;td&gt;25–40 keys&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;Yes&lt;/strong&gt; (Instant group forward)&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;Yes&lt;/strong&gt; (Shared password)&lt;/td&gt;
&lt;td&gt;~$0.0001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;In-Class Routine Passkey Bingo (&lt;code&gt;verifyPasskeyAuth&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.6 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;No&lt;/strong&gt; (Per-student challenge)&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;No&lt;/strong&gt; (&lt;code&gt;1-Phone = 1-Student&lt;/code&gt; lock)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.0002&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;200-Seat Lecture Hall Rotating QR (&lt;code&gt;verifyLecturePasskeyAuth&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.8 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;No&lt;/strong&gt; (15s HMAC + Challenge Tolerance)&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;No&lt;/strong&gt; (&lt;code&gt;credentialID&lt;/code&gt; + &lt;code&gt;deviceFingerprint&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.0003&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shared Lab PC QR Login (&lt;code&gt;verifyDesktopLoginPasskey&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.1 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;No&lt;/strong&gt; (15s HMAC + Physical monitor scan)&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;No&lt;/strong&gt; (Hardware Secure Enclave)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.0003&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never Trust Client-Side Role Claims&lt;/strong&gt;: Use GCIP &lt;code&gt;beforeUserCreated&lt;/code&gt; blocking functions with a deterministic hierarchy (Pre-Enrolled Roster $\rightarrow$ Username Regex $\rightarrow$ Subdomain Priority $\rightarrow$ Zero-Trust Student Default) so unauthorized users can never access high-quota &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; teacher flows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce &lt;code&gt;1-Phone = 1-Student&lt;/code&gt; at the Hardware Layer&lt;/strong&gt;: Combining WebAuthn platform authenticators (&lt;code&gt;credentialID&lt;/code&gt;) with a persistent &lt;code&gt;deviceFingerprint&lt;/code&gt; check in Firestore stops students from logging into a classmate's account on their own phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stamp Token Validity at Challenge Creation&lt;/strong&gt;: When pairing 15-second rotating HMAC QR codes with biometric prompts, validating the QR token at &lt;em&gt;challenge creation time&lt;/em&gt; eliminates false rejections when the projector rotates mid-Face ID scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Audited Human Escape Hatches&lt;/strong&gt;: A 3-tier teacher bypass (1-click podium approval, aisle PIN, and phone-replacement reset) keeps classes moving smoothly when a student's battery dies, without weakening cryptographic enforcement for the rest of the cohort.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>security</category>
      <category>passkeys</category>
      <category>webauthn</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Google AI Classroom Part 3: Frontier Multimodal Orchestration — Right-Sizing Gemini 4 &amp; On-Device Gemma 4 E2B with Google Genkit</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Tue, 10 Nov 2026 06:12:34 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-3-multimodal-orchestration-right-sizing-gemini-4-argon-gemini-38-538b</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-3-multimodal-orchestration-right-sizing-gemini-4-argon-gemini-38-538b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;  &lt;/p&gt;




&lt;p&gt;When we run an AI-powered computer science or engineering classroom for 40 to 100+ concurrent students, supporting &lt;strong&gt;live teaching, inclusive learning, student performance evaluation, and proctored exams&lt;/strong&gt; requires radically different latency, context-window, and FinOps profiles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every 16ms–3s on the Student's Laptop (Real-Time Proctored Exam Investigation)&lt;/strong&gt;: During a practical test or examination, a human invigilator cannot watch 40+ screens and webcams simultaneously, and calling cloud APIs every second for 40 seats would require 432,000+ cloud calls per exam. High-frequency posture, gaze, and speech-intent investigation &lt;strong&gt;must&lt;/strong&gt; run locally in the browser via &lt;strong&gt;MediaPipe&lt;/strong&gt;, &lt;strong&gt;LiteRT Whisper-Tiny&lt;/strong&gt;, and &lt;strong&gt;LiteRT-LM Gemma 4 E2B (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;)&lt;/strong&gt; at &lt;strong&gt;$0.00 API cost&lt;/strong&gt;, immediately uploading synchronized screen + webcam + audio evidence when an irregularity is confirmed.
  &lt;iframe src="https://www.youtube.com/embed/WneUu4y9KUM" width="710" height="399"&gt;
  &lt;/iframe&gt;

  &lt;iframe src="https://www.youtube.com/embed/kVSoIame36o" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every 4 Seconds at the Lecture Podium (Inclusive Teaching &amp;amp; Live Comprehension Checks)&lt;/strong&gt;: When a lecturer speaks Cantonese mixed with English Kubernetes/React jargon (&lt;em&gt;"呢個 Pod 個 Readiness Probe fail 咗"&lt;/em&gt;), students need 9-language subtitles on their screens in under 800 milliseconds—making &lt;strong&gt;Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt; ($0.25 In / $1.50 Out per 1M tokens) or on-device &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; ideal.
  &lt;iframe src="https://www.youtube.com/embed/r1dZXo-2lU4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard Student Performance Video Grading &amp;amp; Whole-Lecture CC Generation&lt;/strong&gt;: Evaluating a 45-minute 1-FPS student lab recording against a practical task rubric or generating bilingual subtitles and YouTube study chapters from a 3-hour Opus lecture audio stream requires balanced multimodal throughput—where &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt; ($0.50 In / $3.00 Out per 1M tokens) shines.
  &lt;iframe src="https://www.youtube.com/embed/u6P2XklMR3c" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Rubric Synthesis, Multi-Step Code Evaluation &amp;amp; Capstone Supervision (Frontier Reasoning)&lt;/strong&gt;: When a teacher wants to watch an instructor demonstration video and synthesize a partial-credit grading rubric, evaluate complex student coding performance, audit 60 weekly capstone logs against live 720p mobile hardware demos, or meta-optimize a JSON-schema prompt, the workload demands &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; ($2.00 In / $10.00 Out per 1M tokens) with its 1-million-token shared multimodal context window and deep reasoning capabilities.
  &lt;iframe src="https://www.youtube.com/embed/qHmExRv6k-Y" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the arrival of &lt;strong&gt;Gemini 4 (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; in Vertex AI alongside &lt;strong&gt;Gemma 4 E2B (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;)&lt;/strong&gt; in the browser via LiteRT-LM WebGPU, we engineered our &lt;strong&gt;Google Genkit&lt;/strong&gt; orchestration layer in &lt;code&gt;functions/ai_flows/&lt;/code&gt; so teachers can pick &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; across every single classroom workflow—while automatically enforcing parameter sanitization, exponential backoff with jitter, cross-model failover, and real-time FinOps quota governance.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architectural Deep-Dive: The 5-Model Edge-to-Cloud Right-Sizing Matrix
&lt;/h2&gt;

&lt;p&gt;Rather than hardcoding a single model ID across our backend, we built a &lt;strong&gt;5-Model Right-Sizing Architecture&lt;/strong&gt; spanning Tier-1 Browser WebGPU Workers and Tier-4 Google Genkit Cloud Functions. Teachers can configure a class-wide default model in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/ClassManagement.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ClassManagement.jsx&lt;/code&gt;&lt;/a&gt; and override it on the fly inside any feature studio:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F72h8ypalgqai5hhi719f.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F72h8ypalgqai5hhi719f.png" alt="5-Model Edge-to-Cloud Right-Sizing Architecture (Gemini 4 &amp;amp; Gemma 4)" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete Matrix of Teacher-Selectable Gemini 4 Surfaces
&lt;/h3&gt;

&lt;p&gt;Every AI-powered workflow in the repository exposes &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Classroom Workflow&lt;/th&gt;
&lt;th&gt;Frontend Selector Component&lt;/th&gt;
&lt;th&gt;Backend Cloud Function / Genkit Flow&lt;/th&gt;
&lt;th&gt;Recommended Model Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Class-Wide Default &amp;amp; Live Vision Monitor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/ClassManagement.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ClassManagement.jsx&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/monitor/ControlsPanel.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ControlsPanel.jsx&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;analyzeImageFlow&lt;/code&gt;&lt;/a&gt; / &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;analyzeAllImagesFlow&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; for instant classroom peeks; switch to &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; for deep code inspection on a student's IDE screenshot.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Practical Task Rubric Synthesis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;#task-ai-model-select&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/extractTaskDemoSteps.js" rel="noopener noreferrer"&gt;&lt;code&gt;extractTaskDemoSteps&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; to watch your reference demo video and synthesize nuanced partial-credit marking criteria and visual proof requirements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Practical Task Video Auto-Grading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskGradingMatrixView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskGradingMatrixView.jsx&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;Grading AI Model&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/evaluateTaskSubmission.js" rel="noopener noreferrer"&gt;&lt;code&gt;evaluateTaskSubmission&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Defaults to &lt;code&gt;taskData.model&lt;/code&gt;; override with &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; in the Grading Matrix toolbar for complex multi-step debugging labs or exam appeals.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. After-Class Cohort Video Map-Reduce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoLibrary.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoLibrary.jsx&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/VideoAnalysisJobs.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;VideoAnalysisJobs.jsx&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;analyzeSingleVideoFlow&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/generateLabTaskPrompt.js" rel="noopener noreferrer"&gt;&lt;code&gt;generateLabTaskPrompt&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;gemini-3.8-flash&lt;/code&gt; for Stage-1 per-student video logs and &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; for Stage-2 cohort rubric discovery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Whole-Lecture Subtitles (CC) &amp;amp; YouTube Chapters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/ClassManagement.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ClassManagement.jsx&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/LectureRecordingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;LectureRecordingsView.jsx&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/processLectureSubtitles.js" rel="noopener noreferrer"&gt;&lt;code&gt;processLectureSubtitles&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Single-pass pure Opus audio ingestion (&lt;code&gt;thinkingBudget: 0&lt;/code&gt;, &lt;code&gt;maxOutputTokens: 65536&lt;/code&gt;) with &lt;code&gt;gemini-3.8-flash&lt;/code&gt; or &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6. Capstone Project Meeting Supervision&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/meetings/ProjectMeetingsView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;ProjectMeetingsView.jsx&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/projectMeetingFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;analyzeProjectMeeting&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Stage 1 diarizes audio via &lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt;; Stage 2 uses &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; or &lt;code&gt;gemini-3.8-flash&lt;/code&gt; to cross-examine weekly logs against 720p mobile demo videos and photos.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7. Bingo Question Bank AI Generator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/BingoQuestionBankModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;BingoQuestionBankModal.jsx&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;Bingo AI Model&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/bingoFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;generateBingoQuestionBank&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; for fast recall MCQs or &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; for scenario-based architecture diagnostic questions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;8. AI Prompt Studio Meta-Optimizer (&lt;code&gt;✨ Optimize&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/prompt/PromptForm.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;PromptForm.jsx&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/PromptManagement.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;PromptManagement.jsx&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;analysisFlows.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Select &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt; next to &lt;code&gt;Optimize&lt;/code&gt; to rewrite rough instructor grading notes into schema-safe system prompts while locking &lt;code&gt;{{variable}}&lt;/code&gt; tags.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9. Live Subtitles &amp;amp; Multilingual Captions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/subtitles/TeacherSubtitleControlModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TeacherSubtitleControlModal.jsx&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/subtitleFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;translateTeacherSpeech&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertGemma.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertGemma.worker.js&lt;/code&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Toggle between $0.00 on-device &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; (&lt;code&gt;client&lt;/code&gt; mode) and Cloud Functions (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; / &lt;code&gt;gemini-3.8-flash&lt;/code&gt; / &lt;code&gt;gemini-4-argon&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  3. Implementation Walkthrough 1: Inside &lt;code&gt;generateWithResilience&lt;/code&gt; — Gemini 4 Parameter Sanitization &amp;amp; Self-Healing Failover
&lt;/h2&gt;

&lt;p&gt;One of the most important engineering lessons when adopting &lt;strong&gt;Gemini 4 (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt; in production is that frontier reasoning models enforce stricter API parameter contracts than earlier generation models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict Parameter Contract on &lt;code&gt;gemini-4-*&lt;/code&gt;&lt;/strong&gt;: Passing legacy sampling parameters (&lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;topP&lt;/code&gt;, &lt;code&gt;top_p&lt;/code&gt;, &lt;code&gt;topK&lt;/code&gt;, &lt;code&gt;top_k&lt;/code&gt;) inside &lt;code&gt;config&lt;/code&gt; to &lt;code&gt;gemini-4-argon&lt;/code&gt; throws an immediate &lt;code&gt;400 INVALID_ARGUMENT&lt;/code&gt; error because Gemini 4 manages internal reasoning sampling natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transient Overload &amp;amp; Quota Spikes in Live Labs&lt;/strong&gt;: When 40 students submit their practical exam videos simultaneously or a teacher triggers &lt;code&gt;analyzeAllImagesFlow&lt;/code&gt; across 40 screens with up to &lt;code&gt;maxToolRoundtrips = numScreenshots * 3&lt;/code&gt;, regional Vertex AI endpoints can return &lt;code&gt;429 RESOURCE_EXHAUSTED&lt;/code&gt;, &lt;code&gt;503 UNAVAILABLE&lt;/code&gt;, or &lt;code&gt;ECONNRESET&lt;/code&gt;. The pipeline must never fail a student's submission—it must retry with exponential backoff + random jitter and seamlessly fail over to &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; if the primary model is exhausted.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ds24jwybp2q7inzalbm.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ds24jwybp2q7inzalbm.png" alt="Genkit Resilience Flow" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the exact sequence executed by &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;generateWithResilience&lt;/code&gt;&lt;/a&gt; for every Genkit invocation across our backend:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffa47v8h5v799rt3el2n2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffa47v8h5v799rt3el2n2.png" alt="Genkit generateWithResilience: Gemini 4 Sanitization &amp;amp; Self-Healing Failover" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code Listing 1: &lt;code&gt;generateWithResilience&lt;/code&gt; in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/analysisFlows.js&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Here is the production implementation in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/analysisFlows.js&lt;/code&gt;&lt;/a&gt; showing how we strip unsupported sampling keys when &lt;code&gt;modelName.startsWith('gemini-4')&lt;/code&gt; and handle network/quota retries before failing over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Executes AI generation with automatic exponential backoff, jitter,
 * connection reset recovery, and fallback to flash-lite if the primary model is overloaded.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;preferredModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;fallbackModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gemini-3.5-flash-lite'&lt;/span&gt;
&lt;span class="p"&gt;)&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;modelsToTry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;preferredModel&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;fallbackModel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="n"&gt;fallbackModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;modelsToTry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fallbackModel&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="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lastError&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;for&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;modelName&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;modelsToTry&lt;/span&gt;&lt;span class="p"&gt;)&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;maxAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;effectiveGenerateConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generateConfig&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="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;modelName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'gemini-4'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'object'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;topP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;top_p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_tp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;topK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_tk&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;restConfig&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="n"&gt;effectiveGenerateConfig&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;restConfig&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="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;response&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;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effectiveGenerateConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertexAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelName&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="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="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelName&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;lastError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;err&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&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;isRetryable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'overloaded'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'connection reset'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ECONNRESET'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ETIMEDOUT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fetch failed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'socket'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'timeout'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'503'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'429'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'504'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RESOURCE_EXHAUSTED'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'UNAVAILABLE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
          &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'INTERNAL'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`[AI Resilience] Attempt ${attempt}/${maxAttempts} for model ${modelName} encountered: ${msg}`&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isRetryable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&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;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;)&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;backoffMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;backoffMs&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="p"&gt;}&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="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;lastError&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;h2&gt;
  
  
  4. Implementation Walkthrough 2: Two-Stage Audio Diarization &amp;amp; Multimodal Rubric Synthesis
&lt;/h2&gt;

&lt;p&gt;Two of the best examples of &lt;strong&gt;model right-sizing&lt;/strong&gt; in our codebase are &lt;strong&gt;Two-Stage Audio Invigilation (&lt;code&gt;analyzeAudioFlow&lt;/code&gt;)&lt;/strong&gt; and &lt;strong&gt;Automated Demo Video Rubric Synthesis (&lt;code&gt;extractTaskDemoSteps&lt;/code&gt;)&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Two-Stage Audio (&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; + Reasoning Model) Beats Single-Pass Audio Tool Calling
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/analysisFlows.js" rel="noopener noreferrer"&gt;&lt;code&gt;analyzeAudioFlow&lt;/code&gt;&lt;/a&gt;, we discovered that passing complex Genkit tool definitions (&lt;code&gt;recordAudioIrregularity&lt;/code&gt;, &lt;code&gt;recordAudioAudit&lt;/code&gt;, &lt;code&gt;sendMessageToStudent&lt;/code&gt;) directly to an acoustic Speech-to-Text model degrades transcription fidelity on Cantonese/English code-switching. Instead, we split the pipeline into two specialized stages—and if the student's browser already transcribed the audio via &lt;strong&gt;LiteRT Whisper-Tiny&lt;/strong&gt;, Stage 1 is skipped altogether (&lt;code&gt;suppliedTranscript&lt;/code&gt;), saving 100% of audio input tokens!&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code Listing 2: Multimodal Reference Video Rubric Synthesis in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/extractTaskDemoSteps.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/extractTaskDemoSteps.js&lt;/code&gt;&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;When creating a hands-on practical assignment in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/tasks/TaskEditorModal.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;TaskEditorModal.jsx&lt;/code&gt;&lt;/a&gt;, an instructor can attach a YouTube URL, upload an MP4 demo, or select a recorded lecture, and choose &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Gemini 3.8 Flash (&lt;code&gt;gemini-3.8-flash&lt;/code&gt;)&lt;/strong&gt;, or &lt;strong&gt;Gemini 3.5 Flash-Lite (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* web-app/src/components/tasks/TaskEditorModal.jsx */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="n"&gt;htmlFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"task-ai-model-select"&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"block text-xs font-semibold text-gray-700 mb-1"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    🤖 &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nc"&gt;Rubric&lt;/span&gt; &lt;span class="nc"&gt;Synthesis&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Grading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"task-ai-model-select"&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;aiModel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&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;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setAiModel&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="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"w-full px-3 py-2 border border-purple-300 rounded-lg bg-white text-gray-900 text-sm font-medium"&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-4-argon"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;🧠 &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;Argon&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Flagship&lt;/span&gt; &lt;span class="nc"&gt;Reasoning&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;1M&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; — &lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="mi"&gt;1M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-3.8-flash"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;✨ &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="mf"&gt;3.8&lt;/span&gt; &lt;span class="nc"&gt;Flash&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Recommended&lt;/span&gt; &lt;span class="nc"&gt;Multimodal&lt;/span&gt; — &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="mi"&gt;1M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-3.5-flash-lite"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;⚡ &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="mf"&gt;3.5&lt;/span&gt; &lt;span class="nc"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Lite&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Fastest&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Low&lt;/span&gt; &lt;span class="nc"&gt;Cost&lt;/span&gt; — &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="mi"&gt;1M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the backend, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/extractTaskDemoSteps.js" rel="noopener noreferrer"&gt;&lt;code&gt;extractTaskDemoSteps.js&lt;/code&gt;&lt;/a&gt; resolves Cloud Storage (&lt;code&gt;gs://&lt;/code&gt;) or YouTube URIs, invokes &lt;code&gt;generateWithResilience&lt;/code&gt; with &lt;code&gt;responseMimeType: 'application/json'&lt;/code&gt;, and normalizes the extracted milestone steps, expected visual proof, and partial-credit marking criteria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// functions/ai_flows/extractTaskDemoSteps.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'gemini-3.8-flash'&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;promptParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;metaPrompt&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolvedVideoUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;promptParts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resolvedVideoUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'video/mp4'&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="p"&gt;;&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;generateConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptParts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Automatically stripped by generateWithResilience if preferredModel === 'gemini-4-argon'&lt;/span&gt;
    &lt;span class="n"&gt;responseMimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&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="p"&gt;}&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithResilience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preferredModel&lt;/span&gt;&lt;span class="p"&gt;)&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;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt; &lt;span class="o"&gt;||&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modelUsed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;preferredModel&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;h2&gt;
  
  
  5. Implementation Walkthrough 3: Three-Tier Prompt Governance &amp;amp; Modality-Specific Meta-Prompting (&lt;code&gt;✨ Optimize&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Hardcoding system prompts inside backend code prevents lecturers from adapting AI grading to their specific syllabus (e.g., Cybersecurity Penetration Testing vs. Frontend React Development). At the same time, writing JSON-schema-safe prompts with required runtime template placeholders (&lt;code&gt;{{transcript}}&lt;/code&gt;, &lt;code&gt;{{captureInterval}}&lt;/code&gt;, &lt;code&gt;{{studentEmail}}&lt;/code&gt;) is error-prone for busy educators.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp25klz4unwehr9b73d3x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp25klz4unwehr9b73d3x.png" alt="Teacher AI Prompt Studio" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We solved this in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/prompt/PromptForm.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;PromptForm.jsx&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;promptRegistry.js&lt;/code&gt; with a &lt;strong&gt;Three-Tier Prompt Governance Architecture&lt;/strong&gt; and an in-place &lt;strong&gt;Gemini 4 Prompt Optimizer&lt;/strong&gt;:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cnhr9vh4vcw1rjujc5x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5cnhr9vh4vcw1rjujc5x.png" alt="3-Tier Prompt Governance &amp;amp; Gemini 4 Meta-Prompt Optimizer (✨ Optimize)" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code Listing 3: Optimizer Model Picker &amp;amp; Required Placeholder Validation in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/prompt/PromptForm.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;web-app/src/components/prompt/PromptForm.jsx&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/prompt/PromptForm.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;PromptForm.jsx&lt;/code&gt;&lt;/a&gt;, teachers can switch between &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/strong&gt; right next to the &lt;strong&gt;Optimize&lt;/strong&gt; button, while interactive placeholder chips (&lt;code&gt;ph.required&lt;/code&gt;) warn the instructor if any mandatory runtime interpolation variable is missing before saving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* web-app/src/components/prompt/PromptForm.jsx */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;setOptimizerModel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;
        &lt;span class="n"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Prompt Optimizer Model"&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;optimizerModel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&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;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setOptimizerModel&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="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;isOptimizing&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;isReadOnly&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"optimizer-model-select"&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Select Gemini model for prompt optimization"&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-4-argon"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;🧠 &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;Argon&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-3.8-flash"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;✨ &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="mf"&gt;3.8&lt;/span&gt; &lt;span class="nc"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-3.5-flash-lite"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;⚡ &lt;span class="nc"&gt;Gemini&lt;/span&gt; &lt;span class="mf"&gt;3.5&lt;/span&gt; &lt;span class="nc"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Lite&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; 
    &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt; 
    &lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;handleOptimize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;isOptimizing&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;isReadOnly&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Optimize prompt with Gemini"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;isOptimizing&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'Optimizing...'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Optimize'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;handleUndo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;originalPromptText&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;isReadOnly&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"secondary-btn"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Undo&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. FinOps Governance: Dynamic Cloud Billing SKU Sync &amp;amp; Composite Pipeline Cost Accounting
&lt;/h2&gt;

&lt;p&gt;When different teachers across a university department experiment with &lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/strong&gt;, and two-stage composite pipelines (&lt;code&gt;gemini-3.5-transcribe-preview + gemini-3.5-flash-lite&lt;/code&gt;), institutional cloud administrators need &lt;strong&gt;micro-dollar cost attribution&lt;/strong&gt; and &lt;strong&gt;hard per-class budget caps&lt;/strong&gt; (&lt;code&gt;aiQuota&lt;/code&gt; vs. &lt;code&gt;aiUsedQuota&lt;/code&gt;).&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4vbi5afgro3b4ncymc2w.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4vbi5afgro3b4ncymc2w.png" alt="AI Cost &amp;amp; FinOps Governance" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code Listing 4: Multi-SDK Token Normalization &amp;amp; Composite Pipeline Pricing in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/cost.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/cost.js&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/functions/ai_flows/cost.js" rel="noopener noreferrer"&gt;&lt;code&gt;functions/ai_flows/cost.js&lt;/code&gt;&lt;/a&gt;, we maintain a warm 12-hour in-memory pricing cache synced from &lt;code&gt;system_config/pricing&lt;/code&gt; (populated daily by our Cloud Billing Catalog API job &lt;code&gt;syncGeminiPricing&lt;/code&gt;), normalize token property variations across Genkit, Google GenAI SDK, and Vertex AI (&lt;code&gt;promptTokenCount&lt;/code&gt; vs. &lt;code&gt;inputTokens&lt;/code&gt;), and compute exact USD costs down to &lt;code&gt;$0.000001&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Default baseline pricing lookup table for Gemini models in USD per 1 million tokens (Input / Output)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;MODEL_PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-4-argon'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-flash-lite'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.8-flash'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.7-flash'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.7-pro'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-transcribe'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-transcribe-preview'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.5-transcribe-live'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.00&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'gemini-3.1-flash-live-preview'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&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="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_MODEL&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;usageMetadata&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;usageMetadata&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'object'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;pricing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getModelPricing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&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;promptTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promptTokenCount&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promptTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputTokenCount&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="p"&gt;)&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;candidatesTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidatesTokenCount&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completionTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidatesTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
      &lt;span class="n"&gt;usageMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputTokenCount&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="p"&gt;)&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;inputRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="no"&gt;MODEL_PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_MODEL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input&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;outputRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pricing&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="no"&gt;MODEL_PRICING&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_MODEL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&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;inputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inputRate&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;outputCost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidatesTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;outputRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputCost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;outputCost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&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="mi"&gt;0&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;Before any Genkit flow invokes Vertex AI, &lt;code&gt;checkQuota(classId, estimatedCost)&lt;/code&gt; verifies that the class's &lt;code&gt;aiUsedQuota&lt;/code&gt; has not exceeded its allocated &lt;code&gt;aiQuota&lt;/code&gt; budget. Once the call completes, &lt;code&gt;logJob()&lt;/code&gt; writes an immutable audit record to &lt;code&gt;aiJobs/{jobId}&lt;/code&gt; with the &lt;strong&gt;actual&lt;/strong&gt; model used (&lt;code&gt;actualModel&lt;/code&gt; returned by &lt;code&gt;generateWithResilience&lt;/code&gt;), and an atomic Firestore trigger (&lt;code&gt;onAiJobCreated&lt;/code&gt; in &lt;code&gt;quotaTriggers.js&lt;/code&gt;) increments &lt;code&gt;classes/{classId}.aiUsedQuota&lt;/code&gt; via &lt;code&gt;FieldValue.increment(cost)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. FinOps &amp;amp; Latency Benchmark Table Across the 5-Model Constellation
&lt;/h2&gt;

&lt;p&gt;Here is how our 5-model architecture performs in production across real HKIIT classroom workloads in &lt;code&gt;asia-east2&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model Identifier&lt;/th&gt;
&lt;th&gt;Execution Tier&lt;/th&gt;
&lt;th&gt;Input / Output Rate (per 1M Tokens)&lt;/th&gt;
&lt;th&gt;Median Latency&lt;/th&gt;
&lt;th&gt;Typical Classroom Workload Cost&lt;/th&gt;
&lt;th&gt;Best Pedagogical Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tier 1: Browser WebGPU (&lt;code&gt;@litert-lm/core&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00 / $0.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;420 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0.0000&lt;/strong&gt; (Unlimited)&lt;/td&gt;
&lt;td&gt;Continuous exam speech-intent proctoring &amp;amp; $0.00 client-mode live lecture subtitles.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tier 4: Vertex AI Genkit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.30 / $2.50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;620 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.00018 / call&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sub-second 7-language live subtitles, Bingo screen verification &amp;amp; Cloud face fallback.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tier 4: Vertex AI Genkit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.50 / $2.50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.1 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.00045 / min&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stage-1 multi-speaker Cantonese/Mandarin/English capstone consultation diarization.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemini-3.8-flash&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tier 4: Vertex AI Genkit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.75 / $3.75&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.8 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.0104 / student video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default practical task video grading &amp;amp; single-pass 3-hour Opus lecture CC generation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemini-4-argon&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tier 4: Vertex AI Genkit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2.00 / $10.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.6 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.0370 / student video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reference video rubric synthesis, complex multi-step code grading, cohort Map-Reduce discovery &amp;amp; Prompt Studio meta-optimization.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  8. Key Takeaways for Google Cloud &amp;amp; Genkit Builders
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sanitize Legacy Sampling Configs for &lt;code&gt;gemini-4-*&lt;/code&gt;&lt;/strong&gt;: Wrap all Genkit &lt;code&gt;ai.generate()&lt;/code&gt; calls in a central middleware (&lt;code&gt;generateWithResilience&lt;/code&gt;) that automatically strips &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;topP&lt;/code&gt;, and &lt;code&gt;topK&lt;/code&gt; whenever &lt;code&gt;modelName.startsWith('gemini-4')&lt;/code&gt;—allowing teachers to switch between Gemini 3.8 and Gemini 4 Argon without breaking shared flow code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always Record &lt;code&gt;modelUsed&lt;/code&gt; After Failover&lt;/strong&gt;: If a primary &lt;code&gt;gemini-4-argon&lt;/code&gt; request fails over to &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; during a regional quota spike, pass the &lt;em&gt;actual&lt;/em&gt; &lt;code&gt;modelUsed&lt;/code&gt; returned by &lt;code&gt;generateWithResilience&lt;/code&gt; into &lt;code&gt;calculateCost()&lt;/code&gt; and &lt;code&gt;logJob()&lt;/code&gt; so class FinOps quotas are billed at the true underlying model rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple Acoustic Diarization from Tool-Calling Reasoning&lt;/strong&gt;: Using &lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; (without tools) for verbatim multi-speaker transcription and passing the resulting text to &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;, &lt;code&gt;gemini-3.8-flash&lt;/code&gt;, or &lt;code&gt;gemini-4-argon&lt;/code&gt; (with tools) eliminates tool-call hallucinations on bilingual audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empower Educators with Guardrailed Prompt Engineering&lt;/strong&gt;: Combining a 3-tier prompt resolution hierarchy with an in-app &lt;strong&gt;Gemini 4 Argon (&lt;code&gt;✨ Optimize&lt;/code&gt;)&lt;/strong&gt; meta-optimizer and required &lt;code&gt;{{placeholder}}&lt;/code&gt; chip validation lets non-programmer faculty safely customize AI rubrics without breaking downstream JSON parsers.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>googlecloud</category>
      <category>genkit</category>
      <category>gemini</category>
      <category>ai</category>
    </item>
    <item>
      <title>Google AI Classroom Part 2: Zero-Egress Browser Edge AI Investigator — Running Gemma 4 E2B, MediaPipe 3D Face Mesh &amp; LiteRT Whisper in Web Workers</title>
      <dc:creator>Cyrus Wong</dc:creator>
      <pubDate>Tue, 10 Nov 2026 02:57:14 +0000</pubDate>
      <link>https://dev.to/gde/google-ai-classroom-part-2-zero-egress-browser-edge-ai-investigator-running-gemma-4-e2b-2k6a</link>
      <guid>https://dev.to/gde/google-ai-classroom-part-2-zero-egress-browser-edge-ai-investigator-running-gemma-4-e2b-2k6a</guid>
      <description>&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/cyruswong/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cyrus Wong (黃俊彥)&lt;/strong&gt;&lt;/a&gt; — &lt;em&gt;Google Developer Expert - Cloud (AI), Senior Lecturer at HKIIT&lt;/em&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  1. Problem Statement: Why Proctored Tests &amp;amp; Practical Examinations Need an On-Device Browser Edge AI Investigator
&lt;/h2&gt;

&lt;p&gt;In proctored tests and practical examinations, student screen captures, webcam frames, and audio evidence are recorded and uploaded to Cloud Storage for official exam invigilation and forensic audit trails. However, relying solely on human invigilators or cloud-polled snapshots during a live 40-to-100+ student examination creates three critical operational blind spots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Human Invigilator Attention Bottleneck&lt;/strong&gt;: A single lecturer or invigilator cannot simultaneously watch 40+ student screens, 40+ live webcam feeds, and listen to 40+ microphones in real time to catch fleeting exam misconduct—such as glancing at a concealed smartphone, peeking at a neighbor's monitor, whispering answers, or querying a voice assistant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fleeting Violations Between Snapshots vs. 432,000+ Cloud AI Calls&lt;/strong&gt;: Periodic cloud screen and webcam snapshots provide the historical archive, but a 3.5-second glance at a cheat sheet or a whispered answer often occurs &lt;em&gt;between&lt;/em&gt; polling intervals. Conversely, streaming 30-FPS video and continuous audio from 40 exam seats to Cloud LLMs for real-time AI inspection every second would require &lt;strong&gt;432,000+ cloud API calls per 3-hour exam&lt;/strong&gt;—saturating exam lab bandwidth, triggering API rate limits, and incurring runaway cloud inference costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High False-Positive Rates Without Local Geometric &amp;amp; Semantic Reasoning&lt;/strong&gt;: During a practical coding or cloud exam, students naturally blink, glance down at their keyboard, read exam questions aloud to themselves (&lt;em&gt;"Let me check Question 3"&lt;/em&gt;), or call the invigilator for technical assistance (&lt;em&gt;"Mr. Wong, my Docker daemon crashed"&lt;/em&gt;). Naive motion or audio-level triggers flood invigilators with false alarms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using &lt;strong&gt;Google MediaPipe (&lt;code&gt;@mediapipe/tasks-vision&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Google LiteRT.js (&lt;code&gt;@litertjs/core&lt;/code&gt; Whisper-Tiny)&lt;/strong&gt;, and &lt;strong&gt;LiteRT-LM WebGPU (&lt;code&gt;@litert-lm/core&lt;/code&gt; Gemma 4 E2B &lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;)&lt;/strong&gt;, we turned every student's browser into an always-on &lt;strong&gt;Zero-Egress Browser Edge AI Investigator&lt;/strong&gt;—continuously inspecting 3D head pose, iris metric distance, and spoken semantic intent at 60 FPS with &lt;strong&gt;$0.00 cloud AI inference egress&lt;/strong&gt;, and immediately flagging confirmed exam irregularities with synchronized dual screen + webcam + audio evidence for the invigilator.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqt0zw1uarxipfbazox7p.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqt0zw1uarxipfbazox7p.png" alt="Edge Vision Gaze &amp;amp; 3D Face Mesh" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/kVSoIame36o" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architectural Deep-Dive: Off-Main-Thread Web Worker &amp;amp; Hardware Frame Sync Pipeline
&lt;/h2&gt;

&lt;p&gt;Running a 478-point 3D facial landmark detector, an iris-depth estimator, a Chirp-Z log-Mel spectrogram extractor, a Whisper speech-to-text model, and a 2-billion-parameter &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; LLM on the main React thread would freeze the browser UI immediately.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useFaceMonitor.js" rel="noopener noreferrer"&gt;&lt;code&gt;useFaceMonitor.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertWhisper.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertWhisper.worker.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertGemma.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertGemma.worker.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useClientLiteRTGemma.js" rel="noopener noreferrer"&gt;&lt;code&gt;useClientLiteRTGemma.js&lt;/code&gt;&lt;/a&gt;, we isolate speech and LLM reasoning inside dedicated Web Workers while synchronizing vision landmark detection to the browser's hardware video decoder via &lt;code&gt;requestVideoFrameCallback()&lt;/code&gt;:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1y8cu10tnh4i7hjzxjei.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1y8cu10tnh4i7hjzxjei.png" alt="Zero-Copy Off-Main-Thread Browser Edge AI Pipeline" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Implementation Walkthrough 1: 478-Point 3D Face Mesh, Iris Metric Depth &amp;amp; The 3-Second Debounce State Machine
&lt;/h2&gt;

&lt;p&gt;Legacy proctoring software is notorious for false-positive alerts: if a student sneezes, blinks, or glances down at their mechanical keyboard for 500 milliseconds, they get flagged for "cheating." Conversely, if a student sits in a corner desk where their webcam is mounted at a $15^\circ$ angle, uncalibrated head-pose detectors flag them continuously.&lt;/p&gt;

&lt;p&gt;We solved both problems in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useFaceMonitor.js" rel="noopener noreferrer"&gt;&lt;code&gt;useFaceMonitor.js&lt;/code&gt;&lt;/a&gt; through three mathematical and state-machine innovations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;1-Click Adaptive Neutral Pose Calibration (&lt;code&gt;calibrateBaseline&lt;/code&gt;)&lt;/strong&gt;: Students click &lt;strong&gt;🎯 Set Center Pose&lt;/strong&gt; to zero out their personal baseline head Yaw ($\text{Yaw}_0$) and Pitch ($\text{Pitch}_0$) offsets for off-center monitor mounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monocular Depth-from-Iris Distance Estimation ($D_{\text{cm}}$)&lt;/strong&gt;: Using the anatomical constancy of the human horizontal iris diameter ($d_{\text{iris}} \approx 11.7\text{ mm}$) and MediaPipe's iris boundary landmarks (&lt;code&gt;469, 471&lt;/code&gt; for left iris; &lt;code&gt;474, 476&lt;/code&gt; for right iris), we compute physical camera-to-face distance in centimeters without any hardware depth sensor:&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fddsqb1rhckimwllp6ttg.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fddsqb1rhckimwllp6ttg.png" alt="Monocular Depth-from-Iris Distance Estimation" width="660" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;3-Second Debounce Gate with 600ms Blink/Jitter Grace Window &amp;amp; Lifecycle Incident Tracking&lt;/strong&gt;: An anomaly (&lt;code&gt;looking_away&lt;/code&gt;, &lt;code&gt;no_face&lt;/code&gt;, &lt;code&gt;multiple_faces&lt;/code&gt;, &lt;code&gt;eyes_closed&lt;/code&gt;, &lt;code&gt;talking&lt;/code&gt;) must persist continuously for &lt;code&gt;debounceSeconds&lt;/code&gt; (default &lt;strong&gt;3.0 seconds&lt;/strong&gt;) before opening a single active incident document in Firestore (&lt;code&gt;irregularities/{id}&lt;/code&gt;), and automatically updates &lt;code&gt;endedAt&lt;/code&gt; and &lt;code&gt;durationSeconds&lt;/code&gt; when the student returns to normal posture!&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fylyws4ph374ozi0ldebr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fylyws4ph374ozi0ldebr.png" alt="3-Second Biometric Debounce &amp;amp; 600ms Blink Grace State Machine" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code Listing 1: 3D Iris Depth, Eye-Gaze Ratio &amp;amp; Anti-Jitter Debounce in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useFaceMonitor.js" rel="noopener noreferrer"&gt;&lt;code&gt;web-app/src/hooks/useFaceMonitor.js&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Here is the exact real-time biometric geometry and debounce gate running inside &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useFaceMonitor.js" rel="noopener noreferrer"&gt;&lt;code&gt;useFaceMonitor.js&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// MediaPipe Iris: Depth-from-Iris Distance Calculation (11.7mm constant human iris diameter)&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;calculatedDistanceCm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;55&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;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;469&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;471&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;474&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;476&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;leftIrisPx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;469&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;471&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;469&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;471&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;videoWidth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;)&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;rightIrisPx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;474&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;476&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;474&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;476&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;videoWidth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;)&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;avgIrisPx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leftIrisPx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rightIrisPx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;avgIrisPx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&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;focalLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;videoWidth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;calculatedDistanceCm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;focalLength&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.17&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;avgIrisPx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;calculatedDistanceCm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calculatedDistanceCm&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="nf"&gt;setMetricDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculatedDistanceCm&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Horizontal Yaw &amp;amp; Vertical Pitch with Adaptive Neutral Baseline Calibration Offset&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;dLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;leftEyeOuter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;leftEyeOuter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&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;dRight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rightEyeOuter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rightEyeOuter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&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;rawYaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&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="n"&gt;dLeft&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dRight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dLeft&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dRight&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;dNoseChin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&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;dNoseForehead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;forehead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&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;rawPitch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&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="n"&gt;dNoseChin&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dNoseForehead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dNoseChin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dNoseForehead&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;calculatedYaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawYaw&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baselineOffsetRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yaw&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="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;calculatedPitch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rawPitch&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baselineOffsetRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pitch&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="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Anti-Jitter Debounce Gate Logic with 600ms Grace Window&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentAnomaly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;lastAnomalyTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;anomalyStartRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;anomalyStartRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="n"&gt;currentAnomaly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;anomalyStartRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentAnomaly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;detailsMessage&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="k"&gt;else&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;elapsedSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;anomalyStartRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&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;elapsedSeconds&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;debounceSeconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setActiveViolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentAnomaly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;startIncident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentAnomaly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detailsMessage&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Grace window of 600ms to tolerate natural blinks or momentary landmark noise&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;timeSinceLastAnomaly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastAnomalyTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&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="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;timeSinceLastAnomaly&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;anomalyStartRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&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="nf"&gt;setActiveViolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;activeIncidentDocRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;resolveIncident&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="p"&gt;}&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;h2&gt;
  
  
  4. Implementation Walkthrough 2: On-Device Whisper-Tiny Log-Mel DSP &amp;amp; Special Language Token Decoding
&lt;/h2&gt;

&lt;p&gt;In Hong Kong classrooms, students and lecturers naturally code-switch between &lt;strong&gt;Cantonese, Mandarin, and English technical terminology&lt;/strong&gt; within a single sentence (e.g., &lt;em&gt;"唔該阿Sir，呢個 Question 3 個 option B 係咪啱㗎？"&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/WneUu4y9KUM" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;To transcribe student speech on-device with zero UI jank, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertWhisper.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertWhisper.worker.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/utils/whisperTokenizer.js" rel="noopener noreferrer"&gt;&lt;code&gt;whisperTokenizer.js&lt;/code&gt;&lt;/a&gt; run &lt;code&gt;@litertjs/core&lt;/code&gt; inside a dedicated Web Worker:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Peak Auto-Gain &amp;amp; 80-Bin Log-Mel Spectrogram via Chirp-Z FFT&lt;/strong&gt;: Whisper expects a normalized &lt;code&gt;[1, 80, 3000]&lt;/code&gt; Float32 tensor representing 30 seconds of 16 kHz audio. Because quiet whispering during an exam often has low amplitude ($0.003 &amp;lt; \text{peak} &amp;lt; 0.25$), &lt;code&gt;extractAudioFeatures()&lt;/code&gt; applies up to $6\times$ automatic gain normalization before computing a 400-point Hann-windowed Chirp-Z Fast Fourier Transform (&lt;code&gt;hopLength = 160&lt;/code&gt;) and projecting onto 80 Slaney-normalized Mel filterbanks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whisper Special Language Token Decoding &amp;amp; WebGPU-to-WASM Delegate Failover&lt;/strong&gt;: Rather than relying on brittle regex heuristics, &lt;code&gt;extractWhisperLanguageToken()&lt;/code&gt; reads Whisper's native special language token (&lt;code&gt;50259..50357&lt;/code&gt;, e.g. &lt;code&gt;&amp;lt;|en|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|zh|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|yue|&amp;gt;&lt;/code&gt;) directly from the decoded token stream right after &lt;code&gt;&amp;lt;|startoftranscript|&amp;gt;&lt;/code&gt; (&lt;code&gt;50258&lt;/code&gt;), while &lt;strong&gt;Gemma 4 E2B (&lt;code&gt;litertGemma.worker.js&lt;/code&gt;)&lt;/strong&gt; and &lt;strong&gt;Cloud Gemini (&lt;code&gt;analyzeAudioFlow&lt;/code&gt;)&lt;/strong&gt; perform semantic language and code-switching classification (&lt;code&gt;"language": "cantonese|mandarin|english|mixed"&lt;/code&gt;) alongside exam intent evaluation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Production Code Listing 2: WebGPU/WASM Whisper Compilation &amp;amp; Special Language Token Decoder in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertWhisper.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertWhisper.worker.js&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/utils/whisperTokenizer.js" rel="noopener noreferrer"&gt;&lt;code&gt;whisperTokenizer.js&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Extracts the language code emitted by Whisper's special language token
 * (`&amp;lt;|en|&amp;gt;`, `&amp;lt;|zh|&amp;gt;`, `&amp;lt;|yue|&amp;gt;`, etc.) in the output token sequence.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractWhisperLanguageToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vocabulary&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;fallbackLanguage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'auto'&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;tokenIds&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;tokenIds&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'string'&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;fallbackLanguage&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;prefixLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;prefixLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&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;tokenId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenIds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;vocabToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vocabulary&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tokenId&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="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;vocabToken&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;)&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vocabToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/^&amp;lt;&lt;/span&gt;\&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;\&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt;$&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;i&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;match&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;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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="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;tokenId&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="no"&gt;WHISPER_LANGUAGE_TOKEN_START&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="no"&gt;WHISPER_LANGUAGE_TOKEN_END&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="no"&gt;WHISPER_LANGUAGE_ID_MAP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;fallbackLanguage&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fallbackLanguage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compileWhisperModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loadAndCompile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preferredDelegate&lt;/span&gt;&lt;span class="p"&gt;)&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;compile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accelerator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadAndCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelBuffer&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="n"&gt;accelerator&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preferredDelegate&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;'webgpu'&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="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wasm'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'wasm'&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="k"&gt;try&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="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'webgpu'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'webgpu'&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webGpuError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s1"&gt;'[LiteRTWorker] WebGPU could not compile the dynamic Whisper graph; retrying with WASM:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;webGpuError&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;webGpuError&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="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wasm'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'wasm'&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Implementation Walkthrough 3: On-Device Gemma 4 E2B (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;) for Semantic Proctoring, Dialect Classification &amp;amp; Live Translation
&lt;/h2&gt;

&lt;p&gt;Recognizing speech tokens is only half the battle: if a student mutters &lt;em&gt;"Let me read this question carefully"&lt;/em&gt; (benign self-talk) or raises their hand and says &lt;em&gt;"Mr. Wong, my Docker daemon crashed"&lt;/em&gt; (legitimate inquiry), a keyword-based detector would falsely accuse them of cheating.&lt;/p&gt;

&lt;p&gt;We needed a true &lt;strong&gt;Large Language Model&lt;/strong&gt; running locally in the browser to evaluate semantic intent and spoken dialect/code-switching. Using &lt;code&gt;@litert-lm/core&lt;/code&gt; (&lt;code&gt;v0.15.0&lt;/code&gt;) and the quantized &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; model (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;, ~1.91 GB in WebGPU), &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertGemma.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertGemma.worker.js&lt;/code&gt;&lt;/a&gt; serves &lt;strong&gt;two mission-critical classroom roles at $0.00 API cost&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EVALUATE_TRANSCRIPT&lt;/code&gt; (Exam Acoustic Intent &amp;amp; Dialect Investigator)&lt;/strong&gt;: Classifies student speech transcripts into one of 5 strict categories (&lt;code&gt;COLLUSION_EXAM&lt;/code&gt;, &lt;code&gt;EXTERNAL_AI_ASSIST&lt;/code&gt;, &lt;code&gt;UNAUTHORIZED_TALK&lt;/code&gt;, &lt;code&gt;LEGITIMATE_INQUIRY&lt;/code&gt;, or &lt;code&gt;BENIGN&lt;/code&gt;) along with spoken dialect/code-switching (&lt;code&gt;"language": "cantonese|mandarin|english|mixed"&lt;/code&gt;), severity, confidence, quoted evidence, and rationale—and supports custom teacher prompts (&lt;code&gt;{{transcript}}&lt;/code&gt;, &lt;code&gt;{{classId}}&lt;/code&gt;, &lt;code&gt;{{studentEmail}}&lt;/code&gt;) authored in the AI Prompt Studio!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TRANSLATE_TRANSCRIPT&lt;/code&gt; ($0.00 Client-Mode Live Subtitles)&lt;/strong&gt;: When a lecturer selects &lt;strong&gt;On-Device Gemma 4 E2B&lt;/strong&gt; in the Live Subtitles modal, the same WebGPU worker translates Cantonese/English code-switched lecture speech into up to 7 target languages (&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;zh-Hant&lt;/code&gt;, &lt;code&gt;zh-Hans&lt;/code&gt;, &lt;code&gt;ja&lt;/code&gt;, &lt;code&gt;ko&lt;/code&gt;, &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;) while preserving technical identifiers like &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;Docker&lt;/code&gt;, and &lt;code&gt;Python&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Production Code Listing 3: Gemma 4 E2B WebGPU Initialization &amp;amp; Structured Output Parser in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertGemma.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;web-app/src/workers/litertGemma.worker.js&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;GEMMA_OUTPUT_INSTRUCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`Use exactly one category:
- COLLUSION_EXAM: asking for, offering, or discussing exam answers, questions, or options
- EXTERNAL_AI_ASSIST: asking a voice assistant, search engine, phone, or AI tool for help
- UNAUTHORIZED_TALK: unrelated conversation with another person during a silent exam
- LEGITIMATE_INQUIRY: procedural or technical help requested from the teacher or proctor
- BENIGN: self-talk, reading aloud, ambient speech, coughing, or silence

Also classify the spoken language/code-switching of the transcript in "language" as one of: "cantonese", "mandarin", "english", or "mixed".

Respond with only one JSON object and no markdown:
{"isViolation":boolean,"category":"COLLUSION_EXAM|EXTERNAL_AI_ASSIST|UNAUTHORIZED_TALK|LEGITIMATE_INQUIRY|BENIGN","severity":"critical|high|medium|low|none","confidence":number,"language":"cantonese|mandarin|english|mixed","evidence":"quoted key phrase","rationale":"short explanation"}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initializeGemma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelUrl&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;gemmaEngine&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="n"&gt;fromCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gemmaLoadedFromCache&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;initializationPromise&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;initializationPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;initializationPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&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;isMobileOrIos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;iPhone&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;iPad&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;iPod&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;Android&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;CriOS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;FxiOS&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'MacIntel'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxTouchPoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&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;isMobileOrIos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'On-device Gemma 4 E2B (~1.9 GB) exceeds mobile browser memory limits; using Cloud fallback.'&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceMemory&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'number'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceMemory&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sb"&gt;`Device RAM (${self.navigator.deviceMemory} GB) is below the 4 GB minimum for Gemma 4 E2B; using Cloud fallback.`&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WebGPU is unavailable on this device'&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="nf"&gt;configureLiteRtLmWasmLocation&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;modelStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cacheWritePromise&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;streamModelWithProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'STATUS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'loading'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&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="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;gemmaEngine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;mainExecutorSettings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;maxNumTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&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="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;GEMMA_PROCTOR_SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;)&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;cached&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;cacheWritePromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;gemmaLoadedFromCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fromCache&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="n"&gt;fromCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fromCache&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;cached&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="p"&gt;)&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;initializationPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;initializationPromise&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Edge Cases &amp;amp; Production Failure Modes We Solved: Fresh "DeepFreeze" Lab PCs, Dynamic Tensor Guards, iOS Jetsam OOM &amp;amp; Live Hot-Swapping
&lt;/h2&gt;

&lt;p&gt;Deploying a 1.91 GB WebGPU LLM (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;) and an on-device Whisper TFLite worker across university computer labs and BYOD student devices exposed four real-world failure modes that we engineered around in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/StudentView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;StudentView.jsx&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/utils/gemmaLiteRTLoader.js" rel="noopener noreferrer"&gt;&lt;code&gt;gemmaLiteRTLoader.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useClientLiteRTGemma.js" rel="noopener noreferrer"&gt;&lt;code&gt;useClientLiteRTGemma.js&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/workers/litertWhisper.worker.js" rel="noopener noreferrer"&gt;&lt;code&gt;litertWhisper.worker.js&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"DeepFreeze" Wiped Lab PCs &amp;amp; Automatic Uncached Pre-Warming&lt;/strong&gt;: University lab PCs reboot into a clean image every morning, wiping &lt;code&gt;CacheStorage&lt;/code&gt; (&lt;code&gt;isGemmaCached === false&lt;/code&gt;). If auto-preloading gated on &lt;code&gt;isGemmaCached&lt;/code&gt;, a wiped lab PC would stay stuck at &lt;code&gt;gemmaStatus: 'idle'&lt;/code&gt; (&lt;code&gt;gemmaEngine: 'uninitialized'&lt;/code&gt;) forever unless the student manually clicked a preload button. Instead, &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/StudentView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;StudentView.jsx&lt;/code&gt;&lt;/a&gt; automatically triggers &lt;code&gt;preloadGemmaModel()&lt;/code&gt; whenever &lt;code&gt;preloadClientAi&lt;/code&gt; is broadcast by the teacher, whenever active audio monitoring starts (&lt;code&gt;isAudioCaptureActive&lt;/code&gt;), or on the very first spoken transcript (&lt;code&gt;handleLiveVoiceTranscript&lt;/code&gt;), requesting &lt;code&gt;navigator.storage.persist()&lt;/code&gt; and streaming the 1.91 GB &lt;code&gt;.litertlm&lt;/code&gt; bundle directly into &lt;code&gt;Engine.create({ model: modelStream })&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Blocking Cloud-to-Edge Live Hot-Swap&lt;/strong&gt;: If an exam begins while Gemma 4 E2B is still downloading on a wiped lab PC, the student is never blocked from starting their exam. Any speech detected while &lt;code&gt;isGemmaReady&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; is transparently routed to our Cloud Genkit flow (&lt;code&gt;analyzeAudio&lt;/code&gt; using &lt;code&gt;gemini-3.5-transcribe-preview&lt;/code&gt; + &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;), and &lt;strong&gt;automatically hot-swaps to 100% local Gemma 4 E2B WebGPU inference the millisecond the worker emits &lt;code&gt;INIT_COMPLETE&lt;/code&gt; (&lt;code&gt;ready: true&lt;/code&gt;)&lt;/strong&gt;!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic-Output TFLite Tensor Allocation Guard (&lt;code&gt;clientInferenceSupported&lt;/code&gt;)&lt;/strong&gt;: Certain exported Whisper TFLite graphs declare their output &lt;code&gt;sequences&lt;/code&gt; tensor with &lt;code&gt;kTfLiteDynamic&lt;/code&gt; allocation, which causes &lt;code&gt;@litertjs/core&lt;/code&gt; WASM &lt;code&gt;LiteRtCompiledModel.run()&lt;/code&gt; to throw &lt;code&gt;Failed to register output tensor buffer: Failed to set custom allocation for output&lt;/code&gt;. Our worker catches this on the first segment and flips &lt;code&gt;clientInferenceSupported = false&lt;/code&gt; to prevent repeated WASM C++ error spam while seamlessly continuing transcription via the bound &lt;code&gt;MediaStreamTrack&lt;/code&gt; recognizer and Firebase AI Logic STT fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS / iPadOS WebKit Jetsam OOM Guard &amp;amp; Storage Quota Pre-Flight&lt;/strong&gt;: Allocating a 1.91 GB WebGPU buffer on an iPhone or iPad (which masquerades as &lt;code&gt;MacIntel&lt;/code&gt; with &lt;code&gt;maxTouchPoints &amp;gt; 1&lt;/code&gt; on iPadOS) triggers Apple's kernel Jetsam killer, crashing the browser tab. Similarly, laptops with &lt;code&gt;&amp;lt; 2.5 GB&lt;/code&gt; free disk space throw &lt;code&gt;QuotaExceededError&lt;/code&gt; mid-download. Our &lt;code&gt;isMobileOrIosBrowser()&lt;/code&gt;, &lt;code&gt;deviceMemory &amp;lt; 4&lt;/code&gt;, and &lt;code&gt;precheckGemmaViability()&lt;/code&gt; guards fail fast in &lt;code&gt;&amp;lt; 5ms&lt;/code&gt; and route mobile/low-RAM devices cleanly to Cloud Fallback.&lt;/li&gt;
&lt;/ol&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2x225hr86emmop3yhigr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2x225hr86emmop3yhigr.png" alt="Step-1 Readiness Pre-Warming &amp;amp; Cloud-to-Edge Live Hot-Swap" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Code Listing 4: Cloud-to-Edge Hot-Swap &amp;amp; Dual Firestore Alerting in &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/StudentView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;StudentView.jsx&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useClientLiteRTGemma.js" rel="noopener noreferrer"&gt;&lt;code&gt;useClientLiteRTGemma.js&lt;/code&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Notice how &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/components/StudentView.jsx" rel="noopener noreferrer"&gt;&lt;code&gt;StudentView.jsx&lt;/code&gt;&lt;/a&gt; automatically starts &lt;code&gt;preloadGemmaModel()&lt;/code&gt; if &lt;code&gt;gemmaStatus === 'idle'&lt;/code&gt; while routing transcripts to Cloud Genkit until &lt;code&gt;isGemmaReady&lt;/code&gt; becomes &lt;code&gt;true&lt;/code&gt;, and how &lt;a href="https://github.com/wongcyrus/Google-AI-Classroom/blob/main/web-app/src/hooks/useClientLiteRTGemma.js" rel="noopener noreferrer"&gt;&lt;code&gt;useClientLiteRTGemma.js&lt;/code&gt;&lt;/a&gt; writes confirmed violations and LLM-classified &lt;code&gt;speechLanguage&lt;/code&gt; to Firestore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In StudentView.jsx: Non-blocking Cloud-to-Edge Hot-Swap + Auto-Start on Idle&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;handleLiveVoiceTranscript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;shouldEvaluateVoiceWithGemma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isGemmaReady&lt;/span&gt;&lt;span class="p"&gt;)&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;localResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;evaluateSpeechWithGemma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&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;localResult&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;localResult&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="k"&gt;else&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;isGemmaWorkable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;gemmaStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'idle'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;preloadGemmaModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;preloadGemmaModel&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[StudentView] Auto-starting Gemma 4 E2B preload on transcript:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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="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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;shouldSendTranscriptToCloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;effectiveVoiceAiMode&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="kc"&gt;null&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;analyzeAudioCallable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpsCallable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'analyzeAudio'&lt;/span&gt;&lt;span class="p"&gt;)&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;analyzeAudioCallable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;activeClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;studentEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&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="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;activeClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;effectiveVoiceAiMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluateSpeechWithGemma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gemmaStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isGemmaReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isGemmaWorkable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preloadGemmaModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shouldEvaluateVoiceWithGemma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&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;// In useClientLiteRTGemma.js: Dual Firestore Irregularity &amp;amp; Status Telemetry Update&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;isViolation&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;currentClassId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;currentStudentUid&lt;/span&gt;&lt;span class="p"&gt;)&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;irregPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;classId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentClassId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;studentUid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentStudentUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentStudentEmail&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;studentEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentStudentEmail&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`AI Speech Alert: ${category.replace(/_/g, ' ')}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rationale&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sb"&gt;`Spoken text: "${transcript}"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'audio'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rationale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rationale&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;transcriptSnippet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'on_device_gemma'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;serverTimestamp&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'irregularities'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;irregPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'classes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentClassId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'irregularities'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;irregPayload&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentClassId&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;currentStudentUid&lt;/span&gt;&lt;span class="p"&gt;)&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;statusRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'classes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentClassId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentStudentUid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;gemmaAlert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isViolation&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;gemmaSeverity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isViolation&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;gemmaConfidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speechLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lastGemmaTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Browser Edge AI Performance &amp;amp; Memory Benchmarks
&lt;/h2&gt;

&lt;p&gt;We benchmarked our 3-model browser edge stack across standard HKIIT lab hardware (Intel Core i5-12500 / 16 GB RAM integrated UHD 770 GPU, and Apple M2 MacBook Air 8 GB RAM) running Chrome 130+:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;On-Device Edge Model&lt;/th&gt;
&lt;th&gt;Runtime &amp;amp; Delegate&lt;/th&gt;
&lt;th&gt;Model Size (Cached in &lt;code&gt;CacheStorage&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Cold Load (From Cache)&lt;/th&gt;
&lt;th&gt;Inference Latency&lt;/th&gt;
&lt;th&gt;Main-Thread UI Frame Rate&lt;/th&gt;
&lt;th&gt;Cloud API Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MediaPipe FaceLandmarker (478-pt + Iris)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@mediapipe/tasks-vision&lt;/code&gt; (WebGL GPU)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~3.8 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;180 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11–16 ms / frame&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;60 FPS&lt;/strong&gt; (&lt;code&gt;requestVideoFrameCallback&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LiteRT Whisper-Tiny (Multilingual)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@litertjs/core&lt;/code&gt; (WebGPU / WASM + Chirp-Z FFT)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~41 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;420 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;280–450 ms / segment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;60 FPS&lt;/strong&gt; (Isolated Worker)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LiteRT-LM Gemma 4 E2B (&lt;code&gt;gemma-4-E2B-it-web.litertlm&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@litert-lm/core&lt;/code&gt; v0.15.0 (WebGPU)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1,915 MB (1.91 GB)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2.4 s&lt;/strong&gt; (Streamed from Cache)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;380–620 ms / JSON eval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;60 FPS&lt;/strong&gt; (Isolated Worker)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Fallback (&lt;code&gt;analyzeFaceFallback&lt;/code&gt; / &lt;code&gt;analyzeAudio&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vertex AI Genkit (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; / &lt;code&gt;gemini-4-argon&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;650–1,100 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;60 FPS&lt;/strong&gt; (Async HTTPS Callable)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.0002 / check&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  8. Key Takeaways for Web AI &amp;amp; Google Cloud Builders
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stream Large &lt;code&gt;.litertlm&lt;/code&gt; Models Directly into &lt;code&gt;Engine.create()&lt;/code&gt;&lt;/strong&gt;: Instead of buffering a 1.91 GB &lt;code&gt;ArrayBuffer&lt;/code&gt; twice in memory, tee the &lt;code&gt;fetch&lt;/code&gt; / &lt;code&gt;CacheStorage&lt;/code&gt; &lt;code&gt;ReadableStream&lt;/code&gt; directly into &lt;code&gt;@litert-lm/core&lt;/code&gt; &lt;code&gt;Engine.create({ model: modelStream })&lt;/code&gt; while reporting live download percentages (&lt;code&gt;5%..90%&lt;/code&gt;) to the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always Dispose Ephemeral LLM Conversations&lt;/strong&gt;: Inside a long-running Web Worker, calling &lt;code&gt;await conversation.delete()&lt;/code&gt; in a &lt;code&gt;finally&lt;/code&gt; block after every single-turn &lt;code&gt;EVALUATE_TRANSCRIPT&lt;/code&gt; or &lt;code&gt;TRANSLATE_TRANSCRIPT&lt;/code&gt; request prevents KV-cache memory leaks over a 3-hour exam.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard Against Mobile/iPadOS OOMs Before Fetching&lt;/strong&gt;: Checking &lt;code&gt;maxTouchPoints &amp;gt; 1&lt;/code&gt; on &lt;code&gt;MacIntel&lt;/code&gt; (iPadOS), &lt;code&gt;deviceMemory &amp;lt; 4&lt;/code&gt;, and &lt;code&gt;navigator.storage.estimate()&lt;/code&gt; before downloading multi-gigabyte weights prevents browser tab crashes and seamlessly routes mobile learners to Cloud Gemini fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine Geometric Heuristics with Debounce Gates&lt;/strong&gt;: Pairing MediaPipe's 11.7 mm iris depth estimation and neutral-pose calibration with a 3.0-second debounce gate and 600 ms blink grace window eliminates false-positive cheating flags while catching real irregularities with dual screen-and-webcam visual proof.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>browser</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
