---
title: "Dr.Well-B — Multi-Service Platform for Doctors — Tran Manh Tuan"
description: "Multi-service platform for doctors and dentists in Japan: verified membership with approvals, profile-targeted ads, dynamic surveys, job search and batch reward points."
canonical: "https://tmtuan.dev/en/projects/dr-well-b"
language: "en"
image: "https://tmtuan.dev/_img/projects/dr-well-b/dr-well-b.a4fd353d-og.jpg"
tokens: 5026
---

# Dr.Well-B

**Dr.Well-B** is a membership platform for doctors, dentists and medical students in Japan, launched publicly in 2023. Rather than solving one task, it brings many services into one portal: verified professional accounts, profile-based advertising, surveys, recruitment and job search, professional content and a reward-point programme — all run by an operations team from a back-office console.

I was the **Tech Lead** from 2022 to 2024 and also worked directly on the **backend**: technical direction, system and data-flow design, the Node.js / NestJS API, Firestore, search, background processing and the Google Cloud services around them, code review, and coordination between the Japanese side and the development team in Vietnam.

> **Why it is a management-system project:** every module reads the same member profile and writes to the same action log. A change to how ads are targeted changes points, reports and exports downstream — so the work was as much about workflows, data flow and back-office control as about individual features.

## Modules and who uses them

Two groups use the system. Members — doctors, dentists, doctors in training, independent practitioners and medical students — use the member portal. Operators and admins use a separate administration console to review accounts and approvals, manage ads, suppliers, surveys and job postings, and run downloads.

```mermaid
flowchart TB
    accTitle: Dr.Well-B module overview
    accDescr: Doctors, dentists, medical students and other members use the member portal; operators and admins use the administration console. The member portal leads to accounts and profiles, personalised advertising, dynamic surveys, recruitment and job search, and professional content. The administration console manages accounts and profiles, advertising, surveys, recruitment and downloads/exports. Advertising and jobs generate points and rewards. Advertising, surveys, jobs and accounts feed analytics and reporting. Points and surveys produce download/export files, and downloads/exports send notifications and email.

    USER["Doctors / Dentists / Medical students / Members"]
    ADMIN["Operators / Admins"]

    PORTAL["Dr.Well-B member portal"]
    ADMIN_PORTAL["Dr.Well-B administration"]

    ACCOUNT["Accounts & profiles"]
    ADS["Personalised advertising"]
    SURVEY["Dynamic surveys"]
    JOB["Recruitment / job search"]
    CONTENT["Professional content"]
    POINT["Points / rewards"]
    DOWNLOAD["Download / export"]
    NOTIFY["Notifications / email"]
    ANALYTICS["Analytics / reporting"]

    USER --> PORTAL
    ADMIN --> ADMIN_PORTAL

    PORTAL --> ACCOUNT
    PORTAL --> ADS
    PORTAL --> SURVEY
    PORTAL --> JOB
    PORTAL --> CONTENT

    ADMIN_PORTAL --> ACCOUNT
    ADMIN_PORTAL --> ADS
    ADMIN_PORTAL --> SURVEY
    ADMIN_PORTAL --> JOB
    ADMIN_PORTAL --> DOWNLOAD

    ADS --> POINT
    JOB --> POINT

    ADS --> ANALYTICS
    SURVEY --> ANALYTICS
    JOB --> ANALYTICS
    ACCOUNT --> ANALYTICS

    POINT --> DOWNLOAD
    SURVEY --> DOWNLOAD

    DOWNLOAD --> NOTIFY
```

_The portal and the console share one set of modules. Profiles and action logs are what connect them, rather than each feature standing alone._

## My responsibilities

- Set the technical direction and chose the solution for each large feature, assessing technical risk before it was built.
- Designed the system architecture and how modules exchange data.
- Designed and built backend APIs in **Node.js / NestJS**.
- Designed the Firestore data model, Elasticsearch search, background processing and the cloud services behind them.
- Reviewed technical designs and pull requests, reviewed security, and unblocked the team on hard problems.
- Set the unit and automation testing strategy (Cypress) for a system with many rules.
- Supported staging and production deployments, and tracked technical progress and risk across the large features.

## Membership with professional verification

Registration here is a verification workflow, not an email-and-password form. The platform collects professional data — specialty and sub-specialty, workplace, department, position, graduation year, university, student number, licence number, clinical training — and verification documents such as licence images, student cards, training certificates and CVs.

When the details can be verified, the account is activated straight away. When they cannot, the application moves into an admin approval flow: the member is asked for more documents, an admin reviews them, the decision is recorded in an approval history, and the record is locked once it is approved. Each outcome sends its own email.

