

Redacting Japanese PII at Production Scale: Securing Long-Context VoC Data for AI Innovation
.png)
Hi, this is Emrys 👋
I'm an AI Engineer on the Money Forward Vietnam (MFV) AI Data team, currently working on the VoC (Voice of Customer) project in PMxAI Lab at Money Forward.
This work is being shaped by a cross-functional core team: me (Emrys), Quinn, Tony Hoang, Remi, Morio, and Elly. Together, we work across AI engineering, product thinking, data governance, and operational design.
We believe that every sufficiently messy problem hides a surprisingly elegant structure. Whether it's customer feedback, knowledge scattered across products and tools, or LLMs that don't quite behave the way we'd like, the fun part is uncovering that structure.
On the VoC project, that means building a trusted Voice of Customer intelligence pipeline — one that safely turns customer feedback from large volumes of support tickets, sales conversations, and other unstructured sources into actionable insights for the people building Money Forward products.
In the long term, we want VoC to become Money Forward's single source of truth for understanding customer needs, revenue risks, competitive gaps, and product opportunities. The hard question underneath it all: how can Money Forward make production-like customer feedback useful for AI and analytics without compromising security or trust?
This blog series is where we share that journey. Some posts will dive into engineering challenges, some into product ideas, and some into the small design decisions that don't usually make it into architecture diagrams. We'll start with one of the less glamorous, but essential topics: Redacting Japanese PII at Production Scale: Securing Long-Context VoC Data for AI Innovation
🧭 The Statement
As engineers, we all understand the hesitation around touching production. Breaking production can lead to financial loss, loss of trust, and a painful recovery effort. That's why Money Forward keeps production data inside a dedicated, isolated environment.
Unfortunately, cruel life happens.
Isolation protects the data, but it also makes the data harder to learn from. AI applications, analytics, and product improvements all rely on production data, yet that same data is often the most restricted. The challenge is no longer "how do we use production data?" but "how do we use it safely?"
💡 Personally Identifiable Information (PII) redaction is one pragmatic answer.
When data is neatly organized in tables, life is simple. Each column represents a single attribute, sometimes even standardized, making sensitive fields easy to identify, remove, hash, or encrypt before sharing the rest. In some cases, a simple regex would do the work.
But cruel life happens, again!
Real customer inquiries rarely arrive in perfect tables. They arrive as support tickets, sales call transcripts, chat logs, emails, and long conversations. Somewhere inside those thousands of words may be customer names, email addresses, phone numbers, usernames, passwords shared during debugging, or other sensitive information nobody intended to expose.
Suddenly, PII is no longer a column in a database. It's hidden somewhere inside tens of thousands of tokens of free-form text. And that's where our story begins.
🧩 The Definition
Before diving into the implementation, we first had to answer a deceptively simple question:
What exactly should we redact?
Not all entities carry the same level of sensitivity. A customer name is fundamentally different from a password, and treating them the same would either remove useful context or expose information that should never leave production.
So we classified entities into three sensitivity levels using two criteria: how risky the information would be if exposed, and how useful it remains for downstream product analysis.
| Level | Entity Name | Description | Examples |
|---|---|---|---|
| Level 1 | COMPANY | Company or organization names. | Example Co., Sample Client Ltd., 架空株式会社 |
| Level 1 | DEPARTMENT | Department or team names. | サンプル経理部, テストCSチーム, Example Operations Team |
| Level 1 | COMPETITOR | Competitor product or company names. | DemoProduct, SampleSuite, 架空会計クラウド |
| Level 1 | BUDGET | Budget amounts from the client side. | 月額10万円の検討予算, annual demo budget, sample renewal budget |
| Level 2 | PERSON_NAME | Real person names, including honorifics and variants. | 山田太郎, 田中花子様, Sample Taro, Example Hanako, サンプル リコ |
| Level 2 | Email addresses, excluding MF staff names. | sample.user@example.test, demo.customer@example.invalid | |
| Level 2 | PHONE | Phone numbers, including signatures and Japanese formats. | 03-1234-56XX, 050-2345-67XX, 080-3456-78XX, 090の4567の89XX |
| Level 2 | ADDRESS | Detailed physical addresses, postal codes, buildings, and rooms. | 〒123-4567 東京都サンプル区架空町12-34-56 サンプルビル8F, 大阪府テスト市例町23-45-67, 福岡県デモ市仮町34-56-78 |
| Level 3 | ID | Account, ticket, company, channel, UUID, or similar IDs. | ID-1234, ACCOUNT-87654321, ACC-20991231-4242, #TICKET-246810, CEXAMPLE01, p-example-13579, 123e4567-e89b-12d3-a456-426614174000 |
| Level 3 | SSN | Social security numbers, including Japan My Number. | 123-45-67XX, 1234-5678-90XX, sample government identifier |
| Level 3 | CREDIT_CARD_NUMBER | Credit card numbers. | 4111-1111-1111-1111, 5555-5555-5555-4444 |
| Level 3 | CREDIT_CARD_PROVIDER | Actual card brand or provider names. | Example Card, Sample Pay Card, Demo Credit, 架空カード |
| Level 3 | IP_ADDRESS | IPv4 or IPv6 addresses. | 192.0.2.10, 198.51.100.25, 203.0.113.42, 2001:db8::1 |
| Level 3 | URL | Private, internal, tracking, or user-specific URLs. | https://example.test/unsubscribe?token=sample-token-000&email=demo.customer@example.invalid&customer_id=CUST-0000 |
| Level 3 | PIN | PIN numbers. | 0000, 9999, 1234 as a dummy PIN |
| Level 3 | PASSWORD | Actual password or credential values. | DummyPassw0rd!, ExampleTemp@Pass123!, sample temporary password |
🟢 Level 1: Business Context These entities provide useful business context while posing relatively low privacy risk. Company names, departments, competitors, and budget references fall into this category. They are useful for product analysis and can generally be retained within our internal analytics workflows.
🟡 Level 2: Personal Information Data that can identify a person, such as names, emails, phone numbers, addresses, etc. These require protection, but authorized users may still need access, so we support selective unmasking.
🔴 Level 3: Confidential Credentials This category includes passwords, PINs, credit card numbers, government identifiers, IP addresses, and other credentials. Unlike Level 2 entities, these provide almost no value for downstream AI analysis while carrying the highest security risk. If one of these leaks, all hell breaks loose at Money Forward, and quite frankly, nobody needs to see a customer's password in the first place. So the decision was simple: always mask them.
🛠️ The Solution
In case you're wondering whether we jumped directly to the solution after defining the entities: no, we didn't. But to save some time, I'll leave the comparisons and results for the later sections.
🔎 Tuning the detector
When people hear LLM-based PII detection, they often imagine the hardest part is prompt engineering. That wasn't our experience.
From an implementation perspective, the detector itself is almost boring. Given a transcript, we ask an LLM to identify sensitive entities and return structured output. The implementation fits comfortably on a single screen. The difficult part starts after the first successful inference.
How do we know the detector is actually good enough?
Unlike traditional software, there isn't a unit test that guarantees an LLM will never miss a password hidden inside a 30,000-token transcript. Every prompt change improves some cases while potentially hurting others. Without a disciplined evaluation process, "it feels better" quickly becomes the only metric, and that's not a place we wanted to be.
So instead of treating prompt engineering as trial and error, we built a feedback loop around it.

