Lattice
A workspace for your research.
日本年金機構等の公文書ZIPを一括でPDFに変換し、同梱PDFのリネームも自動化するアプリです。
Translation pending
ProductivityTypeScript · JavaScriptGitHub @spot-s-systemOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗公文書ZIPファイル(XML/XSL形式)をPDFに変換するWebアプリケーション
このアプリケーションは、日本の公的機関で使用される電子申請ファイル(ZIP形式)を受け取り、内部のXML/XSLドキュメントをPDFに変換します。
| 通知書番号 | 通知書名 | 出力ファイル名例 |
|---|---|---|
| 7027001 | 厚生年金保険養育期間標準報酬月額特例申出受理通知書 | 奥 絵梨花様_厚生年金保険養育期間標準報酬月額特例申出受理通知書.pdf |
| 7130001 | 健康保険・厚生年金保険被保険者標準報酬決定通知書(算定基礎・B連結) | 令和7年度算定_山田太郎様他1名_健康保険・厚生年金保険被保険者標準報酬決定通知書.pdf |
| 7200001 | 厚生年金保険70歳以上被用者標準報酬月額相当額決定のお知らせ(算定基礎・A個別) | 令和7年度算定_山田太郎様_厚生年金保険70歳以上被用者標準報酬月額相当額決定のお知らせ.pdf |
| henrei | 返戻のお知らせ | 山田太郎様_返戻のお知らせ.pdf |
| kagami | 日本年金機構からのお知らせ | 田中一郎様_日本年金機構からのお知らせ.pdf |
ZIP内の既存PDF(XML/XSLから生成されないファイル)を、フォルダ名から判定して自動的にリネームします。優先順位の高い順に適用されます。
{被保険者名}様_ プレフィックスを付けず、固定の通知書名にします。
| 対象 | フォルダ名パターン | 出力ファイル名 |
|---|---|---|
| 労保 年度更新 | ..._[労保]年度更新_{タイムスタンプ}_公文書_* | 令和{n}年度_労働保険概算・確定保険料申告書.pdf |
| 労保 年度更新(建設) | ..._[労保]年度更新(建設)_{タイムスタンプ}_公文書_* | 令和{n}年度_労働保険概算・確定保険料申告書(建設).pdf |
| 労保 保険関係成立届 | ..._[労保]保険関係成立届_..._公文書_* | 労働保険関係成立届.pdf |
| 労保 名称所在地変更 | ..._[労保]名称所在地変更_..._公文書_* | 労働保険名称所在地変更届.pdf |
| 労保 概算保険料申告(継続) | ..._[労保]概算保険料申告(継続)_..._公文書_* | 労働保険概算保険料申告書.pdf |
| 社保 新規適用 | ..._[社保]新規適用_..._公文書_* | (社会保険)適用通知書.pdf |
_公文書_ を含むフォルダのみ対象(コメントフォルダは除外)公文書フォルダ内の任意PDFを、被保険者名 + 固定通知書名にリネームします。
| 対象 | 出力ファイル名 |
|---|---|
..._[社保]育児休業等申出書_..._公文書_* | {被保険者名}様_健康保険・厚生年金保険育児休業等取得者確認通知書.pdf |
..._[社保]産前産後休業等申出書_..._公文書_* | {被保険者名}様_健康保険・厚生年金保険産前産後休業取得者確認通知書.pdf |
..._[社保]養育期間標準報酬月額特例... | {被保険者名}様_厚生年金保険養育期間標準報酬月額特例申出受理通知書.pdf |
被保険者名はフォルダ名の _[社保]xxx_ の 直前のフィールド から抽出します。これにより以下の両方の構造に対応:
{番号}_{会社名}_{被保険者名}_[社保]xxx_...{番号}_{会社名}_{被保険者番号}_{被保険者名}_[社保]xxx_...数字始まり(ハイフン連番含む)のファイル名の数字部分を被保険者名に置換します。
| 対象 | 元ファイル名例 | 出力ファイル名 |
|---|---|---|
..._[雇保]資格取得_......_[雇保]資格喪失(離職票交付あり)_......_[雇保]育児休業出生後休業給付_......_[雇保]育児時短就業給付_......_[雇保]育児休業出生時休業給付_... | 2501793096_xxx.pdf202602021152166333-0001_xxx.pdf | {被保険者名}様_xxx.pdf |
_[雇保]xxx_ 直前のフィールド」方式(4F/5F両対応)高橋 なつみ様_...)XML/XSLから新規生成するPDFで、XMLから被保険者名や通知書名がうまく取れないケース(DataRoot形式の喪失で <TITLE> が無い等)でも、フォルダ名から補完して以下の名前で生成します:
| フォルダ | 出力ファイル名 |
|---|---|
..._{被保険者名}_[社保]資格取得_... | {被保険者名}様_健康保険・厚生年金保険資格取得確認および標準報酬決定通知書.pdf |
..._{被保険者名}_[社保]資格喪失_... | {被保険者名}様_健康保険・厚生年金保険資格喪失確認通知書.pdf |
..._{被保険者名}_[社保]育児休業等申出書_... | {被保険者名}様_健康保険・厚生年金保険育児休業等取得者確認通知書.pdf |
..._{被保険者名}_[社保]産前産後休業等申出書_... | {被保険者名}様_健康保険・厚生年金保険産前産後休業取得者確認通知書.pdf |
..._{被保険者名}_[社保]養育期間標準報酬月額特例_... | {被保険者名}様_厚生年金保険養育期間標準報酬月額特例申出受理通知書.pdf |
..._[社保]新規適用_... | (社会保険)適用通知書.pdf(被保険者名は付与しない) |
XML側で名前/タイトルが正しく取れている場合はXMLの値を優先します(フォールバックは欠損時のみ)。
入力ZIPのフォルダ名は概ね {番号}_{社名}_{被保険者番号}_{被保険者名}_[xxx]{手続き}_… の形をしています。社名が極端に長い場合(例: Tokyo Artisan Intelligence株式会社)や、フォルダ内の被保険者名・帳票名(通知書名)が長い場合、Windows エクスプローラの ZIP エントリパス長制限(89 文字、後述)にフォルダプレフィックス+ファイル名の合計が収まらなくなります。
このとき、出力ZIPでは フォルダ名の社名フィールドのみを末尾から必要分だけ切り詰めて短縮 します(compressFolderNameForBudget)。番号・被保険者番号・被保険者名・手続きタグ・帳票名(通知書名)は触らず、社名側で長さを吸収します。
0004_Tokyo Artisan Intelligence株式会社_3817608_青木 理沙_[雇保]資格取得_2026・・・/ (67 字)0004_Tokyo Artisan Intel_3817608_青木 理沙_[雇保]資格取得_2026・・・/ねらい: 被保険者名と帳票名は本人特定・書類識別の核なのでフル保持し、社名は前後の番号やタグで識別が付くため末尾を素直に切る方が業務影響が小さい、という判断です。省略記号(…)は付けず、収まる長さで素直に切ります。
社名圧縮しても合計 89 字に収まらない極端なケースのみ、最後の砦として fitEntryNameToShellLimit がファイル名側(被保険者名の末尾)を追加で削ります。
社保の月額変更・賞与の生成PDFは、フォルダ内の日付情報からファイル名にプレフィックスを付与します。元号略号始まりの形式から完全名・ゼロパディング除去形式に整形:
| 手続き | 元の日付(XML抽出) | ファイル名プレフィックス |
|---|---|---|
| 月額変更(標準報酬改定通知書 / 70歳以上被用者月額改定通知) | R08年04月 | 令和8年4月改定_ |
| 賞与(賞与額決定通知書 / 70歳以上被用者賞与) | R08年01月25日 | 令和8年1月25日_ |
過去の本コンバーター出力(R08年01月25日_xxx.pdf のような元号略号始まり日付プレフィックス)は、再投入時に 新フォーマットで再生成されたPDFと重複 するため、otherFiles からスキップします。判定パターン: ^[SHR]\d{1,4}年\d{1,2}月(\d{1,2}日)?_
Puppeteerを使用するため、開発環境でも必要な依存関係をインストールする必要があります。
# 依存関係のインストール
npm install
# Puppeteerブラウザのインストール(初回のみ)
npx puppeteer browsers install chrome
# 開発サーバーの起動
npm run dev
ブラウザで http://localhost:3000 を開いてアプリケーションを確認できます。
本番環境と同じ環境で開発・テストする場合はDockerを使用します。
# Dockerイメージのビルド
docker build -t xml-to-pdf-converter .
# コンテナの起動
docker run -p 3000:3000 xml-to-pdf-converter
ブラウザで http://localhost:3000 を開いてアプリケーションを確認できます。
npm run dev が最も高速(Turbopack使用)/api/test-chromium エンドポイントでChromiumの動作確認が可能.
├── app/
│ ├── api/convert/route.ts # 個別ZIP→PDF変換APIエンドポイント
│ ├── api/convert-bulk/route.ts # 一括変換APIエンドポイント(同期レスポンス)
│ ├── api/convert-bulk-stream/route.ts # リアルタイムログ + ID付きdownloadURL返却(SSE)
│ ├── api/download/[id]/route.ts # 結果ZIPストリーミング配信
│ ├── api/health/route.ts # ヘルスチェック
│ ├── page.tsx # メインUIページ(リアルタイムログ対応)
│ └── layout.tsx # アプリケーションレイアウト
├── lib/
│ ├── zip-to-pdf.ts # ZIP処理とドキュメント構成
│ ├── bulk-zip-processor.ts # 一括処理・ネストZIP対応・PDFリネーム
│ ├── download-store.ts # 一時ダウンロードファイルのID管理(globalThis永続化)
│ ├── xslt-processor.ts # XSLT変換処理
│ ├── xsl-adjuster.ts # XSLスタイルシートA4最適化
│ ├── pdf-generator.ts # Puppeteer PDF生成
│ ├── browser-pool.ts # Puppeteerブラウザインスタンスプール
│ ├── logger.ts # リアルタイムログ管理
│ └── utils.ts # ユーティリティ関数
├── components/
│ ├── file-dropzone.tsx # ファイルアップロードUI
│ └── ui/ # shadcn/ui コンポーネント
├── Dockerfile # 本番環境コンテナ定義
└── render.yaml # Renderデプロイ設定
# プロダクションビルド
npm run build
# ビルドしたアプリの起動
npm start
render.yaml を使用してRenderに自動デプロイされます。
services:
- type: web
name: xml-to-pdf-converter
runtime: docker
plan: free
region: oregon
# イメージビルド
docker build -t xml-to-pdf-converter .
# コンテナ起動
docker run -p 3000:3000 \
-e NODE_ENV=production \
xml-to-pdf-converter
ダウンロードされるZIPファイルには以下が含まれます:
公文書(002)_converted.zip
├── 元のファイル/
│ ├── 202508202132142994.xml
│ ├── kagami.xsl
│ ├── 7130001.xml
│ ├── 7130001.xsl
│ ├── 7200001.xml
│ └── 7200001.xsl
└── 生成されたPDF/
├── 田中一郎様_日本年金機構からのお知らせ.pdf
├── 山田太郎様他1名_健康保険・厚生年金保険被保険者標準報酬決定通知書.pdf
└── 山田太郎様他1名_厚生年金保険70歳以上被用者標準報酬月額相当額決定のお知らせ.pdf
ZIPアップロード → ZIP解凍 → 被保険者情報抽出 → HTML変換 → PDF生成 → ZIP作成
app/api/convert/route.ts).xml, .xsl, .txt// ネストされたZIP構造にも対応
返戻のお知らせ.zip
└── 202508261727120754.zip // ← これも自動展開
└── 202508261727120754/
├── kagami.xml
└── henrei.xml
lib/xml-parser.ts)各通知書XMLから被保険者情報を抽出し、個別のXMLを生成:
// 7130001.xml(複数被保険者)
<N7130001>
<_被保険者>
<被保険者氏名><![CDATA[山田太郎]]></被保険者氏名>
...
</_被保険者>
<_被保険者>
<被保険者氏名><![CDATA[鈴木花子]]></被保険者氏名>
...
</_被保険者>
</N7130001>
↓ 抽出
[
{ name: "山田太郎", xmlContent: "個別XML1" },
{ name: "鈴木花子", xmlContent: "個別XML2" }
]
lib/xslt-processor.ts)各被保険者の個別XMLをHTMLに変換:
XSLTProcessorを使用XML + XSL → XSLTProcessor → HTML
lib/zip-to-pdf.ts)複数被保険者のHTMLを1つに結合:
<!DOCTYPE html>
<html>
<body>
<div class="document-container">山田太郎のHTML</div>
<div class="page-break"></div>
<div class="document-container">鈴木花子のHTML</div>
</body>
</html>
page-break-after: always により、PDF生成時に自動的に改ページされます。
lib/pdf-generator.ts)PuppeteerでHTMLをPDFに変換:
lib/document-names.ts)被保険者名リスト → ファイル名
["山田太郎"]
→ "山田太郎様_健康保険・厚生年金保険被保険者標準報酬決定通知書.pdf"
["山田太郎", "鈴木花子"]
→ "山田太郎様他1名_健康保険・厚生年金保険被保険者標準報酬決定通知書.pdf"
["山田太郎", "鈴木花子", "佐藤次郎"]
→ "山田太郎様他2名_健康保険・厚生年金保険被保険者標準報酬決定通知書.pdf"
waitUntil: "domcontentloaded" で不要な待機時間削除初回: 2.2秒
2回目以降: 0.6秒(86%高速化)
MAX_REQUESTS_PER_BROWSERで設定可能(デフォルト: 50, Render無料: 30)/api/convert-bulk-stream では、Render無料枠(512MB)でのメモリ枯渇を防ぐため以下の最適化を実施:
/api/download/{id} 経由でストリーミング配信JSZip.generateNodeStream({ streamFiles: false }) でディスクへ pipe(後述の互換性の都合で streamFiles: false)ReadStream 参照のみ保持fs.createReadStream でJSZipに渡すglobalThis + Symbol.for で Next.js dev のHMR/Route別コンパイルを跨いでMapを共有50MB規模の結果ZIPで ピーク -150〜200MB のメモリ削減効果。
社内ユーザーの多くは結果ZIPを Windows エクスプローラ(標準の「すべて展開」)で開くため、以下2つの互換性対策を入れている。
7-Zip / PowerShell Expand-Archive / macOS / Unix unzip ではどちらも問題なく開けるが、エクスプローラ単体で同等に動かないとサポート負担が大きいので、生成側で吸収する方針。
実測(Windows 11, JSZip生成ZIP)で folderPrefix + fileName の合計が 90 文字以上のエントリは、エクスプローラで開いたときに「エントリ 0 件」として認識され、「すべて展開」を押しても何も取り出せない。
発覚した経路: 外国籍被保険者(半角カナ氏名)+ 長い会社名フォルダで、{被保険者名}様_{通知書名}.pdf が 60 文字超 + フォルダ名 40 文字超 → 合計 100 文字超 → 結果ZIPがエクスプローラで空に見える。
実装(lib/bulk-zip-processor.ts の fitEntryNameToShellLimit()):
様 より前の部分)を末尾から切り詰める様_{通知書名}.pdf / 様他N名_{通知書名}.pdf の suffix 部分は固定なので一切切らない…)は付けず、収まる文字数で素直に切る様 を含まないファイル(月変統合PDF R7年n月_…、固定名公文書)は、拡張子を残してベース名末尾を切るフォールバック✂️ Shortened for Windows shell: ... → ... を出力streamFiles: true) を避けるJSZip.generateNodeStream({ streamFiles: true }) だと各エントリの CRC/サイズを Local File Header に書かず末尾の Data Descriptor (General Purpose Bit Flag bit 3) として書く形式になる。
ZIP 仕様上は正当だが エクスプローラはこの形式を未サポート で、やはりエントリ 0 件として見えてしまう。
このため streamFiles: false に固定している(CRC/サイズ計算のためエントリ単位でバッファ化が必要になりメモリは増えるが、PDF1枚あたり数MBなので影響は許容範囲)。
ローカル開発環境でPuppeteerが起動しない場合:
# Chromiumを再インストール
npx puppeteer browsers install chrome
Dockerコンテナ内で日本語が表示されない場合は、Dockerfileに以下のフォントが含まれていることを確認:
大きなZIPファイルを処理する場合、Node.jsのメモリ制限を増やす:
NODE_OPTIONS="--max-old-space-size=4096" npm start
MIT