<?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: john lee</title>
    <description>The latest articles on DEV Community by john lee (@64johnlee).</description>
    <link>https://dev.to/64johnlee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3997295%2F79493f6e-fe4c-465d-b092-e0551a9373fc.png</url>
      <title>DEV Community: john lee</title>
      <link>https://dev.to/64johnlee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/64johnlee"/>
    <language>en</language>
    <item>
      <title>Three hackathon agents, one card-free API key: what I learned building on Qwen Cloud</title>
      <dc:creator>john lee</dc:creator>
      <pubDate>Tue, 07 Jul 2026 12:11:44 +0000</pubDate>
      <link>https://dev.to/64johnlee/three-hackathon-agents-one-card-free-api-key-what-i-learned-building-on-qwen-cloud-4c56</link>
      <guid>https://dev.to/64johnlee/three-hackathon-agents-one-card-free-api-key-what-i-learned-building-on-qwen-cloud-4c56</guid>
      <description>&lt;p&gt;Over the past month I shipped three entries to the Global AI Hackathon Series with Qwen Cloud — an autonomous bounty-hunting agent, a GitLab CI diagnosis agent, and an AI "showrunner" that turns one line of premise into a finished, subtitled vertical short-drama episode. All three run on the same &lt;strong&gt;card-free Qwen Cloud free-tier API key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That last part matters. I'm in Malaysia, I don't put credit cards into cloud consoles for hackathons, and Qwen Cloud turned out to be one of the few platforms where a genuinely free, no-payment-method tier covers LLM, video, image &lt;em&gt;and&lt;/em&gt; speech generation. This post is the map of the gotchas I hit so you don't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the key (the card-free path)
&lt;/h2&gt;

&lt;p&gt;There are &lt;strong&gt;two&lt;/strong&gt; signup flows and they look confusingly similar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Model Studio "Free Quota"&lt;/strong&gt; via &lt;code&gt;home.qwencloud.com/benefits&lt;/code&gt; — email/phone signup, ~1M tokens &lt;em&gt;per model&lt;/em&gt; across 200+ models, &lt;strong&gt;no payment method required&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;❌ The broader &lt;strong&gt;Alibaba Cloud free trial&lt;/strong&gt; ("Activate full account", ECS etc.) — this one &lt;em&gt;is&lt;/em&gt; card-gated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any screen asks for a card, you're in the wrong flow — back out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The endpoint maze
&lt;/h2&gt;

&lt;p&gt;The free-tier &lt;code&gt;sk-…&lt;/code&gt; key works on the &lt;strong&gt;international&lt;/strong&gt; endpoint only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://dashscope-intl.aliyuncs.com/compatible-mode/v1      # OpenAI-compatible chat
https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/…  # video / image / speech
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things that cost me hours:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Beijing-region model names don't exist on intl.&lt;/strong&gt; Docs and blog posts written for the China region will point you at &lt;code&gt;wanx2.1-*&lt;/code&gt; or &lt;code&gt;qwen-tts&lt;/code&gt; — on &lt;code&gt;dashscope-intl&lt;/code&gt; the working IDs are &lt;code&gt;wan2.2-t2v-plus&lt;/code&gt; / &lt;code&gt;wan2.1-t2v-turbo&lt;/code&gt; (video), &lt;code&gt;wan2.2-t2i-flash&lt;/code&gt; (image) and &lt;code&gt;qwen3-tts-flash&lt;/code&gt; (speech). When in doubt, probe the endpoint: an auth-valid request with a wrong model returns &lt;code&gt;InvalidParameter: "Model not exist"&lt;/code&gt; &lt;em&gt;with a request ID&lt;/em&gt;, which tells you your key is fine and only the name is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTS must be called synchronously.&lt;/strong&gt; Sending the &lt;code&gt;X-DashScope-Async&lt;/code&gt; header to &lt;code&gt;qwen3-tts-flash&lt;/code&gt; gets you &lt;code&gt;AccessDenied: does not support asynchronous calls&lt;/code&gt;. Video and image are the opposite — async submit + poll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The token-plan/MaaS endpoint 401s the free-tier key.&lt;/strong&gt; Don't mix them up; &lt;code&gt;compatible-mode/v1&lt;/code&gt; is the one you want for chat.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project 1: AutoPR — an agent that hunts open-source bounties
&lt;/h2&gt;