Figure 1: The tuning loop that kept prompt changes tied to benchmark results, instead of vibes.
The tuning process came down to four ideas.
A model is only as good as its benchmark
Before tuning anything, we needed an evaluation dataset we could trust. We used an LLM to draft labels, then had human reviewers verify every entity. Each disagreement helped tighten the ground truth.
Change one thing, measure everything
We isolated variables like prompt design, chunking strategy, output structure, and speaker-label handling. Every change ran against the same benchmark, so regressions showed up immediately.
Every improvement has to earn its place
Higher overall accuracy did not always mean a safer detector. A version that caught more passwords might miss more phone numbers, so each iteration had to prove it was better than the previous checkpoint.
Ship the strongest system, not the strongest model
No single model won everywhere. Different passes were stronger for different entity types, so we combined the best ones into a union pipeline. That gave us better recall than relying on one detector.
🏭 The Pipeline in Production
⚙️ From Experiment to Production
One thing I learned from this project: the best experiment is not always the best production system.
During experimentation, our objective was simple: maximize recall. The strongest setup was a two-pass pipeline. The first pass acted as a general detector across all three sensitivity levels. The second pass revisited the transcript with a recall-focused model, looking for Level 2 and Level 3 entities the first pass was likely to miss. We then merged and deduplicated both outputs.

