Runze Yuan

Runze Yuan 袁润泽

Full-stack developer, Java / React and applied AI. Final-year engineering student at CY Tech (graduating 2027).

Looking for a six-month end-of-studies internship from 4 May 2027, in or around Paris.

At a glance

Education
Engineering degree in computer science, CY Tech, 2027
Experience
2 years 7 months in industry before CY Tech; since 2026, a part-time permanent job and a freelance contract
Languages
French B2, English B2, Chinese native
Internship
Six months, from 4 May 2027
Until then
Available now, during my programme’s in-company weeks
Afterwards
Open to a permanent position
Based in
Cergy, Île-de-France

As the sole developer, orchestrating AI agents, I designed and shipped the production-management system of a leather factory (MES / IIoT). It collects machine readings, makes them reliable, computes output and piece-rate pay, and presents it all in eight web and Android apps.

8
web and Android apps on a single API contract
49
releases in 5 months, more than two a week
≈ 10,500
automated tests, in Java and Kotlin
≈ 10
shop-floor machines connected in real time
What does the animation show?

Simulation: each line is a window of readings from a spray machine, and the newest is written at the bottom. A suspicious reading is never silently fixed: it is flagged in red, with the anomaly code.

A complete IIoT system, from machine to screen

Jiarun IOT: production-management (MES) and connected-machine (IIoT) software for the leather industry. Independent contract (freelance), sole developer, since May 2026. In production at a leather manufacturer; version 1.4.11 deployed on 12 October 2026. jiarun.tech

The journey of one reading, from machine to screen, in six steps:

  1. Factory floor: the machines

    Details

    Each machine has an industrial touch screen (HMI) wired to its PLC. I defined the format of the messages these screens send (MQTT protocol) and each machine’s identity; the factory’s engineers configure the screens to that spec.

    device/{clientId}/spray device/{clientId}/energy-points

    • Spray, v1 then v2: v2 numbers every message, so a duplicate can be recognised.
    • Energy (electricity, steam, water): a fixed 6-channel format (77 fields) and a configurable-points format, described by a versioned terminal → equipment → point inventory; at most 64 devices, 256 points and 128 KiB per message.
    • Identity: one permanent connection ID (ClientID) per machine, issued by the platform back end and never reused.
  2. MQTT broker: the message relay

    Details

    The broker (Mosquitto) receives the machines’ messages and passes them to the back end. Guaranteed delivery (QoS 1) and persistent sessions: if the back end stops, the broker holds up to 20,000 messages, about 80 minutes of downtime absorbed without loss.

    • VerneMQ was evaluated, then retired: one broker to operate.
    • On the Java side, a Paho client with cleanSession=false: the session resumes where it left off.
  3. Ingestion: nothing lost, nothing counted twice

    Details

    The back end acknowledges a message to the broker only once it is stored in the database. If the write fails, the message is not acknowledged and the broker sends it again. A message received twice is counted once.

    • Uniqueness key client|seq|deviceTime: a resend is recognised as a duplicate; the same key with different content is rejected.
    • A malformed message cannot break the connection.
    • Stored readings are never modified. A late reading recomputes the neighbouring intervals in a single transaction, in the machine’s own time order.
  4. Quality: flag, never disguise

    Details

    A suspicious reading is not corrected: it gets flags that say why. For example: an impossible or physically out-of-range value, special code, missing or duplicate message, clock off by more than 10 minutes. The raw value is kept, but left out of the statistics.

    • Flags can be added, never redefined; the stored value is never rewritten.
    • Three-state indicators: true, false or unknown (missing data is not “false”). Known special codes: a speed of 99 means “cleaning”.
    • Counters: resets, 16- and 32-bit wraparounds, missing values, gaps that are too long. Units converted on arrival (Chinese jin and kg, sq ft and m², g/m²).
    • No interpolation, no invented zero: a doubtful interval leaves the statistics, and an incomplete total is published as “partial”, with its coverage.
  5. Storage and API

    Details

    PostgreSQL 16 with TimescaleDB, a time-series extension. Hourly and daily totals are recomputed only where a reading changed, and screens update in real time (WebSocket). A query that is too heavy fails rather than return an incomplete total.

    • Two Spring Boot back ends (Java 25, 10 Maven modules): one runs the factory, the other manages the platform; each has its own database and Flyway migrations.
    • Tests: ArchUnit enforces module boundaries, Testcontainers runs a real PostgreSQL for integration tests, PIT measures test strength through mutation testing.
    • Errors: the client gets a stable code and a trace ID, never SQL, stack traces or personal data.
    • Cursor pagination: stable lists, even while new readings arrive.
  6. Eight apps

    Details

    One API contract (openapi.json) generates the Kotlin and TypeScript client code of every app: if the API changes, the Android apps stop compiling until they catch up. A business rule: machine readings never create a production report or a wage. The machine measures; only a person declares what gets paid.

    • Production and payroll consoleReact 19
    • Operations centreReact 18
    • Public websiteNext.js 15
    • AI analysis for managementPWA
    • Shop-floor tabletKotlin
    • Operator phoneKotlin
    • Mobile appKotlin
    • Workstation screenKotlin
    App technologies
    • Web: React 19, TypeScript, TanStack Router, Query and Table, Zustand, react-hook-form and Zod, react-intl, Tailwind; tests with Vitest, MSW and Playwright; module boundaries checked by ESLint.
    • Android: Kotlin 2.2, Jetpack Compose, Hilt, Room, Retrofit, WorkManager, CameraX; seven languages; kiosk mode (Device Owner) and over-the-air updates.
    • Voice: a Python service (faster-whisper) transcribes voice recordings.
From machines to eight apps: MQTT broker, ingestion, quality, storage, API contract Factory floor ≈10 machines, meters MQTT broker Mosquitto, QoS 1 Ingestion acked after the write Quality anomaly flags PostgreSQL + Timescale 2 Spring Boot back ends AI agent read-only openapi.json WebOpsSiteAI PWAPadPhoneMobileStation
Context, figures, five decisions≈ 2 min read

The context: the factory sprays finishes onto hides and pays its workers by the piece. It wants to know, machine by machine and shift by shift, what was produced, consumed and paid. The difficulty: the machines’ industrial screens drop messages, sometimes impersonate one another and switch units, while payroll tolerates no approximation.

Tests
About 5,200 Java tests and 5,300 Kotlin tests, plus 226 front-end test files.
Delivery
49 releases since May 2026. The latest applied 6 database migrations in 0.6 s, with no error.
Hardware
About 10 connected spray machines, and 13 registered Android devices: 4 tablets, 7 phones, 2 workstation screens.
Performance
Synthetic load test on the real code: 65.7 messages stored per second, 95% of them in under 18.9 ms, 0 errors. A test measurement, not a production SLA.

Figures as of 12 October 2026.

Five decisions, and what they cost me

Acknowledge a message only once it is stored

WhyA message is lost only if the database refused it, and then the broker sends it again.

What it cost meEvery resend creates a duplicate to recognise: each message needs a uniqueness key, and a clear rule for when the same key comes back with different content.

Flag anomalies, never rewrite values

WhyA rewritten value is destroyed evidence. When a cleaning rule changes, the whole history must be recomputable.

What it cost meStatistics are computed on read or rebuilt. Every algorithm change means recomputing history, after a rehearsal on a copy of production.

Never invent a zero

WhyA missing counter is not a zero. Payroll and production totals cannot rest on an estimate.

What it cost meSome totals stay “partial”, and the interface has to show it: a less flattering dashboard, but a correct one.

One API contract for eight apps

WhyEight apps, each moving at its own pace, must understand the server in exactly the same way.

What it cost meEvery field change means regenerating code and rebuilding the Android modules. On the TypeScript side, the tool (springdoc) does not mark required fields: the shape of the data is checked, not its presence. That annotation work is still to do.

Merge fast, deploy only what is fully verified

WhyAgents produce faster than the full test suite runs: waiting for it on every merge would have stalled the work.

What it cost meThe main branch can stay red for a while; we fix forward. Only a version that passes the whole suite, on a dedicated test machine, can go to production.

When field data lies

An industrial system is judged on its real data. The rule: recompute history, never touch a raw reading.

Four problems found in production

An overly strict filter erased real output

Cause and fix
Finding
On one machine, a day’s produced surface was under-counted by 54%.
Cause
A noise filter rejected any rate above 8 units per second. But some real production rates exceeded that cap.
Fix
Filter replaced by an algorithm applied at read time (spray-interval-v4), leaving the raw readings untouched, then history recomputed. Rehearsed first on a copy of production: 1,363 hourly rows and 75 production runs recomputed in about 4 minutes.
Proof
On 4 machine-days, published totals match an external reference, within 1% at worst.

Eight machines, one identity

Cause and fix
Finding
Data from each machine arrived only 8 to 33% of the time.
Cause
Eight screens shared the same MQTT connection ID. The broker accepts one connection per ID, so the screens kicked each other off, about 25,000 times a day.
Diagnosis
Readings were lost, but never attributed to the wrong machine: what did arrive stayed reliable.
Fix
One permanent ID per machine, issued by the back end, and a machine-by-machine migration plan for the factory.

A field that did not mean what its name said

Cause and fix
Finding
One machine’s material consumption was overstated about 4.5 times.
Cause
Its “used weight” field (usedWeight) held not a running total but a difference: W0 − totalWeight.
Detection
A mass balance: a machine cannot consume more than what was loaded into it.
Fix
The calculation now uses the field’s real meaning for that machine.

A production incident, fixed the same day

Cause and fix
Finding
In production, spray data stopped updating.
Cause
A real-time refresh re-read an entire table every second.
Fix
Hotfix shipped the same day (version 1.4.7, on 5 October 2026) with at most 79 s of API downtime, then consolidated in version 1.4.8.
Demo: why an average lies≈ 1 min read

Why a reading-by-reading average lies

A machine sends more readings while it works than while it is idle. An average taken reading by reading therefore gives too much weight to busy periods, and it shifts as soon as fewer readings are stored. Weighted by how long each reading lasts, the average stays correct.

0 min15 min30 min45 min60 mintime-weightedreading by reading
Mean throughput over a simulated hour, in m²/h. True value: 535.1
StorageReading by readingTime-weighted
1 reading / 2 s783.9 +46.5%535.1 0.0%
1 reading / 10 s667.9 +24.8%535.1 0.0%
1 reading / 60 s518.0 −3.2%535.1 0.0%
A teaching simulation. On the factory’s real data, the same test showed a ±13% median deviation for the reading-by-reading average, against 0 for the time-weighted one. Statistics therefore use time weighting, and stored volume is only reduced after a replay of history proves that results do not change.

AI in my products: the model proposes, code verifies

Three systems, one rule: the server checks every number, dish and correction before it reaches the user.

An analysis agent for the factory, written without a framework

Jiarun IOT, 2026. Java. Built into the production back end, enabled factory by factory.

The owner asks a question; the agent answers with charts in which the model writes no number.

See the demo and details≈ 2 min read

I don’t use an agent framework: I write the loop myself, and I limit what the model is allowed to do.

The factory owner asks a question in plain language. The agent queries the database read-only and answers with charts whose every number comes from the database, not from the model. The same agent serves as the product’s built-in help, that is, its customer support.

Which machine used the most energy this week?
  1. draft saved, waiting for confirmation
  2. confirmed: the draft runs exactly as saved, 90 s at most
  3. 1describelists the views allowed for this role (read-only)
  4. 2querySELECT machine, SUM(kwh) AS kwh FROM v_energy_day WHERE day >= current_date - 7 GROUP BY machine ORDER BY kwh DESC12 rows, complete → evidence-1
  5. 3chart{ evidenceId: "evidence-1", type: "BAR" }the server reads the values from evidence-1; factory ✓ unit ✓
  6. 4reviewno tools: writes the final answer
  7. PDF report saved, SHA-256 fingerprint checked on download
Example trace, fictional data. The model wrote none of the chart’s four numbers: it only cited evidence-1.
  • A bounded loop: at most 16 steps and 64 tool calls. Every call is checked (allowed tool, valid parameters) before any database access.
  • Read-only access to approved views, with one database login per factory and role. If that login is missing, the agent refuses; it never falls back to an account that can write.
  • The model writes no numbers: a chart cites a piece of evidence (a query result), and the server reads its values, checking the factory, the units and that the result is complete.
  • Help mode, which serves as customer support: 32 reviewed articles, filtered by app and role. The agent reads the whole article before answering and cites its sources.
More details
  • Draft, then confirmation: the question is saved, then run exactly as saved; confirming twice does not run it again, and the PDF report carries a SHA-256 fingerprint.
  • A mobile app (PWA) for management, with on-device dictation (sherpa-onnx in WebAssembly, a 102 MB model verified by SHA-256): the voice never leaves the phone.

A WhatsApp assistant for restaurants

Ruby Tech, 2026. Sole owner of the assistant within the team (194 of the module’s 199 commits). Java 21, Spring Boot 3, PostgreSQL 16, Redis.

It answers customers from the restaurant’s real data; dishes and allergens are checked by code.

See the demo and details≈ 2 min read

A restaurant’s customers write on WhatsApp or on the website: opening hours, menu, allergens, bookings. The assistant answers in their language from the restaurant’s real data, and hands over to the staff when needed.

  1. Hi, are you open on Sunday lunchtime?

  2. Hello! I’m the restaurant’s AI assistant. Yes, Sunday from 12:00 to 14:30. Shall I book you a table?

    BookSee the menu

    rateinputquotacachebudgetmodel

    opening time checked against the restaurant’s data

  3. Do you have a gluten-free dish?

  4. Yes: the porcini risotto and the grilled salmon are marked gluten-free by the restaurant.

    See the menu

    rateinputquotacachebudgetmodel

    dishes on sale only, per the restaurant’s own labels

  5. Am I talking to a real person?

  6. No, I’m an AI. I’ll let the team know: someone will reply during opening hours.

    Call the restaurant

    rateinputquotacachebudgetmodel

    mandatory AI notice, handed over to a human

Example conversation with a fictional restaurant. Under each answer: the checks passed before calling the model, then the server’s verification.
  • One answer path for the website, WhatsApp and voice notes: a new channel is just an adapter, never a second path.
  • Five checks before calling the model, cheapest first: rate limit, input validity, quotas and kill switches, answer cache, budget reservation.
  • A hallucination is not an instruction: every dish the model proposes is re-checked against what is on sale, and allergens and legal notices are enforced by code, not by the prompt.
  • Direct integration of the WhatsApp Business API: signed messages (HMAC), an immediate reply then background processing, duplicates dropped, the 24-hour window respected. If a message format fails, sending falls back step by step to plain text, so the “I am an AI” notice required by the AI Act (art. 50) always arrives.
More details
  • Automatic evaluation: test cases derived from each restaurant, checks done by code (intent, dishes on sale, allergens; every quoted price and time must exist in the data) and the cost of each run.
  • An in-house multi-provider AI layer (OpenAI- and Anthropic-compatible APIs): routing, cost caps, prompt caching. Six languages. Voice notes are transcribed in memory, never stored.

Jellydog: AI sentence correction, measured before going live

Personal project, live at jellydog.fr. Go, React and TypeScript, PostgreSQL 17.

The AI corrects a sentence within seconds; its quality was measured against thresholds set in advance.

See the demo and the evaluation≈ 1 min read

A vocabulary app for Chinese-speaking students in France. The learner writes a sentence with the word of the day; the AI corrects it within seconds and explains the mistakes in Chinese. No grade and no opinions on style: only objective errors.

Word of the day emprunter (to borrow)

Hier, j’ai emprunté deux livre à la bibliothèque.

Hier, j’ai emprunté deux livres à la bibliothèque.

名词复数要加 -s:deux livres。 (“A plural noun takes an -s.”)

Use of the word of the day: correct

  • A simple filter runs before the AI: 30 of the 197 test sentences (impossible to assess, or a plain copy of the example) are handled without calling the model.
  • Strict JSON output (JSON Schema): every flagged error must appear exactly once in the sentence, without overlap, and the server rebuilds the corrected sentence. The model never rewrites the sentence.
More details
  • Common-error cards for each exercise, which the model cites by number: a single invalid reference rejects the whole result.
  • Every evaluation run records the fingerprint of the prompt, test set, schema and cards; the Go validator must reject exactly what the Python validator rejects.
Thresholds set before measuring; results on the 167 sentences sent to the model
CriterionThresholdWithout cardsWith cards
Word use judged correctly≥ 90%94.6%95.2%
Wrong use accepted as correct≤ 5%3.6%1.8%
Unusable answers≤ 1%0%0%
Response time (95% of cases)≤ 3.2 s3.16 s2.12 s
Prompt served from cache–0%76%
Cost of the run–$0.024$0.013

First run with cards: 92.8%. The model mistook scenario details for a change of meaning; I fixed two sentences of the instructions, ran it again, and kept all three runs. The test sentences are synthetic: this is not teacher grading.

Working with AI agents: I decide, they execute, tests have the final say

Tools: Claude Code, Codex and Cursor, with several agents working in parallel.

AI did not mainly make me code faster. It freed my time for what cannot be delegated, deciding:

About 3,900 commits and 49 releases in five months as the sole developer, with up to some twenty agents in parallel, each in its own copy of the repository. With agents, volume proves nothing: what counts is what tests and production confirm.

How a change travels from an agent to production:

  1. Isolated copyeach agent works in its own copy of the repository (a Git worktree) and stops at a commit; I review the diff
  2. Integrationrebase, database migrations checked (unique numbers, never edited), quick tests of the affected apps
  3. main branchthe full test suite starts on a dedicated test machine
  4. main-verifiedonly advances to a commit where every test passes
  5. Productionreceives only that commit, after a rehearsal on a copy of production and a tested rollback
My principles, and an A/B test≈ 1 min read

Context is a budget

The instruction file read at every session holds only the costly misunderstandings and pointers; everything else loads on demand.

An important rule should be checked by a machine

If a rule can be checked automatically, it becomes a hook, an architecture test or a consistency check. A rule you have to keep repeating to agents is in the wrong place.

Tests are the counterweight

A good test judges the code from the outside instead of mirroring it: that is what stops an agent from approving its own work.

One source, many tools

Rules are written once, then generated for each agent tool; a checker refuses any drift. The same principle as the API contract, applied to rules.

I measured the effect of my own agent instructions

Do my reusable instructions (“skills”) really change the agent’s behaviour? An A/B test with a protocol fixed before measuring, in a sandbox with a real Git remote. Verdicts come from the state of the repository, not from the agent’s own account; placebo scenarios and blind review.

  1. New feature

    With the skilltests the error cases, waits for approval

    Withoutdeclares itself “done” and commits directly

    Verdictslight advantage

  2. Data rewrite

    With the skillidentical result

    Withoutidentical, byte for byte

    Verdictskill redundant, retired

  3. Commit and push

    With the skillread the rule, then pushed to the main branch anyway

    Withoutopened a branch

    Verdictrule turned into an automatic block (hook)

Result: +26% tokens per run, for an inconsistent gain with an already strong model. Two skills retired; what must hold moved into a blocking hook. A pilot with one run per variant: a signal, not a statistic.

Background

Two years and seven months in industry before engineering school, then three products built alongside my studies.

  1. May 2026 →

    Jiarun IOT

    Full-stack developer, sole engineer on the whole software, independent contract (freelance)

    Production-management and connected-machine software for the leather industry, in production at a manufacturer. From the machine protocol to the apps.

  2. 2026 →

    Ruby Tech

    Full-stack developer, part-time permanent contract, after a five-month internship

    Ordering and loyalty platform for restaurants. I build the WhatsApp AI assistant, part of the back office (React, TypeScript) and field deployment: servers installed automatically in restaurants, Android tablets in kiosk mode, and a self-hosted operations centre to monitor and update the fleet remotely.

  3. 2026 →

    Jellydog

    Personal project, live

    French vocabulary app with AI sentence correction; 201 lessons online.

  4. 2024 → 2027

    CY Tech, CY Cergy Paris Université

    Engineering degree in computer science, embedded systems track

    Taught entirely in French.

  5. Sept 2021 → Apr 2024

    Tangshan Huahong Technology, China

    Front-end and Android developer

    Web interfaces for 3D scanning equipment (Vue, JavaScript); a face-recognition payment terminal (Android, Java).

  6. 2017 → 2021

    Xidian University, Xi’an

    Bachelor’s in electronic and information engineering, Sino-French programme

Skills

Everything below has been used in production, not only in coursework.

Back end
Java 17–25, Spring Boot 3 / 4 (Security, JWT, Data JPA, WebSocket, Actuator), Go, REST APIs under an OpenAPI contract, Flyway, Maven
Front end
TypeScript, React 18 / 19 (TanStack Router, Query, Table, react-hook-form, Zod, Zustand), Next.js 15, Vue 3, Tailwind, PWA, internationalisation, accessibility
Mobile
Android Kotlin (Jetpack Compose, Hilt, Room, Retrofit, WorkManager, CameraX), kiosk mode, OTA updates
Data and IoT
PostgreSQL, TimescaleDB, MySQL, Redis, MQTT (Mosquitto, Paho), time series, data quality
Applied AI
agents written without a framework (tool calling), structured outputs (JSON Schema), evaluation against thresholds set in advance, prompt caching, OpenAI, Anthropic, DeepSeek, Gemini, WhatsApp Business API, sherpa-onnx, faster-whisper
AI-assisted development
Claude Code, Codex, Cursor; parallel agents on Git worktrees, agent rules enforced by hooks
Quality
JUnit 5, ArchUnit, Testcontainers, PIT, Vitest, Testing Library, MSW, Playwright, Robolectric, go test -race
Operations
Linux, Docker Compose, Nginx, systemd, Prometheus, Alertmanager, Grafana, VictoriaMetrics, GitHub Actions, GitLab CI
Languages
French B2 (engineering degree taught in French), English B2, Chinese native

What I’m looking for

A six-month end-of-studies internship from 4 May 2027, in or around Paris, in full-stack development, and I am open to a permanent position after the internship. Ideally in a team close to its customers, that uses AI to design and not only to execute. I like to own a scope early: that is how I have learned the most.