```mermaid
flowchart TD
    accTitle: Registration and verification flow for medical professionals
    accDescr: A user starts registration, enters personal details, professional details, licence or school details, uploads verification documents and submits. If the details can be verified, the system creates and activates the member account and sends a registration-complete email. If not, the application moves to additional review: the user is asked for more documents and an admin reviews and approves. Once approved, the account is activated and the approval history is updated; if not yet approved, the flow returns to requesting more documents.

    A["User starts registration"]
    B["Enter personal details"]
    C["Enter professional details"]
    D["Enter licence / school details"]
    E["Upload verification documents"]
    F["Submit registration"]
    G{"Can the details be verified?"}

    H["Create / activate member account"]
    I["Send registration-complete email"]

    J["Move to additional review"]
    K["Request additional documents"]
    L["Admin review / approval"]
    M{"Approved?"}

    N["Activate account"]
    O["Update approval history"]

    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G

    G -- "Yes" --> H
    H --> I

    G -- "No" --> J
    J --> K
    K --> L
    L --> M

    M -- "Yes" --> N
    N --> O

    M -- "Not yet" --> K
```

_Automatic verification where possible, human approval where not — with a history of every decision._

## Profile-targeted advertising

Ads are not random banners. Each one carries delivery rules: placement and banner size, destination URL, banner or text, tags, target specialties and medical societies, a delivery window, a click limit, a priority and a status. Selection filters every active ad against the member's profile and history, and what a member does with an ad feeds back into the next selection — hiding or refreshing an ad can drop it from that member's candidates or lower its priority for them, while signs of interest keep similar ads coming.

On the console side, operators create, copy and schedule ads, set limits and priority, manage the suppliers behind them, and can download the list of members an ad's tags match.

```mermaid
flowchart TD
    accTitle: Profile-based advertisement selection flow
    accDescr: An admin creates and publishes ads, the system fetches the active ads and filters them through four checks: the ad matches the user's specialty, society or tags; it is inside its delivery period; its click limit has not been reached; and the user has not hidden or refreshed it. An ad that fails any check is dropped or its priority is lowered. Ads that pass every check are scored for priority and relevance, ranked, prepared as banner, text and URL, displayed, and an action log is written; the action log feeds back into the next selection.

    A["Admin creates / publishes ads"]
    B["Fetch active ads"]

    C{"Matches specialty / society / tag?"}
    D{"Within the delivery period?"}
    E{"Click limit reached?"}
    F{"User hid / refreshed this ad?"}

    G["Score priority / relevance"]
    H["Rank candidates"]
    I["Prepare banner / text / URL"]
    J["Display ad"]

    K["Write action log"]
    X["Drop or lower priority"]

    A --> B
    B --> C

    C -- "No" --> X
    C -- "Yes" --> D

    D -- "No" --> X
    D -- "Yes" --> E

    E -- "Yes" --> X
    E -- "No" --> F

    F -- "Yes" --> X
    F -- "No" --> G

    G --> H
    H --> I
    I --> J
    J --> K

    K --> B
```

_Four filters decide whether an ad is a candidate at all; only then does ranking start. The action log closes the loop._

## A configurable survey engine

Admins build surveys without code: title, description, notes, minimum and maximum responses, publish state and a linked ad, then questions with a type, required or optional, answer lists and free input. Questions can depend on each other — "if question 1 is A, show question 2" — which gives conditional branches, sub-questions and embedded questions. Members get a form that changes with their answers, and in some flows a changed answer overwrites the earlier one instead of creating a duplicate submission.

```mermaid
flowchart TD
    accTitle: Dynamic survey flow from configuration to reporting
    accDescr: An admin creates a survey, configures its metadata, adds questions, chooses each question type, sets required or free-input options, configures the relations between questions and publishes. A member opens the survey and answers; if an answer triggers a follow-up question, that question is shown or enabled before continuing, otherwise the member continues straight away. The member submits, the system stores the survey result, and the data moves on to reporting, downloads and analytics.

    A["Admin creates a survey"]
    B["Configure metadata"]
    C["Add questions"]
    D["Choose question type"]
    E["Set required / free input"]
    F["Configure question relations"]
    G["Publish survey"]

    H["Member opens the survey"]
    I["Answer a question"]
    J{"Does the answer trigger a follow-up?"}

    K["Show / enable follow-up question"]
    L["Continue the survey"]
    M["Submit"]
    N["Store survey result"]
    O["Reporting / download / analytics"]

    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G

    G --> H
    H --> I
    I --> J

    J -- "Yes" --> K
    K --> L

    J -- "No" --> L

    L --> M
    M --> N
    N --> O
```

_The form is data, not code: question relations decide at answer time which questions a member sees._

## Points and rewards from action logs

Points are not calculated inside the request that earns them. Eligible actions — ad clicks, job actions and configured bookings — are written to action logs, and batches do the rest. The daily batch reads unprocessed logs, checks eligibility, looks up the points for each action, applies the rules (several clicks on the same ad or job in one day count once; points are capped per month), writes a point distribution plan and marks the logs as processed. The monthly batch aggregates points and profiles into distribution results and files. Members redeem their points through a partner points app.