Figure 2: The experimental two-pass detector optimized for recall before production constraints entered the room.
On the benchmark, it worked. The second pass consistently recovered sensitive entities that would otherwise slip through.
But experiments do not pay cloud bills. Production does.
In production, we could only run Claude Sonnet 4.6 on Databricks. A second LLM pass would nearly double inference cost, increase latency, complicate deployment, and require an additional recall model that the environment could not support.
So the question changed:
❌ which architecture gives the highest recall?
✅ which architecture gives the best trade-off between recall, latency, cost, and operational simplicity?
That led to a simpler answer. We did not ship the two-pass pipeline. Instead, we folded what it taught us back into production: remove Level 1 from PII extraction, keep the detector focused on entities that actually require masking, and preserve business context for downstream analysis.
Then, the experiment did not become production. It became the teacher for production.
Once the production shape was clearer, the next question was not just what the model found, but what the system should do with it.
🎭 Detection is Only Half the Story
Finding a suspicious span of text is not the same as deciding what to do with it.
After the detector returns an entity, the system still has to answer a few less model-shaped, more policy-shaped questions:
Is this actually PII, or just useful business context?
Is this a Money Forward employee name, or a customer name?
Should this value be masked, retained, or skipped?
If it appears ten times in the transcript, should every occurrence receive the same label?
Those decisions should not live inside the detector. They belong to the masking pipeline.

Figure 3: The production masking pipeline turns uncertain detections into consistent masking behavior.
This separation became one of the more important architectural decisions in the project.
🕵️ The detector's job is to be suspicious: find candidate sensitive entities with high recall.
The masking pipeline's job is to be deliberate: apply business rules, access policy, and consistency guarantees before anything is exposed downstream.
That distinction sounds small, but it made the system much easier to evolve. We can improve the detector without rewriting masking rules. We can change masking policy without reprompting the model. And when something goes wrong, we can ask a cleaner question:
Did the model miss the entity, or did the policy layer handle it incorrectly?
In practice, the masking stage loads detections, removes duplicates, identifies Money Forward staff, groups equivalent entities, assigns stable labels, and generates the final masked transcript.
The important part is that the final transcript is not just "LLM output with brackets around it." It is the result of a second layer that turns uncertain detections into consistent, auditable masking behavior.
📏 The Metrics
To better tune and record final results, we needed a way to compare detector versions.
The usual metrics still mattered: precision, recall, and F1. But for PII redaction, the most important lesson was simple:
⚖️ Not all mistakes are equally bad.
False positives remove context. False negatives leak data.
So recall mattered more than precision, especially for Level 2 and Level 3 entities. But precision still mattered. If the model masks too aggressively, the downstream AI receives a transcript that is technically safe but no longer useful.
For evaluation, a detection counted as correct only when it matched the ground-truth entity at the right sensitivity level.
| GT: PII at the correct level | GT: Not PII | |
|---|---|---|
| Model: Detected | ✅ True Positive (TP) — the model found the entity and assigned the correct sensitivity level | ❌ False Positive (FP) — the model flagged something that should not be treated as PII |
| Model: Not detected | ❌ False Negative (FN) — the model missed real PII, or classified it at a lower level than the label | ✅ True Negative (TN) — neither the model nor the label treats it as PII |
The "classified too low" part matters, too. If the label says PASSWORD is Level 3, but the model detects it as Level 2, we still treat that as a risky miss. Worse, if a Level 2 or Level 3 entity is classified as Level 1, it may not be masked at all. For evaluation, we mark these cases as false negatives for the correct sensitivity level.
We tracked the standard metrics:
| Metric | Meaning | Formula |
|---|---|---|
| Precision | Of detected entities, how many were correct? | $P = TP / (TP + FP)$ |
| Recall | Of real PII entities, how many were detected? | $R = TP / (TP + FN)$ |
| F1 | Balance between precision and recall. | $F1 = \frac{2 \times P \times R}{P + R}$ |
Then we added sensitivity-weighted metrics, because:
🔐 Missing a company name and missing a password should not carry the same penalty.
| Metric | Formula |
|---|---|
| Weighted Precision | $WP = 0.10 \times P_{\text{lv1}} + 0.40 \times P_{\text{lv2}} + 0.50 \times P_{\text{lv3}}$ |
| Weighted Recall | $WR = 0.10 \times R_{\text{lv1}} + 0.40 \times R_{\text{lv2}} + 0.50 \times R_{\text{lv3}}$ |
| Weighted F1 | $WF1 = \frac{2 \times WP \times WR}{WP + WR}$ |
After the metrics were in place, the question became simpler: did the detector catch the risky things often enough to be useful?
📊 The Results
After several tuning rounds, the LLM-based detector reached the best overall weighted recall: 0.99 (with human validation). More importantly, it kept misses for Level 2 and Level 3 entities low, avoided obvious business-context false positives, and handled the messy Japanese formats that rule-based systems tend to miss.