&lt;p&gt;AutoPR scans bounty platforms every 15 minutes, triages issues with &lt;strong&gt;Qwen-Max&lt;/strong&gt; (structured JSON out: &lt;code&gt;{score, reason, approach, skip}&lt;/code&gt;), then hands tractable ones to a &lt;strong&gt;Qwen-Plus tool loop&lt;/strong&gt; — six tools (&lt;code&gt;list_files&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;search_code&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt;, &lt;code&gt;run_command&lt;/code&gt;, &lt;code&gt;finish&lt;/code&gt;) against a cloned repo until the tests pass, then opens the PR.&lt;/p&gt;

&lt;p&gt;What I learned: Qwen-Max's structured output is reliable enough to be a gate (skip anything scoring &amp;lt; 0.45), and Qwen-Plus handles well-scoped bugs — null checks, missing coverage, small API changes — while the triage layer correctly filters out the architectural stuff it would flounder on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/64johnlee/autopr" rel="noopener noreferrer"&gt;https://github.com/64johnlee/autopr&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project 2: QwenPipelineGuard — CI failure diagnosis over MCP
&lt;/h2&gt;

&lt;p&gt;A GitLab CI watcher that pulls failed pipeline logs and has Qwen produce a root-cause diagnosis and a suggested fix, exposed as an MCP server so any MCP-capable client can call it. The interesting part was how little glue code is needed once the chat endpoint is OpenAI-compatible — the same client library talks to Qwen with one base-URL swap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/64johnlee/qwen-pipelineguard" rel="noopener noreferrer"&gt;https://github.com/64johnlee/qwen-pipelineguard&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project 3: Qwen Showrunner — premise in, episode out
&lt;/h2&gt;

&lt;p&gt;The fun one. Give it &lt;em&gt;"a KL hawker discovers his rival stole his recipe"&lt;/em&gt; and it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;writes a shot-by-shot screenplay with &lt;strong&gt;Qwen-Max&lt;/strong&gt; (validated JSON: title, characters, assigned voices),&lt;/li&gt;
&lt;li&gt;films every shot with &lt;strong&gt;Wan&lt;/strong&gt; text-to-video,&lt;/li&gt;
&lt;li&gt;voices every character with &lt;strong&gt;Qwen3-TTS&lt;/strong&gt; in the spoken language,&lt;/li&gt;
&lt;li&gt;translates subtitles into a &lt;em&gt;different&lt;/em&gt; language (spoken 中文, subtitled English — the SEA localization angle),&lt;/li&gt;
&lt;li&gt;cuts it all with ffmpeg into a vertical 9:16 &lt;code&gt;episode.mp4&lt;/code&gt; with burned subtitles.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wan-specific lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free-tier clips are fixed at ~5 seconds.&lt;/strong&gt; The &lt;code&gt;duration&lt;/code&gt; parameter is accepted at submit time and then rejected at execution ("duration customization is not supported"). Episode length is therefore controlled by &lt;em&gt;shot count&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submit all shots up front, then await.&lt;/strong&gt; Wan tasks are async; a 10-shot episode submitted in parallel renders in roughly the wall-clock time of one shot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video quota is metered in seconds, not tokens&lt;/strong&gt; — and "stop-on-exhaust" means the service just stops with a 403 instead of billing you. Exactly what you want on a card-free account.&lt;/li&gt;
&lt;li&gt;ffmpeg on Windows with CJK subtitles: one &lt;code&gt;-filter_complex&lt;/code&gt; only, and the font path needs single quotes plus escaped colon: &lt;code&gt;fontfile='C\:/Windows/Fonts/msyh.ttc'&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/64johnlee/qwen-showrunner" rel="noopener noreferrer"&gt;https://github.com/64johnlee/qwen-showrunner&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof of deployment
&lt;/h2&gt;

&lt;p&gt;The hackathon requires evidence your project actually ran on Alibaba Cloud. The Qwen Cloud console's &lt;strong&gt;Analytics → Usage/Logs&lt;/strong&gt; pages are perfect for this: per-request logs with request IDs, timestamps, models and status codes. Screenshot those rather than your billing page — "running resources", not "quota remaining".&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics
&lt;/h2&gt;

&lt;p&gt;36.8K tokens and 435 requests across a week of building and demos, all inside the free tier. For the bounty agent the math is fun: at fractions of a cent per triage call and $20–$250 bounties, a low single-digit merge rate breaks even.&lt;/p&gt;

&lt;p&gt;If you've been putting off trying the Qwen/Wan stack because you assumed it needed an Alibaba Cloud account with a card on file — it doesn't. One email signup, one key, all four modalities.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built solo in Kuala Lumpur with an agentic coding setup (Claude Code driving the builds, Qwen models doing the runtime work). Questions welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>"Building an HSK Speaking Test AI: Real-time Tone Grading with Gemini</title>
      <dc:creator>john lee</dc:creator>
      <pubDate>Sun, 28 Jun 2026 09:57:22 +0000</pubDate>
      <link>https://dev.to/64johnlee/building-an-hsk-speaking-test-ai-real-time-tone-grading-with-gemini-5bca</link>
      <guid>https://dev.to/64johnlee/building-an-hsk-speaking-test-ai-real-time-tone-grading-with-gemini-5bca</guid>
      <description>&lt;h1&gt;
  
  
  Building an HSK Speaking Test AI: Real-time Tone Grading with Gemini
&lt;/h1&gt;

&lt;p&gt;I built a free Mandarin speaking assessment tool that grades tone + grammar in real time. Here's the engineering behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;HSK (Chinese proficiency test) has a speaking component (HSKK), but most learners can't self-assess their level. Online tutors are expensive. Generic AI conversation tools don't grade tones.&lt;/p&gt;

&lt;p&gt;So I built ToneTutor: a 3-minute spoken-HSK test that estimates your speaking level and identifies weak points.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Audio API (record user voice → PCM → LINEAR16)&lt;/li&gt;
&lt;li&gt;React + TypeScript (real-time transcript display)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI (Python) on Google Cloud Run&lt;/li&gt;
&lt;li&gt;Gemini 2.5 Flash (real-time conversation + transcript grading)&lt;/li&gt;
&lt;li&gt;Firestore (user sessions + results)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Web Audio API records as WebM. Gemini expects LINEAR16 (WAV). iOS Safari doesn't support WebM. So:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Transcode WebM → PCM in browser (Web Audio context)&lt;/li&gt;
&lt;li&gt;Send raw PCM bytes to backend&lt;/li&gt;
&lt;li&gt;Backend wraps PCM in WAV header → sends to Gemini Speech-to-Text&lt;/li&gt;
&lt;li&gt;Gemini analyzes transcript + provides HSK level estimate&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Grading Loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
async def grade_session(transcript: str):
    prompt = """
    Rate this Mandarin response on HSK 1-6 scale.
    Assess: tone accuracy, grammar, vocabulary range.
    Provide: level estimate + weak points.
    """
    response = await gemini.generate_content(prompt, stream=True)
    return parse_hsk_level(response)

Results

- 3-min test
- Real-time feedback
- Shareable HSK score card
- Free (limited sessions)

Open source coming soon. Built because I'm a native speaker + voice actor frustrated with generic tools.

Try it: tonetutor.tefusiang.com (free for 3 sessions)

Curious about the speech-to-text pipeline or tone grading logic? Ask below.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a million-scale World Cup prediction game on DynamoDB + v0 in a weekend</title>
      <dc:creator>john lee</dc:creator>
      <pubDate>Wed, 24 Jun 2026 07:32:26 +0000</pubDate>
      <link>https://dev.to/64johnlee/i-built-a-million-scale-world-cup-prediction-game-on-dynamodb-v0-in-a-weekend-1gn5</link>
      <guid>https://dev.to/64johnlee/i-built-a-million-scale-world-cup-prediction-game-on-dynamodb-v0-in-a-weekend-1gn5</guid>
      <description>&lt;p&gt;&lt;em&gt;I created this project and this post for the **H0: Hack the Zero Stack&lt;/em&gt;* hackathon&lt;br&gt;
(Vercel v0 + AWS Databases). #H0Hackathon*&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://world-cup-prediction-game-psi.vercel.app" rel="noopener noreferrer"&gt;https://world-cup-prediction-game-psi.vercel.app&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;60-second walkthrough:&lt;/strong&gt; &lt;a href="https://youtube.com/shorts/tTPotTmi4fE" rel="noopener noreferrer"&gt;https://youtube.com/shorts/tTPotTmi4fE&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;A World Cup is one of the few events where you genuinely might get hundreds of millions&lt;br&gt;
of people doing the same thing at the same time. So when I set out to build a prediction&lt;br&gt;
game for it, the question wasn't "can I make a CRUD app" — it was "would the data layer&lt;br&gt;
survive matchday?" That's exactly the bet &lt;strong&gt;H0&lt;/strong&gt; asks you to make: prototype on the same&lt;br&gt;
&lt;strong&gt;Amazon DynamoDB&lt;/strong&gt; foundation real products run on, with a &lt;strong&gt;v0&lt;/strong&gt;-scaffolded Next.js&lt;br&gt;
frontend on &lt;strong&gt;Vercel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's how it came together — and the one design decision the whole thing hinges on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;Every match shows a &lt;strong&gt;10,000-run Monte-Carlo forecast&lt;/strong&gt; (win/draw/loss + a likely&lt;br&gt;
scoreline) next to what &lt;strong&gt;the crowd&lt;/strong&gt; actually picked. You call the result, earn points&lt;br&gt;
(+5 exact score, +3 right outcome), and climb a &lt;strong&gt;global leaderboard&lt;/strong&gt; that settles&lt;br&gt;
against real results pulled from ESPN. No login — an anonymous cookie makes every visitor&lt;br&gt;
a player instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  v0 did the frontend; I owned the data layer
&lt;/h2&gt;

&lt;p&gt;v0 scaffolded a genuinely good Next.js App Router app — match cards, a pick sheet, three&lt;br&gt;
tabs, SWR data fetching against &lt;code&gt;/api/*&lt;/code&gt; — in minutes. It even left placeholder route&lt;br&gt;
handlers with a literal TODO: &lt;em&gt;"wire to DynamoDB."&lt;/em&gt; My job was to make that real without&lt;br&gt;
fighting the contract v0 generated. I dropped in the sim engine + a DynamoDB data layer&lt;br&gt;
and rewrote the four routes to return the exact shapes v0's TypeScript types expected.&lt;br&gt;
The UI never knew the difference — it just started showing real fixtures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision the whole thing hinges on: the leaderboard
&lt;/h2&gt;

&lt;p&gt;Single-table DynamoDB is the easy part. The trap is the leaderboard. The naive design —&lt;br&gt;
"put every user in one partition, sort by points" — is a &lt;strong&gt;hot partition&lt;/strong&gt; waiting to&lt;br&gt;
happen: on matchday every score update hammers one partition key.&lt;/p&gt;

&lt;p&gt;So the leaderboard is &lt;strong&gt;write-sharded&lt;/strong&gt;. A user's profile is indexed under&lt;br&gt;
&lt;code&gt;LB#&amp;lt;season&amp;gt;#&amp;lt;hash(uid) % 10&amp;gt;&lt;/code&gt; with their (zero-padded) points as the sort key. Writes&lt;br&gt;
spread across 10 partitions; to read the global top-N I &lt;strong&gt;scatter-gather&lt;/strong&gt; — query the&lt;br&gt;
top-N of each shard in parallel and merge. A user's rank is &lt;code&gt;COUNT(points &amp;gt; mine)&lt;/code&gt; summed&lt;br&gt;
across shards. It's the pattern AWS literally recommends, and it means the board scales&lt;br&gt;
horizontally instead of melting.&lt;/p&gt;

&lt;p&gt;One GSI does triple duty (all-string keys):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LB#&amp;lt;season&amp;gt;#&amp;lt;shard&amp;gt;&lt;/code&gt; → leaderboard&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DATE#&amp;lt;yyyymmdd&amp;gt;&lt;/code&gt; → today's slate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MPICK#&amp;lt;matchId&amp;gt;&lt;/code&gt; → every pick on a match (so settling fans out cheaply)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Picks bump an &lt;strong&gt;atomic counter&lt;/strong&gt; on the match (that's the "crowd picked" split) inside a&lt;br&gt;
transaction. On-demand capacity means I never pre-provision — matchday spikes just work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shippable, not a demo
&lt;/h2&gt;

&lt;p&gt;The thing that makes me happiest: it's &lt;strong&gt;actually deployed and serving real data&lt;/strong&gt; —&lt;br&gt;
real 2026 fixtures, real sim odds, a real sharded leaderboard on real AWS — and it plugs&lt;br&gt;
into a daily-content funnel I already run that sends real fans to it. The whole point of&lt;br&gt;
the "zero stack" is that the weekend prototype &lt;em&gt;is&lt;/em&gt; the production foundation. This one is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://world-cup-prediction-game-psi.vercel.app" rel="noopener noreferrer"&gt;https://world-cup-prediction-game-psi.vercel.app&lt;/a&gt; · built with v0 + Vercel +&lt;br&gt;
Amazon DynamoDB for &lt;strong&gt;#H0Hackathon&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>aws</category>
      <category>dynamodb</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