```mermaid
flowchart TD
    accTitle: Point and reward calculation through action logs and batches
    accDescr: Ad clicks, job actions and other eligible bookings or actions are all written to action logs. A daily batch reads the action logs and checks rules, duplicates and limits; the result both creates a point distribution plan and marks the logs as processed. A monthly batch aggregates the point distribution plans into a monthly distribution result, from which files and reports are generated.

    ADS["Ad click"]
    JOB["Job action"]
    OTHER["Eligible booking / action"]

    LOG["Action logs"]

    DAILY["Daily batch"]
    VALIDATE["Check rules / duplicates / limits"]
    PLAN["Point distribution plan"]
    MARK["Mark logs as processed"]

    MONTH["Monthly batch"]
    RESULT["Monthly distribution result"]
    FILE["Generate file / report"]

    ADS --> LOG
    JOB --> LOG
    OTHER --> LOG

    LOG --> DAILY
    DAILY --> VALIDATE

    VALIDATE --> PLAN
    VALIDATE --> MARK

    PLAN --> MONTH
    MONTH --> RESULT
    RESULT --> FILE
```

_Event log first, daily calculation next, monthly aggregation last. The user action finishes immediately; the business rules run afterwards._

## Asynchronous exports and notifications

Survey results, full doctor-profile exports, CSV files and ZIP archives can take far longer than a request should. They run as background work on Cloud Tasks and Cloud Functions; the file lands in Cloud Storage, and Firebase Cloud Messaging tells the admin when it is ready — the console asks for notification permission for exactly this. A download history keeps track of past exports.

Business email covers the rest of the lifecycle: registration complete, licence verified, licence not verified, additional documents requested, and system or operational notices.

```mermaid
flowchart LR
    accTitle: Asynchronous download flow
    accDescr: An admin requests an export through the backend API. The backend hands the work to a background task or batch; the task reads Firestore and business data, builds a CSV or ZIP file and stores it in Cloud Storage. When the file is ready, Firebase Cloud Messaging sends a completion notice, so the admin does not have to keep a screen open waiting on a long request.

    U["Admin requests an export"]
    API["Backend API"]
    TASK["Background task / batch"]
    DATA["Firestore / business data"]
    FILE["CSV / ZIP"]
    STORAGE["Cloud Storage"]
    FCM["Firebase Cloud Messaging"]
    DONE["Completion notice"]

    U --> API
    API --> TASK

    TASK --> DATA
    DATA --> TASK

    TASK --> FILE
    FILE --> STORAGE

    STORAGE --> FCM
    FCM --> DONE
```

_The admin asks once and is told when the file exists, instead of holding a screen open on a long request._

## Job search on Elasticsearch

The member portal includes recruitment and job search, filtered by free text, employment type and prefecture. Search, filtering and sorting run on Elasticsearch indexes rather than on Firestore, and searches and clicks are logged for points, tracking and analytics.

## High-level architecture

Angular applications sign in through Firebase Authentication and call a Node.js / NestJS backend. Firestore holds business data, Cloud Storage holds files, Elasticsearch serves search, and Cloud Run, Cloud Functions and Cloud Tasks carry the background work. BigQuery serves aggregation and reporting, Cloud Logging collects logs, and Firebase Cloud Messaging delivers notifications.

```mermaid
flowchart TB
    accTitle: Dr.Well-B high-level technical architecture
    accDescr: Users and admins use Angular web applications. The frontend signs in through Firebase Authentication and calls the Node.js / NestJS backend. The backend works with Firestore, Cloud Storage, Elasticsearch and Cloud Run. Cloud Run queues work in Cloud Tasks, sends notifications through Firebase Cloud Messaging, writes data to BigQuery and logs to Cloud Logging. Cloud Tasks and Cloud Functions write to Firestore; Firestore feeds BigQuery; Cloud Functions also log to Cloud Logging. These are the main components in the project documentation, not a one-to-one copy of the internal production diagram.

    USER["Users"]
    ADMIN["Admins"]

    FE["Angular web applications"]

    AUTH["Firebase Authentication"]
    API["Node.js / NestJS backend"]

    FIRESTORE["Firestore"]
    STORAGE["Cloud Storage"]
    SEARCH["Elasticsearch"]

    RUN["Cloud Run"]
    FUNCTION["Cloud Functions"]
    TASK["Cloud Tasks"]

    FCM["Firebase Cloud Messaging"]
    BIGQUERY["BigQuery"]
    LOG["Cloud Logging"]

    USER --> FE
    ADMIN --> FE

    FE --> AUTH
    FE --> API

    API --> FIRESTORE
    API --> STORAGE
    API --> SEARCH

    API --> RUN

    RUN --> TASK
    FUNCTION --> FIRESTORE

    TASK --> FIRESTORE

    FIRESTORE --> BIGQUERY
    RUN --> BIGQUERY

    RUN --> LOG
    FUNCTION --> LOG

    RUN --> FCM
```

_The main components in the project documentation. This is not a one-to-one copy of the internal production diagram._

## How data moves between modules

```mermaid
flowchart LR
    accTitle: Overall data flow between modules
    accDescr: The user profile is an input to ads, surveys and jobs. User actions are written to action logs; ads and jobs write to the action logs too, while surveys feed analytics and BigQuery directly. Action logs feed the point engine and analytics. Jobs use search. The point engine and analytics both produce exports and reports. The content module stands on its own and takes no part in these data flows.

    PROFILE["Profile"]
    ACTION["User actions"]

    ADS["Ads"]
    SURVEY["Survey"]
    JOB["Job"]
    CONTENT["Content"]

    LOG["Action logs"]
    POINT["Point engine"]
    SEARCH["Search"]
    DATA["Analytics / BigQuery"]
    EXPORT["Export / report"]

    PROFILE --> ADS
    PROFILE --> SURVEY
    PROFILE --> JOB

    ACTION --> LOG

    ADS --> LOG
    JOB --> LOG
    SURVEY --> DATA

    LOG --> POINT
    LOG --> DATA

    JOB --> SEARCH

    POINT --> EXPORT
    DATA --> EXPORT
```

_Profiles flow into ads, surveys and jobs; actions flow into logs; logs and survey data flow into points, analytics and exports._

## What the project covers

| Concern in a large management system | How it shows up in Dr.Well-B |
| --- | --- |
| Business workflow with approval | Licence verification, admin approval, approval history, locked records |
| Rules configured by operators, not code | Survey engine with question relations; ad delivery rules |
| Personalisation | Ad selection by specialty, society, tags, schedule, limits and behaviour |
| Event and batch processing | Action logs, a daily point batch, then monthly aggregation and files |
| Long-running work | Background exports on Cloud Tasks / Cloud Functions, FCM notification |
| Search at scale | Elasticsearch indexes and sorting, separate from Firestore |
| Reporting | BigQuery aggregation, CSV/ZIP export, download history |
| Keeping many rules from regressing | Cypress automation across services, surveys and mobile browsers |

The diagrams are reconstructed at component level from the project's design documents. They omit internal URLs, identifiers, proprietary code and client data; the screenshots use test accounts and demo data, with internal names blurred.

## Details

- **Role:** Technical Lead · Backend · Cloud Infrastructure
- **Website:** [www.yokoso.dr-well-b.co.jp/](https://www.yokoso.dr-well-b.co.jp/)

## Technology

- Angular
- TypeScript
- Node.js
- NestJS
- Firebase Auth
- Firestore
- Firebase Hosting
- Cloud Storage
- Firebase Cloud Messaging
- Cloud Run
- Cloud Functions
- Cloud Tasks
- BigQuery
- Secret Manager
- IAM
- Cloud Logging
- Elasticsearch
- Docker
- Cypress

## Services this project shows

- [Custom Business Systems](https://tmtuan.dev/en/services/business-systems)
- [Backend Engineering](https://tmtuan.dev/en/services/backend-development)
- [Cloud Infrastructure](https://tmtuan.dev/en/services/gcp-cloud-infrastructure)

## Related articles

- [Docs-as-code Knowledge Base + Context Graph for a 15-project portfolio](https://tmtuan.dev/en/articles/knowledge-base-docs-as-code-context-graph)
- [How I Traced a Googlebot-only HTTP 500 Across Next.js, Cloud Run and the Hosting Edge](https://tmtuan.dev/en/articles/googlebot-500-firebase-hosting-nextjs)

## Structured data (JSON-LD)

```json
{"@context":"https://schema.org","@graph":[{"@type":"WebPage","@id":"https://tmtuan.dev/en/projects/dr-well-b#webpage","url":"https://tmtuan.dev/en/projects/dr-well-b","name":"Dr.Well-B — Multi-Service Platform for Doctors","description":"Multi-service platform for doctors and dentists in Japan: verified membership with approvals, profile-targeted ads, dynamic surveys, job search and batch reward points.","inLanguage":"en","isPartOf":{"@id":"https://tmtuan.dev/#website"},"about":{"@id":"https://tmtuan.dev/#person"}},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://tmtuan.dev/en"},{"@type":"ListItem","position":2,"name":"Projects","item":"https://tmtuan.dev/en/projects"},{"@type":"ListItem","position":3,"name":"Dr.Well-B","item":"https://tmtuan.dev/en/projects/dr-well-b"}]}]}
```