Figure 4: Final benchmark results after tuning and human validation.
The strongest signal was recall. For PII redaction, we would rather review a few extra detections than silently miss a password, phone number, or private URL. The final version still produced some false positives, but most of them were acceptable because they removed low-value text rather than exposing sensitive data.

Figure 5: Additional result breakdown showing by level and type score

Figure 6: Entity-level behavior matters
The charts show how well we detect PII, but not why one approach succeeds where another fails — which is what the next section unpacks.
🧪 Comparisons of different approaches
Before moving to the LLM-based detector, we also tested a more traditional approach: regex rules plus Japanese NER (Named Entity Recognition) using Presidio. It was fast and explainable, but the failure modes were exactly the kind of messy Japanese input we expected to see in real customer conversations.
| Case | Regex + Presidio behavior | Why it fails | LLM behavior | Takeaway |
|---|---|---|---|---|
Year-like number: 2024年から始まった新しいプラン | ⚠️ Detects 2024 as ID | The ID regex treats any 4+ digit sequence as an identifier, even when it is clearly a year. | ✅ Ignores it | Regex sees digits. The LLM reads sentence role. |
Money Forward product names: 経費精算と給与システムを導入 | ⚠️ Tags product names as COMPANY | spaCy maps some product-like terms to ORG, without knowing they are internal Money Forward products. | ✅ Ignores them | Domain context matters. Product names are not customer companies. |
Generic card brand mention: JCBやVisaなど主要クレジットカード会社 | ⚠️ Detects JCB and Visa as sensitive values | The regex fires on brand names anywhere, even when the text is only a generic business statement. | ✅ Ignores them | Keyword presence is not the same as PII. |
Full postal address: 〒108-0023 東京都港区芝浦3-1-21 ... 21F | ⚠️ Extracts only 東京都港区 | spaCy catches the geographic entity, but misses postal code, street number, building, and floor. | ✅ Extracts the full address | For masking, partial address detection is not enough. |
Full-width IP address: 192.168.1.100 | ❌ Misses it | The regex expects half-width periods (.), not full-width Japanese periods (.). | ✅ Detects the IP address | Byte-level patterns break on ordinary Japanese typing variants. |
Phone number with の: 090の1234の5678 | ❌ Misses it | The phone regex allows hyphens and spaces, but not the Japanese connector の. | ✅ Detects the phone number | Spoken Japanese formatting does not look like a standard regex format. |
Password with は: 仮パスワードはTempPass2025!です | ❌ Misses it | The password rule expects separators like : or =, but Japanese often uses the topic marker は. | ✅ Detects the password | The dangerous part is not the separator character. It is the disclosure pattern. |
The point is not that regex is useless. Regex is excellent when the surface form is stable: emails, standard phone numbers, UUIDs, and URLs. But customer text is not stable. It contains Japanese grammar, full-width characters, product names, shorthand, dictated phone numbers, and partial addresses.
That is where the LLM helped: not by being magical, but by using context when the surface pattern was ambiguous or slightly broken.
That still does not mean a third-party LLM is the final destination. It just gave us a strong place to start.
🔮 The Future
A third-party LLM is probably not the final answer to this problem.
It gives us a strong baseline today: high recall, reasonable precision, and enough flexibility to handle messy Japanese text. More importantly, it gives us a way to create human-validated training data from production-like examples.
The next step is a smaller, self-hosted PII detection model tuned for Money Forward's domain, policies, and Japanese customer conversations.
🧵 Final words
This project started with a very practical question: can we use production-like customer feedback safely enough to learn from it?
The answer was not a single model, a clever prompt, or a perfect regex. It was a pipeline: define what risk means, measure the detector honestly, separate detection from masking, and keep improving the boring parts until the system becomes trustworthy.
PII redaction is not the most glamorous AI problem. Nobody puts "masked a password correctly" on an architecture diagram and gets applause. But it is one of those problems that decides whether the more exciting systems can exist at all.
If we want VoC data to become useful for product decisions, we first need a safe path for that data to move. This work is one small piece of that path.
As it turned out, cruel life had once again refused to fit neatly into a database schema. Fortunately, neither did our solution.
Thank you for reading!
📚 References
[1] Microsoft. (n.d.). Presidio [Computer software]. GitHub. https://github.com/microsoft/presidio
[2] Microsoft. (n.d.). What is personally identifiable information (PII) detection in Azure AI Language? Microsoft Learn. https://learn.microsoft.com/en-us/azure/ai-services/language-service/personally-identifiable-information/overview
[3] Google. (n.d.). Classification: Accuracy, precision, and recall. Machine Learning Crash Course. Google Developers. https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall



