First commit

This commit is contained in:
MangoPig
2026-02-21 21:55:42 +00:00
commit 060629ca94
27 changed files with 2739 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
Recommended .gitignore
# Config with real server details
Sync/config.ini
# Generated at runtime
Sync/rclone/
Sync/.sync_ignore
Sync/logs/
# Python
__pycache__/
*.pyc
# Sample outputs
Tools/Samples/outputs/
# Environment
.env
.env.*
!.env.example
# OS
.DS_Store
Thumbs.db
desktop.ini
+53
View File
@@ -0,0 +1,53 @@
# JingTian-Tracker
Document processing pipeline for JingTian & Gongcheng IP/trademark workflow.
## Pipeline
```
Windows VM Ubuntu VM
(Docker Compose)
BenjaminTeam/
user drops files ──► rclone sync ──► inotifywait detects new files
State check (SQLite)
- new file → process
- changed file → re-process
- unchanged → skip
- tracker.xlsx → skip
Docling (text extraction + OCR)
Ollama / Qwen 3-1.7B (structured extraction)
→ deadline, doc type, assigned person, etc.
Tools service (openpyxl)
- read latest tracker.xlsx
- append new rows / update re-uploaded
- preserve manual edits
rclone sync tracker.xlsx back
tracker.xlsx updated ◄── rclone ◄── done
```
## Stack
| Service | Role | Language |
|-------------|-------------------------------|----------|
| `server` | Orchestrator, watcher, state | Go |
| `docling` | Text extraction, OCR | Python |
| `ollama` | LLM structured extraction | - |
| `tools` | Excel read/write, extensible | Python |
| `sqlite` | State tracking (file, no container) | - |
## Key Rules
- LLM only writes to Excel on **first processing** or **source doc re-upload**
- Mr. Choi can edit any cell freely — edits are preserved
- `_LLM/` directory is excluded from sync and file watching
- Windows watcher uses debounce (15s) and `.last_sync` to avoid feedback loops
## TODO
See `TODO.md`.
+314
View File
@@ -0,0 +1,314 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
JingTian LLM - Windows Sync Setup
.DESCRIPTION
Sets up rclone for automatic file syncing between this Windows machine
and the JingTian Ubuntu server. Run once, then syncing happens automatically.
.NOTES
Run via Setup.bat (double-click) or directly with admin privileges.
#>
param(
[string]$SyncRoot = $PSScriptRoot,
[string]$Password = ""
)
$ErrorActionPreference = "Stop"
# ============================================================================
# Helpers
# ============================================================================
function Write-Step {
param([int]$Number, [string]$Title)
Write-Host ""
Write-Host "[$Number/6] $Title" -ForegroundColor Cyan
Write-Host ("-" * 50)
}
function Read-Config {
param([string]$Path)
$config = @{}
$section = ""
$listItems = @()
$listSection = ""
foreach ($line in Get-Content $Path) {
$line = $line.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { continue }
if ($line -match '^\[(.+)\]$') {
# Save any pending list
if ($listSection -and $listItems.Count -gt 0) {
$config[$listSection] = $listItems
}
$section = $Matches[1]
# Check if this section is a list section (bidirectional)
if ($section -eq "bidirectional") {
$listSection = $section
$listItems = @()
} else {
$listSection = ""
}
continue
}
if ($listSection) {
$listItems += $line
} elseif ($line -match '^(.+?)=(.+)$') {
$key = "$section.$($Matches[1].Trim())"
$config[$key] = $Matches[2].Trim()
}
}
# Save final list section
if ($listSection -and $listItems.Count -gt 0) {
$config[$listSection] = $listItems
}
return $config
}
# ============================================================================
# Main Setup
# ============================================================================
Write-Host ""
Write-Host "========================================" -ForegroundColor Yellow
Write-Host " JingTian LLM - Sync Setup" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Yellow
Write-Host ""
# Determine paths
$configPath = Join-Path $SyncRoot "config.ini"
$rclonePath = Join-Path $SyncRoot "rclone"
$rcloneExe = Join-Path $rclonePath "rclone.exe"
$rcloneConf = Join-Path $rclonePath "rclone.conf"
$logsPath = Join-Path $SyncRoot "logs"
$syncScript = Join-Path $SyncRoot "sync.ps1"
$ignoreFile = Join-Path $SyncRoot ".sync_ignore"
# BenjaminTeam is two levels up from Code/Sync/
$benjaminTeam = (Resolve-Path (Join-Path $SyncRoot "..\..\.." )).Path
Write-Host "Sync root: $SyncRoot"
Write-Host "BenjaminTeam: $benjaminTeam"
Write-Host ""
# Read config
if (-not (Test-Path $configPath)) {
Write-Host "ERROR: config.ini not found at $configPath" -ForegroundColor Red
exit 1
}
$config = Read-Config $configPath
$serverHost = $config["server.host"]
$serverUser = $config["server.user"]
$serverPort = $config["server.port"]
$remotePath = $config["server.remote_path"]
$remoteName = $config["sync.remote_name"]
$interval = $config["sync.interval_minutes"]
Write-Host "Server: ${serverUser}@${serverHost}:${serverPort}" -ForegroundColor Gray
Write-Host "Remote: ${remoteName}:${remotePath}" -ForegroundColor Gray
# --------------------------------------------------------------------------
# Step 1: Install rclone
# --------------------------------------------------------------------------
Write-Step 1 "Installing rclone"
if (Test-Path $rcloneExe) {
$version = & $rcloneExe version 2>&1 | Select-Object -First 1
Write-Host "rclone already installed: $version" -ForegroundColor Green
} else {
Write-Host "Downloading rclone..."
$zipUrl = "https://downloads.rclone.org/rclone-current-windows-amd64.zip"
$zipPath = Join-Path $env:TEMP "rclone.zip"
$extractPath = Join-Path $env:TEMP "rclone-extract"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
Write-Host "Extracting..."
if (Test-Path $extractPath) { Remove-Item $extractPath -Recurse -Force }
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
# rclone extracts into a subfolder like rclone-v1.68.2-windows-amd64/
$rcloneSubDir = Get-ChildItem $extractPath -Directory | Select-Object -First 1
if (-not (Test-Path $rclonePath)) { New-Item -ItemType Directory -Path $rclonePath -Force | Out-Null }
Copy-Item (Join-Path $rcloneSubDir.FullName "rclone.exe") $rcloneExe -Force
# Cleanup
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
Remove-Item $extractPath -Recurse -Force -ErrorAction SilentlyContinue
$version = & $rcloneExe version 2>&1 | Select-Object -First 1
Write-Host "Installed: $version" -ForegroundColor Green
}
# --------------------------------------------------------------------------
# Step 2: Configure rclone (password-based SFTP)
# --------------------------------------------------------------------------
Write-Step 2 "Configuring rclone remote"
if ($Password -ne "") {
$plainPassword = $Password
Write-Host "Using provided password" -ForegroundColor Green
} else {
Write-Host "Enter the password for ${serverUser}@${serverHost}:" -ForegroundColor Yellow
$securePassword = Read-Host -AsSecureString
$plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
)
}
# Obscure the password using rclone
$obscuredPassword = & $rcloneExe obscure $plainPassword 2>&1
$plainPassword = $null # Clear from memory
$rcloneConfContent = @"
[$remoteName]
type = sftp
host = $serverHost
user = $serverUser
port = $serverPort
pass = $obscuredPassword
shell_type = unix
md5sum_command = md5sum
sha1sum_command = sha1sum
"@
# Write without BOM (rclone can't parse BOM)
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($rcloneConf, $rcloneConfContent, $utf8NoBom)
Write-Host "rclone config written to $rcloneConf" -ForegroundColor Green
# --------------------------------------------------------------------------
# Step 3: Test connection
# --------------------------------------------------------------------------
Write-Step 3 "Testing connection to server"
try {
$testOutput = & $rcloneExe lsd "${remoteName}:${remotePath}" --config $rcloneConf 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Connection failed!" -ForegroundColor Red
Write-Host $testOutput
Write-Host ""
Write-Host "Check the server IP, username, and password in config.ini" -ForegroundColor Yellow
exit 1
}
Write-Host "Connection successful! Remote directories:" -ForegroundColor Green
$testOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
} catch {
Write-Host "ERROR: Connection failed: $_" -ForegroundColor Red
exit 1
}
# --------------------------------------------------------------------------
# Step 4: Generate .sync_ignore
# --------------------------------------------------------------------------
Write-Step 4 "Generating sync exclusion rules"
$ignoreContent = @"
# JingTian Sync Ignore Rules
# rclone filter syntax - one rule per line
# See: https://rclone.org/filtering/
# The _LLM directory (scripts, logs, config - managed via git, not sync)
- _LLM/**
# Dotfiles and hidden items
- .*
# Office temp/lock files
- ~`$*
- *.tmp
- ~*.tmp
# OS generated files
- Thumbs.db
- desktop.ini
- .DS_Store
- ehthumbs.db
# Windows shortcuts
- *.lnk
"@
# Write without BOM (rclone can't parse BOM)
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($ignoreFile, $ignoreContent, $utf8NoBom)
Write-Host "Sync ignore rules written to $ignoreFile" -ForegroundColor Green
# --------------------------------------------------------------------------
# Step 5: Create logs directory
# --------------------------------------------------------------------------
Write-Step 5 "Setting up logging"
if (-not (Test-Path $logsPath)) {
New-Item -ItemType Directory -Path $logsPath -Force | Out-Null
}
Write-Host "Logs directory: $logsPath" -ForegroundColor Green
# --------------------------------------------------------------------------
# Step 6: Create scheduled task
# --------------------------------------------------------------------------
Write-Step 6 "Creating scheduled task"
$taskName = "JingTian-Sync"
# Remove existing task if present
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($existing) {
Write-Host "Removing existing task '$taskName'..." -ForegroundColor Yellow
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
# Build the action - run sync.ps1 via PowerShell
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$syncScript`" -SyncRoot `"$SyncRoot`"" `
-WorkingDirectory $SyncRoot
# Trigger: every N minutes, indefinitely
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes ([int]$interval)) `
-RepetitionDuration (New-TimeSpan -Days 9999)
# Settings
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RunOnlyIfNetworkAvailable `
-MultipleInstances IgnoreNew
# Register as SYSTEM so it runs even when user is logged out
Register-ScheduledTask `
-TaskName $taskName `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-RunLevel Highest `
-User "SYSTEM" `
-Description "JingTian LLM - Sync files with Ubuntu server every $interval minutes" `
| Out-Null
Write-Host "Scheduled task '$taskName' created (every $interval minutes)" -ForegroundColor Green
# ============================================================================
# Done
# ============================================================================
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host " Setup Complete!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "Sync will run automatically every $interval minutes." -ForegroundColor White
Write-Host "Logs are saved to: $logsPath" -ForegroundColor White
Write-Host ""
Write-Host "To sync manually, run:" -ForegroundColor Gray
Write-Host " powershell -File `"$syncScript`" -SyncRoot `"$SyncRoot`"" -ForegroundColor Gray
Write-Host ""
+22
View File
@@ -0,0 +1,22 @@
# JingTian Sync Configuration
# Edit these values to match your server setup.
[server]
host=SERVER_IP
user=SERVER_USER
port=22
remote_path=/data/jingtian/BenjaminTeam
[sync]
# How often the sync runs (in minutes)
interval_minutes=5
# rclone remote name (used internally)
remote_name=jingtian
[bidirectional]
# Files that sync BOTH ways (Ubuntu -> Windows AND Windows -> Ubuntu).
# These are files the pipeline generates on Ubuntu that Mr. Choi can also edit.
# One path per line, relative to BenjaminTeam/.
# Lines starting with # are comments.
Admin/Tracker.xlsx
+240
View File
@@ -0,0 +1,240 @@
<#
.SYNOPSIS
JingTian LLM - File Sync Script
.DESCRIPTION
Syncs files between Windows (BenjaminTeam/) and the Ubuntu server.
- PULL first: Ubuntu -> Windows for bidirectional files (pipeline outputs)
- PUSH second: Windows -> Ubuntu (one-way, Windows is source of truth)
Order matters: pull gets pipeline updates, then push sends everything
(including those updates + Mr. Choi's edits) back as source of truth.
Called automatically by Windows Task Scheduler every N minutes.
#>
param(
[string]$SyncRoot = $PSScriptRoot
)
$ErrorActionPreference = "Continue"
# ============================================================================
# Helpers
# ============================================================================
function Read-Config {
param([string]$Path)
$config = @{}
$section = ""
$listItems = @()
$listSection = ""
foreach ($line in Get-Content $Path) {
$line = $line.Trim()
if ($line -eq "" -or $line.StartsWith("#")) { continue }
if ($line -match '^\[(.+)\]$') {
if ($listSection -and $listItems.Count -gt 0) {
$config[$listSection] = $listItems
}
$section = $Matches[1]
if ($section -eq "bidirectional") {
$listSection = $section
$listItems = @()
} else {
$listSection = ""
}
continue
}
if ($listSection) {
$listItems += $line
} elseif ($line -match '^(.+?)=(.+)$') {
$key = "$section.$($Matches[1].Trim())"
$config[$key] = $Matches[2].Trim()
}
}
if ($listSection -and $listItems.Count -gt 0) {
$config[$listSection] = $listItems
}
return $config
}
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$entry = "[$timestamp] [$Level] $Message"
Add-Content -Path $logFile -Value $entry
if ($Level -eq "ERROR") {
Write-Host $entry -ForegroundColor Red
}
}
# ============================================================================
# Setup
# ============================================================================
$configPath = Join-Path $SyncRoot "config.ini"
$rcloneExe = Join-Path $SyncRoot "rclone\rclone.exe"
$rcloneConf = Join-Path $SyncRoot "rclone\rclone.conf"
$logsPath = Join-Path $SyncRoot "logs"
$ignoreFile = Join-Path $SyncRoot ".sync_ignore"
# BenjaminTeam is three levels up from Code/Sync/ (_LLM -> BenjaminTeam)
$benjaminTeam = (Resolve-Path (Join-Path $SyncRoot "..\..\.." )).Path
# Log file: one per day
$logDate = Get-Date -Format "yyyy-MM-dd"
$logFile = Join-Path $logsPath "sync-$logDate.log"
# Ensure logs directory
if (-not (Test-Path $logsPath)) {
New-Item -ItemType Directory -Path $logsPath -Force | Out-Null
}
# Preflight checks
if (-not (Test-Path $configPath)) {
Write-Log "config.ini not found at $configPath" "ERROR"
exit 1
}
if (-not (Test-Path $rcloneExe)) {
Write-Log "rclone.exe not found at $rcloneExe. Run Setup.bat first." "ERROR"
exit 1
}
if (-not (Test-Path $rcloneConf)) {
Write-Log "rclone.conf not found at $rcloneConf. Run Setup.bat first." "ERROR"
exit 1
}
# Read config
$config = Read-Config $configPath
$remoteName = $config["sync.remote_name"]
$remotePath = $config["server.remote_path"]
$bidirectionalFiles = @()
if ($config.ContainsKey("bidirectional")) {
$bidirectionalFiles = $config["bidirectional"]
}
$remoteBase = "${remoteName}:${remotePath}"
Write-Log "=== Sync started ==="
Write-Log "BenjaminTeam: $benjaminTeam"
Write-Log "Remote: $remoteBase"
# ============================================================================
# Phase 1: PULL bidirectional files (Ubuntu -> Windows)
# Runs FIRST so pipeline updates reach Windows before push sends everything back.
# Uses --update: only copies if remote file is newer than local.
# This means Mr. Choi's recent edits are preserved (his file is newer).
# ============================================================================
if ($bidirectionalFiles.Count -gt 0) {
Write-Log "--- Phase 1: PULL bidirectional files (Ubuntu -> Windows) ---"
foreach ($relPath in $bidirectionalFiles) {
# Normalize path separators
$relPath = $relPath.Replace("/", "\")
$remoteSrc = "${remoteBase}/$($relPath.Replace('\', '/'))"
$localDest = Join-Path $benjaminTeam $relPath
$localDir = Split-Path $localDest -Parent
# Ensure local directory exists
if (-not (Test-Path $localDir)) {
New-Item -ItemType Directory -Path $localDir -Force | Out-Null
}
Write-Log "Pulling: $relPath"
try {
$pullOutput = & $rcloneExe copyto $remoteSrc $localDest `
--config $rcloneConf `
--update `
--contimeout 30s `
--timeout 60s `
--retries 3 `
--log-level NOTICE 2>&1
$pullExitCode = $LASTEXITCODE
if ($pullExitCode -eq 0) {
Write-Log " Pulled OK: $relPath"
} else {
# Exit code 3 = directory not found / file doesn't exist yet (OK)
if ($pullExitCode -eq 3) {
Write-Log " Not found on server (yet): $relPath"
} else {
Write-Log " Pull failed (exit code: $pullExitCode): $relPath" "ERROR"
foreach ($line in $pullOutput) {
Write-Log " $line" "ERROR"
}
}
}
} catch {
Write-Log " Pull exception for ${relPath}: $_" "ERROR"
}
}
} else {
Write-Log "No bidirectional files configured, skipping pull phase"
}
# ============================================================================
# Phase 2: PUSH (Windows -> Ubuntu)
# Windows is source of truth. rclone sync mirrors local to remote.
# Bidirectional files are EXCLUDED from push — they are managed by the
# pipeline on Ubuntu and pulled down in Phase 1.
# ============================================================================
Write-Log "--- Phase 2: PUSH (Windows -> Ubuntu) ---"
$pushArgs = @(
"sync"
$benjaminTeam
$remoteBase
"--config", $rcloneConf
"--transfers", "4"
"--checkers", "8"
"--contimeout", "30s"
"--timeout", "120s"
"--retries", "3"
"--log-level", "NOTICE"
)
# Add filter file if it exists
if (Test-Path $ignoreFile) {
$pushArgs += "--filter-from"
$pushArgs += $ignoreFile
}
# Exclude bidirectional files from push (pipeline manages these on Ubuntu)
foreach ($relPath in $bidirectionalFiles) {
$excludePath = $relPath.Replace("\", "/")
$pushArgs += "--exclude"
$pushArgs += $excludePath
}
$pushStart = Get-Date
Write-Log "Running: rclone sync (push)"
try {
$pushOutput = & $rcloneExe @pushArgs 2>&1
$pushExitCode = $LASTEXITCODE
$pushDuration = ((Get-Date) - $pushStart).TotalSeconds
if ($pushExitCode -eq 0) {
Write-Log "Push completed in ${pushDuration}s"
} else {
Write-Log "Push failed (exit code: $pushExitCode) in ${pushDuration}s" "ERROR"
foreach ($line in $pushOutput) {
Write-Log " $line" "ERROR"
}
}
} catch {
Write-Log "Push exception: $_" "ERROR"
}
# ============================================================================
# Done
# ============================================================================
Write-Log "=== Sync finished ==="
+52
View File
@@ -0,0 +1,52 @@
# TODO
## Phase 0: Infrastructure (DONE)
- [x] Azure VMs created and configured (jt-ub, jt-win)
- [x] Ubuntu VM provisioned (Docker, Go, Python, rclone, zsh, dotfiles)
- [x] Directory structures on both VMs (William's layout)
- [x] Sample document generator (7 generators, Azure Claude content pools, CJK fonts)
- [x] Project restructure: Tracker/Setup.bat + Tracker/Code/{Sync,Tools,Tracker}
- [x] Windows rclone sync setup (Setup.bat, Win-Setup.ps1, sync.ps1, config.ini)
- rclone auto-download + self-contained install in _LLM/Code/Sync/rclone/
- Password-based SFTP auth (demo), config.ini for server details
- .sync_ignore generated on setup (excludes_LLM/, dotfiles, temp/lock files)
- 5-minute scheduled task (JingTian-Sync)
- Pull-first-then-push: bidirectional files pulled with --update, excluded from push
- Bidirectional file list in config.ini (e.g. Admin/Tracker.xlsx)
- Daily log rotation in _LLM/Code/Sync/logs/
- [x] Ubuntu VM SSH password auth enabled (Azure cloud-init was blocking it)
- [x] Both sync directions tested and working
- [x] Old JingTian-Rclone repo archived on Gitea
## Phase 1: Foundation
- [ ] Init Go module
- [ ] SQLite schema + migrations (documents, extractions, processing_log)
- [ ] Config loading (YAML)
- [ ] Docker Compose (Docling, Ollama, Python Tools, Go server)
## Phase 2: Pipeline Core
- [ ] Docling HTTP client
- [ ] Ollama HTTP client + structured extraction prompt
- [ ] Tools service (FastAPI + openpyxl Excel endpoints)
- [ ] File watcher (inotify on Ubuntu, 2-min stability check via SHA256 hash)
- [ ] State tracker integration (new/changed/skip logic)
- [ ] Pipeline orchestrator (read latest Tracker before writing, merge not overwrite)
## Phase 3: Integration + Testing
- [ ] End-to-end test (drop file on Windows → syncs to Ubuntu → pipeline → Tracker updates → syncs back)
- [ ] Smoke test with real PDFs + generated samples
- [ ] README documentation
## Future
- [ ] Key-based SFTP auth (replace password for production)
- [ ] Windows Go service (replace PowerShell scheduled task)
- [ ] File hash (SHA256) change detection on Windows side
- [ ] WhatsApp deadline notifications
- [ ] Processing retry logic
- [ ] Multi-client support
- [ ] Smarter bidirectional merge (cell-level, not file-level)
+94
View File
@@ -0,0 +1,94 @@
{
"clients": [
{
"name": "Mark Up Limited",
"chinese_name": "標記有限公司",
"address": "18 Dai Hei Street, Tai Po Industrial Estate, Tai Po, New Territories, Hong Kong",
"contact_person": "Mr. David Wong",
"email": "david.wong@markuplimited.com.hk",
"type": "Incorporated",
"place": "Hong Kong"
},
{
"name": "Vita Green Health Products Company Limited",
"chinese_name": "維特健靈健康產品有限公司",
"address": "Unit 1-10, 12/F, Block A, Hoi Luen Industrial Centre, 55 Hoi Yuen Road, Kwun Tong, Kowloon, Hong Kong",
"contact_person": "Ms. Emily Chan",
"email": "emily.chan@vitagreen.com",
"type": "Incorporated",
"place": "Hong Kong"
},
{
"name": "Monee Payment Holding Private Limited",
"chinese_name": null,
"address": "8 Marina Boulevard, #05-02, Marina Bay Financial Centre, Singapore 018981",
"contact_person": "Mr. Jason Tan",
"email": "jason.tan@moneepay.com",
"type": "Incorporated",
"place": "Singapore"
},
{
"name": "Guru Denim LLC",
"chinese_name": null,
"address": "1888 Century Park East, Suite 900, Los Angeles, CA 90067, USA",
"contact_person": "Ms. Rachel Kim",
"email": "rachel.kim@truereligion.com",
"type": "LLC",
"place": "United States"
},
{
"name": "Frasers Property Limited",
"chinese_name": "輝盛國際有限公司",
"address": "438 Alexandra Road, #21-00 Alexandra Point, Singapore 119958",
"contact_person": "Mr. Andrew Lim",
"email": "andrew.lim@frasersproperty.com",
"type": "Incorporated",
"place": "Singapore"
},
{
"name": "Ram Golf Limited",
"chinese_name": null,
"address": "Unit 2501, 25/F, Tower 1, Lippo Centre, 89 Queensway, Admiralty, Hong Kong",
"contact_person": "Mr. Peter Cheung",
"email": "peter.cheung@ramgolf.com.hk",
"type": "Incorporated",
"place": "Hong Kong"
},
{
"name": "Lau Sze Wai Natalie",
"chinese_name": "劉思慧",
"address": "Flat A, 15/F, Block 3, City Garden, North Point, Hong Kong",
"contact_person": "Ms. Natalie Lau",
"email": "natalie.lau@chickeneggboy.com",
"type": "Individual",
"place": "Hong Kong"
},
{
"name": "Dermavon Holdings Limited",
"chinese_name": "德鎂控股有限公司",
"address": "Room 1205, 12/F, Nan Fung Tower, 173 Des Voeux Road Central, Hong Kong",
"contact_person": "Dr. Kevin Ho",
"email": "kevin.ho@dermavon.com",
"type": "Incorporated",
"place": "Hong Kong"
},
{
"name": "BakeMark USA LLC",
"chinese_name": null,
"address": "7351 Crider Avenue, Pico Rivera, CA 90660, USA",
"contact_person": "Mr. Thomas Lee",
"email": "thomas.lee@bakemark.com",
"type": "LLC",
"place": "United States"
},
{
"name": "Power Beauty Co.",
"chinese_name": null,
"address": "590 Madison Avenue, 21st Floor, New York, NY 10022, USA",
"contact_person": "Ms. Katherine Chen",
"email": "katherine.chen@meritbeauty.com",
"type": "Incorporated",
"place": "United States"
}
]
}
+220
View File
@@ -0,0 +1,220 @@
{
"letter_to_client_bodies": [
{
"topic": "opposition_response_needed",
"body": "Dear {contact_person},\n\nWe write on behalf of our client {client_name} in relation to Trade Mark Application No. {tm_number} for the mark {tm_text}. We wish to bring to your urgent attention that a Notice of Opposition has been filed against the above-mentioned application by a third party before the Trade Marks Registry of Hong Kong. Pursuant to the Trade Marks Ordinance (Cap. 559) and the applicable Registry practice, you are required to file a Counter-Statement in response to the said opposition within the prescribed period.\n\nThe deadline for filing your Counter-Statement is {deadline}. Failure to respond within this period may result in the application being deemed abandoned and the opposition proceeding by default, which would be highly prejudicial to your interests. We therefore strongly urge you to revert to us as soon as possible with your instructions so that we may prepare and file the necessary Counter-Statement on your behalf in a timely manner.\n\n请您注意,上述商标申请目前面临第三方异议,我们需要您尽快提供相关指示及支持文件,以便我们在截止日期 {deadline} 前向香港商标注册处提交答辩陈述书。如有任何疑问,敬请随时与本所联系。\n\nWe look forward to receiving your prompt instructions and remain at your disposal should you require any further information or clarification regarding the above matter."
},
{
"topic": "examination_report_received",
"body": "Dear {contact_person},\n\nWe are writing to you on behalf of {client_name} with respect to Trade Mark Application No. {tm_number} for the mark {tm_text}. We are pleased to inform you that we have recently received an Examination Report issued by the Trade Marks Registry of Hong Kong in connection with the above application. The Examiner has raised certain objections and requisitions which require your attention and a formal written response.\n\nIn summary, the Examiner has objected to the application on the grounds that the mark may be considered descriptive of the goods and/or services applied for, and has further cited certain earlier registered marks which are alleged to be confusingly similar to {tm_text}. We are of the view that these objections are capable of being overcome with appropriate legal arguments and, where necessary, supporting evidence of use and distinctiveness acquired through use.\n\nIn order to prepare a comprehensive and persuasive response to the Examination Report, we kindly request that you provide us with any evidence of the use of the mark {tm_text} in Hong Kong and/or internationally, including but not limited to sales figures, marketing materials, and invoices. The deadline for submitting our response to the Registry is {deadline}, and we would appreciate receiving your instructions and supporting materials no later than two weeks prior to that date so as to allow sufficient time for preparation.\n\nPlease do not hesitate to contact us should you have any questions regarding the Examiner's objections or the response strategy we propose to adopt."
},
{
"topic": "renewal_reminder",
"body": "Dear {contact_person},\n\nWe act as trade mark agents for {client_name} and write to draw your attention to the upcoming renewal of Trade Mark Registration No. {tm_number} for the mark {tm_text}. As you may be aware, trade mark registrations in Hong Kong are granted for an initial period of ten years from the date of filing and are thereafter renewable for successive periods of ten years upon payment of the prescribed renewal fees.\n\n本所谨提醒贵方,上述商标注册的续展截止日期为 {deadline}。根据香港《商标条例》(第559章)的相关规定,如未能在届满日期或其后六个月的宽限期内办理续展手续,该注册将被注销,届时贵方将失去对该商标的专有权利。\n\nWe strongly recommend that renewal instructions be provided to us well in advance of the deadline of {deadline} to ensure that we have sufficient time to prepare and file the renewal application and to avoid any risk of the registration lapsing. Please note that a late renewal may be accepted within a six-month grace period following the expiry date, subject to payment of an additional surcharge, but we would advise against reliance upon this grace period as a matter of best practice.\n\nKindly confirm your instructions to proceed with the renewal of Trade Mark Registration No. {tm_number} at your earliest convenience. We shall be happy to provide you with a detailed costs estimate upon request."
},
{
"topic": "registration_confirmed",
"body": "Dear {contact_person},\n\nWe are delighted to write to you on behalf of {client_name} to inform you that Trade Mark Application No. {tm_number} for the mark {tm_text} has now proceeded to registration. The Trade Marks Registry of Hong Kong has issued the Certificate of Registration confirming that the mark has been duly entered on the Register of Trade Marks. We enclose herewith a copy of the Certificate of Registration for your records.\n\n我们很高兴地通知贵方,商标 {tm_text}(注册编号:{tm_number})现已正式获得注册,贵方自注册日期起享有在香港对该商标的专有使用权。此商标注册的有效期为十年,续展截止日期为 {deadline}。本所将适时提醒贵方办理续展手续。\n\nThe registration grants {client_name} the exclusive right to use the mark {tm_text} in Hong Kong in connection with the goods and/or services covered by the registration. We would recommend that you affix the ® symbol to the mark in connection with the registered goods and/or services, as this serves to put third parties on notice of your registered rights and may assist in any future enforcement proceedings.\n\nWe congratulate you on this successful outcome and thank you for entrusting this matter to Jingtian & Gongcheng. Should you require any further assistance with respect to the protection or enforcement of your trade mark rights in Hong Kong or other jurisdictions, please do not hesitate to contact us."
},
{
"topic": "filing_update",
"body": "Dear {contact_person},\n\nWe write to provide {client_name} with an update on the status of Trade Mark Application No. {tm_number} for the mark {tm_text}, which was filed with the Trade Marks Registry of Hong Kong on your behalf. We are pleased to confirm that the application has been duly accepted for filing and is currently undergoing substantive examination by the Registry.\n\nUpon completion of the examination process, assuming no objections are raised by the Examiner, the application will be published in the Hong Kong Intellectual Property Journal for a period of three months to allow third parties an opportunity to file an opposition against the registration of the mark. Should no opposition be filed within this period, or should any opposition be successfully overcome, the Registry will proceed to grant registration. We anticipate that this process will be completed and a decision rendered by approximately {deadline}, though we wish to note that such timelines are subject to the Registry's processing capacity and may be subject to change.\n\nWe will continue to monitor the progress of the application and will revert to you immediately upon receipt of any further communication from the Registry. In the meantime, should you have any queries regarding the application or wish to discuss any related matters, please feel free to contact the undersigned."
},
{
"topic": "amendment_required",
"body": "Dear {contact_person},\n\nWe refer to Trade Mark Application No. {tm_number} for the mark {tm_text} filed on behalf of {client_name} and write to advise you that the Trade Marks Registry of Hong Kong has issued a requisition requesting certain amendments to the application. Specifically, the Registry has required that the specification of goods and/or services be amended to more precisely define the scope of the protection sought, as certain of the current descriptions are considered by the Examiner to be overly broad or ambiguous.\n\n就上述商标申请,注册处认为现有商品及/或服务说明未能清晰界定所申请保护的范围,并要求申请人于 {deadline} 前提交经修订的说明。本所已就此问题进行研究,并将向贵方提出具体修改建议,以确保相关修订既能满足注册处的要求,又能最大限度地保护贵方的商业利益。\n\nWe have carefully reviewed the Registry's requisition and have prepared proposed amendments to the specification which we believe will satisfy the Examiner's requirements whilst preserving the broadest possible scope of protection for {client_name}. We kindly request that you review our proposed amendments, a copy of which is enclosed herewith, and revert to us with your approval or any comments at the earliest opportunity, and in any event no later than {deadline}.\n\nWe wish to emphasise that failure to respond to the Registry's requisition within the stipulated time may result in the application being treated as withdrawn. We therefore urge you to treat this matter with the utmost urgency and to provide your instructions as soon as possible."
},
{
"topic": "deadline_approaching",
"body": "Dear {contact_person},\n\nWe write to you on behalf of {client_name} in connection with Trade Mark Application No. {tm_number} for the mark {tm_text} and wish to draw your urgent attention to the fact that an important procedural deadline in the above matter is fast approaching. As previously advised, the deadline for the filing of certain required documents and/or taking of a specific procedural step before the Trade Marks Registry of Hong Kong is {deadline}.\n\n本所此前已就上述截止日期向贵方发出提示,然而截至本函发出之日,本所尚未收到贵方就此事项所作出的明确指示。鉴于距截止日期 {deadline} 所余时间有限,本所敦请贵方立即与本所联系,以便及时采取相应行动,避免因逾期而对贵方的商标权益造成不可挽回的损害。\n\nWe must stress in the strongest possible terms that this deadline is non-extendable and that failure to act before {deadline} will result in serious and potentially irreversible consequences for your trade mark application, including but not limited to the deemed abandonment of the application. We therefore respectfully request that you provide us with your instructions by return so that we may take all necessary steps to safeguard your interests before the Registry.\n\nShould you have any difficulties in providing instructions within the required timeframe or require any further information, please contact Benjamin Choi of this office immediately by telephone or email so that we may discuss the matter and explore any available options."
},
{
"topic": "evidence_submission_required",
"body": "Dear {contact_person},\n\nWe act for {client_name} in the opposition proceedings concerning Trade Mark Application No. {tm_number} for the mark {tm_text} currently pending before the Trade Marks Registry of Hong Kong. We write to advise you that, pursuant to the directions issued by the Registrar, the deadline for the filing of evidence in these proceedings is {deadline}. As the applicant in these proceedings, it falls upon {client_name} to file evidence in support of the application within this period.\n\nIn order to mount an effective defence of the application and to demonstrate to the Registry that the mark {tm_text} is registrable and has acquired the necessary distinctiveness through use, we will require you to provide us with the following categories of evidence as soon as possible: (1) records of sales and turnover in Hong Kong and/or internationally attributable to goods and/or services bearing the mark {tm_text}; (2) samples of advertising and promotional materials featuring the mark; (3) evidence of the duration and geographical extent of use of the mark; and (4) any relevant survey evidence, awards, or media coverage which may assist in establishing the reputation and recognition of the mark among the relevant public.\n\n上述证据材料对于本案的成功至关重要。请贵方于 {deadline} 前至少三周内将相关材料提供予本所,以便本所有充裕时间整理、审阅并以证据陈述书的形式向注册处提交。如贵方在收集上述证据材料方面遇到任何困难,敬请尽早告知,以便本所协助寻求解决方案。\n\nWe look forward to receiving the requested materials promptly and will keep you closely informed of all developments in the proceedings. Please do not hesitate to contact us should you require any further guidance on the nature or format of the evidence to be submitted."
},
{
"topic": "hearing_notice",
"body": "Dear {contact_person},\n\nWe write to you on behalf of {client_name} with reference to the opposition proceedings relating to Trade Mark Application No. {tm_number} for the mark {tm_text} before the Trade Marks Registry of Hong Kong. We are writing to inform you that the Registrar has fixed a hearing in the above matter, which is scheduled to take place on {deadline} at the Trade Marks Registry. The purpose of this hearing is for both parties to present oral submissions in support of their respective positions following the completion of the evidence rounds.\n\n就上述聆讯,本所将代表贵方出席并向注册官陈述相关法律论据及事实依据,以支持商标申请编号 {tm_number} 的注册。为确保本所能够在聆讯中充分维护贵方的权益,敬请贵方就以下事项于 {deadline} 前至少四周向本所提供确认:(1) 贵方是否授权本所代表贵方出席聆讯;(2) 是否有任何新的商业信息或其他相关事实需要提请本所注意;(3) 贵方是否有意亲自出席聆讯或委派代表出席。\n\nWe wish to advise that the outcome of the hearing will be determinative of the fate of Trade Mark Application No. {tm_number}, and we are accordingly devoting considerable resources to ensuring that the strongest possible case is presented on behalf of {client_name}. We will be in contact with you in due course to discuss the key arguments to be advanced at the hearing and to seek your input on certain factual matters relevant to the proceedings.\n\nIn the meantime, should you have any questions about the hearing procedure or wish to discuss the current status of the case, please do not hesitate to contact Benjamin Choi of this office at your earliest convenience."
},
{
"topic": "costs_estimate",
"body": "Dear {contact_person},\n\nThank you for your recent instructions to Jingtian & Gongcheng in relation to the proposed trade mark application for {tm_text} on behalf of {client_name}. As requested, we are pleased to provide you with a costs estimate in respect of the professional fees and official fees anticipated to be incurred in connection with the filing and prosecution of an application for registration of the mark {tm_text} before the Trade Marks Registry of Hong Kong under Application No. {tm_number}.\n\nOur estimated costs for the preparation and filing of the trade mark application, including the conduct of a preliminary clearance search, preparation of the application, and attendance to filing formalities, are set out in the schedule enclosed herewith. The official filing fees payable to the Trade Marks Registry are fixed by the Registry and are non-negotiable. Please note that the estimate provided is based on the assumption that the application proceeds in a straightforward manner and does not take into account additional costs that may be incurred in the event that the Registry raises objections, a third party files an opposition, or other complications arise during the prosecution of the application.\n\n如注册处就申请提出审查意见或第三方就申请提出异议,则处理上述事宜所需的额外费用将另行估算并提前知会贵方。本所致力于为贵方提供透明、合理的收费安排,并将在整个申请过程中就任何可能产生额外费用的情况及时向贵方汇报。请注意,就本次申请而言,办理相关手续的截止日期为 {deadline},请贵方尽早确认指示及费用安排,以确保申请能够及时提交。\n\nKindly review the enclosed schedule of costs and revert to us with your approval to proceed at your earliest convenience. We remain at your disposal should you have any queries regarding the estimate or wish to discuss the scope of our proposed services in greater detail."
}
],
"letter_from_client_bodies": [
{
"topic": "proceed_with_filing",
"body": "Dear Benjamin,\n\nThank you for sending over the trademark search report and your recommendations. We have reviewed everything internally and are happy to proceed with the filing of {tm_text} in the classes discussed.\n\nPlease go ahead and file the application at your earliest convenience. We understand that the sooner we file, the better our priority date, so please treat this as urgent. We are comfortable with the estimated official fees and your professional charges as outlined in your fee estimate dated last week.\n\nCould you please confirm once the application has been lodged and provide us with the application number as soon as it is available? Also, please keep us posted on any official correspondence from the Trade Marks Registry.\n\nPlease note we need the filing completed before {deadline} to align with our planned product launch.\n\nThanks again for your help on this.\n\nBest regards,\n{client_name}\n{contact_person}"
},
{
"topic": "approve_response",
"body": "Dear Mr. Choi,\n\nThank you for preparing the draft response to the examination report issued against our trademark application {tm_number} for {tm_text}. We have reviewed the arguments carefully with our marketing and legal teams and are pleased to confirm that we approve the response as drafted.\n\nPlease proceed to file the response with the Trade Marks Registry. We note from your advice that the deadline for submitting the response is {deadline}, so please ensure it is filed well in advance of that date.\n\nIf the Registry raises any further objections or requests additional information following your submission, please do come back to us right away. We are keen to resolve this matter as smoothly as possible.\n\nThank you for your thorough work on this.\n\nKind regards,\n{client_name}\n{contact_person}"
},
{
"topic": "query_about_status",
"body": "Dear Benjamin,\n\nHope you are well. I am writing to check on the current status of our trademark application {tm_number} for {tm_text}. We filed this some months ago and have not heard anything recently, so we just wanted to make sure everything is on track.\n\nIn particular, we would like to know:\n1. Has the application been examined by the Trade Marks Registry yet?\n2. Are there any outstanding objections or actions required on our part?\n3. What is the estimated timeline to registration at this stage?\n\nWe have an internal board meeting coming up where we need to report on the status of our IP portfolio, so a quick update would be very much appreciated.\n\nThanks in advance and look forward to hearing from you.\n\nBest,\n{client_name}\n{contact_person}"
},
{
"topic": "provide_evidence",
"body": "Dear Mr. Choi,\n\nFurther to your letter requesting evidence of use in support of our trademark application {tm_number} for {tm_text}, please find attached the following materials which we hope will satisfy the Registry's requirements:\n\n1. Copies of invoices addressed to Hong Kong customers dated over the past three years;\n2. Screenshots of our Hong Kong website showing use of the mark;\n3. Photographs of product packaging bearing the trademark as sold in Hong Kong retail outlets;\n4. Copies of advertisements placed in local publications.\n\nWe trust that the above evidence is sufficient to demonstrate genuine use of the mark. Please let us know if you need anything further or if any of the materials need to be certified or translated.\n\nWe understand the deadline for filing the evidence is {deadline} and we hope the above materials give you enough time to prepare the necessary submission. Please do not hesitate to call if you have questions.\n\nKind regards,\n{client_name}\n{contact_person}"
},
{
"topic": "confirm_renewal",
"body": "Dear Benjamin,\n\nThank you for your renewal reminder regarding our trademark registration {tm_number} for {tm_text}. We confirm that we wish to renew this registration for a further ten-year term.\n\nPlease go ahead and attend to the renewal on our behalf. We are happy for you to proceed on the basis of the fee estimate set out in your reminder letter. Please note the renewal deadline is {deadline} and we want to make sure this is handled well in advance to avoid any late fees or complications.\n\nFor our records, could you please confirm the renewed registration period once the renewal has been processed? Also, please let us know if there are any changes to the registered details that we should be aware of or update at this stage.\n\nThank you as always for keeping track of these important dates for us.\n\nWarm regards,\n{client_name}\n{contact_person}"
},
{
"topic": "change_instructions",
"body": "Dear Mr. Choi,\n\nI am writing to update you on some changes to our filing instructions in relation to trademark application {tm_number} for {tm_text}.\n\nFollowing a recent restructuring of our group, the trademark is to be held by our newly incorporated Hong Kong entity rather than the parent company as originally instructed. The new applicant details are as follows:\n\nNew Applicant Name: {client_name}\nAddress: [to be confirmed separately]\n\nIf the application has already been filed in the original entity's name, please advise on the process and cost involved in recording an assignment or correction of the applicant details. If it has not yet been filed, please update the application accordingly before submission.\n\nWe apologise for any inconvenience this change may cause. Please let us know what further information you require from us and whether there is any impact on the filing timeline or the deadline of {deadline}.\n\nMany thanks,\n{client_name}\n{contact_person}"
},
{
"topic": "budget_concerns",
"body": "Dear Benjamin,\n\nThank you for your recent advice regarding the opposition proceedings filed against our trademark application {tm_number} for {tm_text}. We have reviewed the situation internally and while we are keen to defend our application, we do have some concerns about the projected costs.\n\nWe would be grateful if you could provide a more detailed cost estimate broken down by stage, so that we can present this to our finance team for approval. Specifically, we would like to understand the likely costs of:\n1. Filing a counter-statement;\n2. Discovery and evidence rounds;\n3. Attending the hearing, if it gets that far.\n\nWe want to be realistic about our budget and may need to reassess our strategy at each stage. Is there a more cost-effective approach you would recommend, such as attempting a settlement with the opponent?\n\nWe note the deadline to file our counter-statement is {deadline} so we will need to make a decision fairly quickly. Please advise at your earliest convenience.\n\nThanks,\n{client_name}\n{contact_person}"
},
{
"topic": "urgent_request",
"body": "Dear Benjamin,\n\nI am sorry to contact you on such short notice but we have an urgent matter that requires your immediate attention.\n\nWe have just discovered that a third party has recently filed a trademark application for a mark that is very similar to our registered trademark {tm_number} for {tm_text}. We only became aware of this today and we are very concerned about the potential impact on our brand.\n\nCould you please urgently review the third party application and advise us on whether we should file an opposition? We understand that the deadline to file an opposition is {deadline}, which does not leave us much time. Please let us know what information you need from us in order to assess the situation as quickly as possible.\n\nWe are available for a call at any time today or tomorrow morning to discuss. Please do not hesitate to reach out directly on my mobile.\n\nThank you very much for your urgent assistance.\n\nBest regards,\n{client_name}\n{contact_person}"
},
{
"topic": "new_trademark_idea",
"body": "Dear Benjamin,\n\nHope you are doing well. I wanted to reach out about a new brand name we are considering for an upcoming product line we plan to launch in Hong Kong and across the Asia-Pacific region.\n\nThe new trademark we have in mind is {tm_text} and we are looking to protect it in {class}. Before we commit to this name from a marketing perspective, we would very much like your advice on the following:\n\n1. Please conduct a clearance search to check whether there are any conflicting prior registrations or applications in Hong Kong;\n2. Please also advise on the registrability of the mark in general, including whether it is distinctive enough for registration;\n3. If the mark looks clear, could you provide a fee estimate for filing in Hong Kong and, if possible, a regional strategy across key Asian markets?\n\nWe are targeting a product launch by {deadline} so it would be ideal to have the search results and your initial advice within the next two weeks if at all possible.\n\nLooking forward to working with you on this new project.\n\nKind regards,\n{client_name}\n{contact_person}"
},
{
"topic": "withdrawal_request",
"body": "Dear Mr. Choi,\n\nI am writing to instruct you to withdraw our trademark application {tm_number} for {tm_text}.\n\nAfter careful internal deliberation, we have decided not to proceed with this application. This decision has been driven by a change in our business strategy and a decision to rebrand this product line under a different name. We appreciate all the work you have done on this matter to date.\n\nCould you please confirm the procedure for withdrawing the application and let us know if there are any official fees or formalities involved? We would like the withdrawal to be processed before {deadline} if possible to avoid incurring any further official costs in respect of this application.\n\nPlease also confirm by return that you have received these instructions and that no further action will be taken on the application pending the withdrawal. If there is anything you need from us to complete the withdrawal process, please do let us know.\n\nThank you for your understanding and continued support.\n\nYours sincerely,\n{client_name}\n{contact_person}"
}
],
"email_from_client_bodies": [
{
"topic": "quick_follow_up",
"body": "Hi Benjamin,\n\nJust following up on our trademark application for {tm_text}. Has there been any update from the Registry since we last spoke? We're keen to keep things moving.\n\nThanks for your help as always."
},
{
"topic": "status_check",
"body": "Dear Benjamin,\n\nI wanted to check in on the status of {tm_number}. Our board has been asking for an update and I'd like to give them something concrete by {deadline}.\n\nPlease let me know if you need anything from our side."
},
{
"topic": "forwarding_document",
"body": "Hi Benjamin,\n\nPlease find attached the latest version of the evidence of use package for {tm_text}. Our marketing team compiled everything this morning.\n\nKindly confirm receipt and let us know if anything is missing before {deadline}. Thanks!"
},
{
"topic": "asking_about_costs",
"body": "Hi Mr. Choi,\n\nWe're considering filing additional trademark applications for a few new product lines and wanted to get a rough sense of the costs involved — official fees, your firm's fees, etc.\n\nNo rush on this one, just want to budget accordingly for next quarter."
},
{
"topic": "confirming_meeting",
"body": "Dear Benjamin,\n\nJust writing to confirm our meeting this Thursday at 3pm regarding {tm_number}. {contact_person} from our legal team will be joining as well.\n\nLooking forward to it — see you then."
},
{
"topic": "deadline_reminder",
"body": "Hi Benjamin,\n\nI just wanted to flag that the response deadline for {tm_number} is coming up on {deadline}. I know you're on top of it, but wanted to make sure it's on your radar given the importance of this mark to us.\n\nPlease let me know if you need any additional information from {client_name} to prepare the response."
},
{
"topic": "new_matter_inquiry",
"body": "Hi Mr. Choi,\n\nWe're looking to register a new trademark, {tm_text}, in Hong Kong and potentially a few other Asia-Pacific jurisdictions. Could you advise on the best approach and whether a clearance search would be recommended first?\n\nHappy to jump on a quick call if that's easier."
},
{
"topic": "sending_signed_docs",
"body": "Dear Benjamin,\n\nPlease find the signed authorization forms attached for {tm_text}. {contact_person} has signed on behalf of {client_name}.\n\nWe understand you need these by {deadline} to proceed with the filing — please confirm once received."
},
{
"topic": "travel_affecting_timeline",
"body": "Hi Benjamin,\n\nI wanted to give you a heads up that I'll be travelling internationally from next Monday through to {deadline} and may have limited availability. If anything urgent comes up regarding {tm_number}, please copy {contact_person} who can assist in my absence.\n\nApologies for any inconvenience this may cause."
},
{
"topic": "board_meeting_deadline",
"body": "Hi Benjamin,\n\nWe have a board meeting scheduled for {deadline} and the directors would like a full status report on all pending trademark matters, including {tm_number} and {tm_text}.\n\nWould it be possible to get a short written summary from you by end of day the Friday before? We'd really appreciate it."
}
],
"memo_bodies": [
{
"topic": "upcoming_renewals_batch",
"body": "INTERNAL MEMO — Upcoming Trademark Renewals (Next 60 Days)\n\nPlease action the following renewal matters before the respective deadlines. Confirm receipt of client instructions and ensure renewal fees are collected in advance.\n\n1. TM No. {tm_number_1} | Mark: {tm_text_1} | Client: {client_1} | Class: {class_1} | Renewal Deadline: {deadline_1}\n — Client instructions received: PENDING. Follow up with {contact_person_1} immediately.\n\n2. TM No. {tm_number_2} | Mark: {tm_text_2} | Client: {client_2} | Class: {class_2} | Renewal Deadline: {deadline_2}\n — Renewal fee invoice to be issued. Note: 6-month grace period expires on {deadline_2}. No further extension available.\n\n3. TM No. {tm_number_3} | Mark: {tm_text_3} | Client: {client_3} | Class: {class_3} | Renewal Deadline: {deadline_3}\n — Instructions confirmed. File renewal with HKIPD by {deadline_3}. Update docket upon completion.\n\nAction: Prepare docketing report for partner review by end of week. Flag any matter where client instructions remain outstanding beyond 14 days prior to deadline."
},
{
"topic": "overdue_items",
"body": "INTERNAL MEMO — Overdue Trademark Matters: Urgent Attention Required\n\nThe following matters are overdue or approaching critical status. Immediate escalation required.\n\n1. TM No. {tm_number_1} | Mark: {tm_text_1} | Client: {client_1}\n — Response to HKIPD examination report was due {deadline_1}. NO RESPONSE FILED. Contact {contact_person_1} today to obtain instructions. If no response is filed, application will be treated as abandoned. Advise client of consequences in writing.\n\n2. TM No. {tm_number_2} | Mark: {tm_text_2} | Client: {client_2}\n — Opposition period expired {deadline_2}. Awaiting confirmation of registration certificate. Check HKIPD online database and update file status accordingly.\n\n3. TM No. {tm_number_3} | Mark: {tm_text_3} | Client: {client_3}\n — Renewal overdue as of {deadline_3}. Grace period filing may still be possible. Check current status with HKIPD registry and advise client of late renewal surcharge immediately.\n\nNote: All overdue matters must be reported in writing to the supervising partner by close of business today. Do not allow further delay on Item 1."
},
{
"topic": "priority_matters",
"body": "INTERNAL MEMO — Priority Trademark Matters: This Week\n\nThe following matters require priority handling and should be actioned before all other routine correspondence.\n\n1. TM No. {tm_number_1} | Mark: {tm_text_1} | Client: {client_1} | Class: {class_1}\n — HKIPD has issued an adverse examination report citing a conflicting mark. Deadline to respond: {deadline_1}. Prepare draft submissions addressing relative grounds and distinctiveness arguments. Circulate draft to {contact_person_1} for client approval no later than {deadline_1} minus 7 days.\n\n2. TM No. {tm_number_2} | Mark: {tm_text_2} | Client: {client_2} | Class: {class_2}\n — Cease and desist letter received from third party claiming prior rights. Matter requires urgent review. Advise {contact_person_2} on defensive options including co-existence agreement or opposition proceedings. Response to opposing counsel due: {deadline_2}.\n\nNote: Both matters are to be flagged as HIGH PRIORITY in the case management system. No extensions to be sought without partner approval."
},
{
"topic": "quarterly_review",
"body": "INTERNAL MEMO — Quarterly Trademark Portfolio Review: Q{quarter} {year}\n\nThis memo summarises outstanding action items identified during the quarterly portfolio review meeting. Please update all file statuses and billing records accordingly.\n\n1. TM No. {tm_number_1} | Mark: {tm_text_1} | Client: {client_1} | Class: {class_1}\n — Status: Registered. Next renewal due {deadline_1}. Reminder letter to be sent to {contact_person_1} 12 months in advance. Confirm current address for recordals.\n\n2. TM No. {tm_number_2} | Mark: {tm_text_2} | Client: {client_2} | Class: {class_2}\n — Status: Application pending. Examination report anticipated by {deadline_2}. Monitor HKIPD database weekly and notify {contact_person_2} upon publication or any office action.\n\n3. TM No. {tm_number_3} | Mark: {tm_text_3} | Client: {client_3} | Class: {class_3}\n — Status: Under opposition. Hearing scheduled for {deadline_3}. Prepare witness statements and evidence of use bundles. Liaise with {contact_person_3} to gather supporting commercial materials.\n\nNext quarterly review meeting to be scheduled for the first week of the following quarter. Docket report to be circulated 5 business days in advance."
},
{
"topic": "opposition_deadlines",
"body": "INTERNAL MEMO — Trademark Opposition Deadlines: Action Required\n\nThe following marks published in the Hong Kong Intellectual Property Gazette are being monitored for potential opposition. Client instructions must be obtained and filed within the statutory opposition window.\n\n1. Published Mark: {tm_text_1} | Application No. {tm_number_1} | Applicant: [Third Party]\n — Our Client: {client_1} | Opposing on behalf of: {contact_person_1}\n — Grounds: Likelihood of confusion with client's registered mark. Notice of opposition deadline: {deadline_1}.\n — Action: Draft Notice of Opposition Form T9 and evidence of earlier rights. Obtain signed authority from {client_1} to proceed. File on or before {deadline_1}.\n\n2. Published Mark: {tm_text_2} | Application No. {tm_number_2} | Applicant: [Third Party]\n — Our Client: {client_2} | Monitoring only — no opposition recommended.\n — Basis for non-opposition: Distinct class and low commercial overlap. File note to be placed on record. Advise {contact_person_2} of monitoring decision by {deadline_2}.\n\nReminder: Opposition periods are strictly non-extendable under the Trade Marks Ordinance (Cap. 559). Miss no deadlines."
},
{
"topic": "examination_response_deadlines",
"body": "INTERNAL MEMO — Examination Report Responses: Pending Deadlines\n\nThe following trademark applications have received examination reports from HKIPD requiring substantive response. Deadlines are firm and no extension will be sought unless absolutely necessary.\n\n1. Application No. {tm_number_1} | Mark: {tm_text_1} | Client: {client_1} | Class: {class_1}\n — Office Action Date: {deadline_1} minus 60 days. Response Due: {deadline_1}.\n — Objection Grounds: Descriptiveness under s.11(1)(b) of the Trade Marks Ordinance.\n — Proposed Response: Submit evidence of acquired distinctiveness and statutory declaration from {contact_person_1}. Draft response to be completed 10 days before deadline.\n\n2. Application No. {tm_number_2} | Mark: {tm_text_2} | Client: {client_2} | Class: {class_2}\n — Office Action Date: {deadline_2} minus 45 days. Response Due: {deadline_2}.\n — Objection Grounds: Citation of earlier conflicting mark No. {tm_number_3}.\n — Proposed Response: Request owner of cited mark to withdraw citation or consent to co-existence. If unsuccessful, prepare substantive submissions distinguishing marks. Liaise with {contact_person_2} immediately.\n\nAll draft responses must be reviewed and approved by Benjamin Choi before filing."
},
{
"topic": "new_filings_status",
"body": "INTERNAL MEMO — New Trademark Filings: Status Update\n\nThe following new trademark applications have been filed or are pending filing instructions. Please verify official receipt and update the docket with filing particulars.\n\n1. Mark: {tm_text_1} | Client: {client_1} | Class: {class_1}\n — Filing Instructions Received: Yes. Target Filing Date: {deadline_1}.\n — Proposed Application No. (once assigned): {tm_number_1}\n — Action: Conduct pre-filing clearance search. If clear, proceed to file with HKIPD. Send filing confirmation and official receipt to {contact_person_1} within 3 business days of filing.\n\n2. Mark: {tm_text_2} | Client: {client_2} | Class: {class_2}\n — Filing Instructions Received: PENDING. Awaiting signed engagement letter and advance payment of official fees.\n — Target Filing Date: {deadline_2}. Note: Client has indicated urgency due to competitor activity.\n — Action: Chase {contact_person_2} for executed documents and payment. Do not file until all formalities are satisfied.\n\n3. Mark: {tm_text_3} | Client: {client_3} | Class: {class_3}\n — Application filed: {tm_number_3}. Awaiting examination. Expected examination period: 36 months from {deadline_3}.\n — Action: Set docket reminder for {deadline_3} to check examination status on HKIPD online portal.\n\nPlease ensure all new files are opened in the practice management system within 24 hours of filing."
},
{
"topic": "billing_follow_up",
"body": "INTERNAL MEMO — Billing Follow-Up: Outstanding Invoices — Trademark Matters\n\nThe following matters have outstanding invoices requiring follow-up. Finance to escalate as appropriate. Do not release further work product until payment is received on overdue accounts unless otherwise instructed by partner.\n\n1. TM No. {tm_number_1} | Mark: {tm_text_1} | Client: {client_1}\n — Invoice No. {invoice_number_1} issued {deadline_1} minus 30 days. Amount: HK${amount_1}.\n — Status: OVERDUE — 30+ days outstanding. Services rendered: Examination report response and prosecution.\n — Action: Send formal payment reminder to {contact_person_1}. If no payment received within 14 days, escalate to partner for decision on further credit extension.\n\n2. TM No. {tm_number_2} | Mark: {tm_text_2} | Client: {client_2}\n — Invoice No. {invoice_number_2} issued {deadline_2} minus 15 days. Amount: HK${amount_2}.\n — Status: Due {deadline_2}. Services rendered: New trademark filing and clearance search.\n — Action: Routine payment reminder to {contact_person_2} one week before due date. Confirm bank transfer details are correct.\n\n3. TM No. {tm_number_3} | Mark: {tm_text_3} | Client: {client_3}\n — Disbursements (HKIPD official fees) paid on client's behalf. Reimbursement invoice to be raised.\n — Action: Prepare disbursement invoice for {contact_person_3} and issue by {deadline_3}. Attach copy of HKIPD payment receipt.\n\nNote: All billing queries to be directed to the finance team. Partner approval required before any fee reduction or write-off."
}
],
"email_subjects": [
"Re: Trademark Application No. {tm_number} Office Action Response Required",
"商標申請編號 {tm_number} 審查意見書通知",
"Urgent: Examination Report Issued TM No. {tm_number}",
"Re: \"{tm_text}\" Trademark Registration Certificate Received",
"商標 \"{tm_text}\" 異議通知 (Opposition Notice)",
"Action Required: Renewal Deadline TM No. {tm_number}",
"Re: {tm_number} Response to Examiner's Objection",
"\"{tm_text}\" 商標 注冊成功通知",
"Re: Trademark Opposition Proceedings {tm_number} Hearing Date Confirmed",
"商標編號 {tm_number} 續期申請確認",
"Re: \"{tm_text}\" Cease and Desist Letter Follow-Up",
"Reminder: Outstanding Instructions Required TM Application \"{tm_text}\"",
"商標 {tm_number} 更改持有人姓名/地址申請",
"Re: {tm_number} Notice of Acceptance and Publication in HKIPJ",
"FYI: Status Update on \"{tm_text}\" Portfolio Hong Kong & Mainland China"
],
"deadline_phrases": [
"Please respond by {deadline} to avoid any lapse in your trademark rights.",
"The statutory deadline for filing a response is {deadline}.",
"請於{deadline}前回覆,以免影響您的商標權益。",
"Kindly revert with your instructions no later than {deadline}.",
"We must receive your instructions by {deadline} at the latest.",
"注意:本案之法定期限為{deadline},逾期將不獲受理。",
"To allow adequate preparation time, we respectfully request your confirmation before {deadline}.",
"This matter requires your urgent attention, as the deadline falls on {deadline}.",
"請盡快與我們聯絡,最遲不得晚於{deadline}。",
"Time is of the essence. The non-extendable deadline is {deadline}.",
"Failure to respond before {deadline} may result in abandonment of the application.",
"如閣下希望提出異議,須於{deadline}前向香港知識產權署提交相關文件。",
"We would appreciate your instructions well in advance of the deadline of {deadline}.",
"As a courtesy reminder, please note that {deadline} is approaching rapidly.",
"最後期限為{deadline},本所建議預留充足時間處理相關程序。"
],
"letter_closings": [
"We trust the foregoing is in order and to your satisfaction. Should you have any queries or require any clarification in respect of the above, please do not hesitate to contact the undersigned. We look forward to receiving your further instructions.",
"Please review the contents of this letter carefully and revert with your instructions at your earliest convenience. We remain at your disposal should you require any additional information or wish to discuss this matter further.",
"We shall proceed in accordance with your instructions upon receipt thereof. In the meantime, should you have any questions regarding the above, please feel free to contact our office at any time.",
"This letter is sent to you strictly on a without-prejudice basis and nothing contained herein shall be construed as a waiver of any of our client's rights or remedies, all of which are expressly reserved.",
"We hope this letter sets out the current position clearly. We would be grateful if you could confirm receipt of this correspondence and advise as to your intended course of action within the time stipulated above.",
"Kindly note that the above constitutes a summary of the current position and should not be taken as a comprehensive statement of all applicable laws and regulations. We recommend that you seek independent legal advice should you have any concerns.",
"We appreciate your continued trust in Jingtian & Gongcheng LLP and look forward to assisting you with this and all future intellectual property matters. Please do not hesitate to contact us should you require any further assistance.",
"Please be guided accordingly. We shall update you on any further developments as and when they arise. In the meantime, we remain at your service and await your valued instructions."
],
"invoice_descriptions": [
"Professional fees for trademark application filing in Hong Kong Class {class}",
"Government filing fee Hong Kong Trademark Application Class {class}",
"Professional fees for preparation and filing of response to Examiner's Office Action",
"Professional fees for trademark renewal filing Class {class}",
"Government official fee for trademark renewal Class {class}",
"Professional fees for trademark search and clearance opinion Class {class}",
"Professional fees for filing Notice of Opposition against conflicting trademark application",
"Professional fees for preparation of Statutory Declaration in support of trademark application",
"Government filing fee for recordal of change of owner name/address TM Portfolio",
"Professional fees for trademark assignment preparation and filing of deed and recordal",
"Professional fees for drafting and serving cease and desist letter in respect of trademark infringement",
"Disbursements courier, translation, official certified copies and miscellaneous out-of-pocket expenses"
]
}
+28
View File
@@ -0,0 +1,28 @@
{
"firm": {
"name": "Jingtian & Gongcheng LLP",
"chinese_name": "競天公誠律師事務所",
"address": "Suites 3203-3207, 32/F, Edinburgh Tower, The Landmark, 15 Queen's Road Central, Central, Hong Kong",
"phone": "+852 2258 6688",
"fax": "+852 2258 6699",
"email": "hk@jingtian.com",
"attorney": {
"name": "Benjamin Choi",
"chinese_name": "蔡明睿",
"title": "Partner",
"email": "benjamin.choi@jingtian.com",
"direct_line": "+852 2258 6601"
}
},
"ipd": {
"name": "Trade Marks Registry, Intellectual Property Department",
"chinese_name": "香港特別行政區政府知識產權署商標註冊處",
"full_header": "Trade Marks Registry, Intellectual Property Department\nThe Government of the Hong Kong Special Administrative Region",
"address": "24/F, Wu Chung House, 213 Queen's Road East, Wan Chai, Hong Kong"
},
"reference_formats": [
"JT/{client_initials}/{year}/{seq:04d}",
"BC/{client_initials}/{year}-{seq:03d}",
"HK-TM-{year}-{seq:05d}"
]
}
+59
View File
@@ -0,0 +1,59 @@
{
"trademarks": [
{"number": "306457384", "text": "肝仔癀", "status": "Application Opposed", "classes": [5], "owner": "Mark Up Limited", "filing_date": "22-01-2024", "mark_type": "Ordinary"},
{"number": "306485220", "text": "CHENXING VENTURES 晨兴创投", "status": "Application Published", "classes": [35, 36], "owner": "Chenxing Ventures Inc.", "filing_date": "05-02-2024", "mark_type": "Ordinary"},
{"number": "306527151", "text": "官药坊", "status": "Refusal Letter Issued", "classes": [5], "owner": "Mark Up Limited", "filing_date": "12-03-2024", "mark_type": "Ordinary"},
{"number": "306623226", "text": "目清素", "status": "Examined - Further Examination Report Issued", "classes": [5], "owner": "Vita Green Health Products Company Limited", "filing_date": "20-05-2024", "mark_type": "Ordinary"},
{"number": "306751963", "text": "冇濕輕 冇湿轻", "status": "Examined - Further Examination Report Issued", "classes": [3, 5, 29, 30, 31, 32], "owner": "Mark Up Limited", "filing_date": "15-08-2024", "mark_type": "Ordinary"},
{"number": "306819931", "text": "MONEE", "status": "Examined - Further Examination Report Issued", "classes": [9, 35, 36, 38, 42], "owner": "Monee Payment Holding Private Limited", "filing_date": "30-08-2024", "mark_type": "Ordinary"},
{"number": "306848830", "text": "Doctor's Choice", "status": "Examined - Further Examination Report Issued", "classes": [3, 5, 29, 30, 31, 32], "owner": "Vita Green Health Products Company Limited", "filing_date": "15-09-2024", "mark_type": "Ordinary"},
{"number": "306848849", "text": "Meno Lite", "status": "Application Opposed", "classes": [5, 30], "owner": "Vita Green Health Products Company Limited", "filing_date": "15-09-2024", "mark_type": "Ordinary"},
{"number": "306893669", "text": "NEO SOLICITORS LLP 梁、吳律師行有限法律責任合夥", "status": "Registered", "classes": [45], "owner": "NEO CONSULTANCY LIMITED", "filing_date": "01-11-2024", "mark_type": "Ordinary"},
{"number": "306914467", "text": "Naturo Vita 極纖秀 极纤秀", "status": "Application Published", "classes": [3, 5, 29, 30, 32], "owner": "Vita Green Health Products Company Limited", "filing_date": "20-11-2024", "mark_type": "Ordinary"},
{"number": "1980B1178", "text": "RAM", "status": "Registered", "classes": [28], "owner": "Ram Golf Limited", "filing_date": "01-01-1980", "mark_type": "Ordinary"},
{"number": "199303414", "text": "ZEBRA", "status": "Registered", "classes": [28], "owner": "Ram Golf Limited", "filing_date": "01-01-1993", "mark_type": "Ordinary"},
{"number": "300431919", "text": "TRUE RELIGION", "status": "Registered", "classes": [25], "owner": "Guru Denim LLC", "filing_date": "15-06-2005", "mark_type": "Ordinary"},
{"number": "306435018", "text": "MERIT", "status": "Registered", "classes": [3], "owner": "Power Beauty Co.", "filing_date": "10-01-2024", "mark_type": "Ordinary"},
{"number": "306797521", "text": "bitkub", "status": "Registered", "classes": [9, 36, 42], "owner": "Bitkub Capital Group Holdings Company Limited", "filing_date": "25-08-2024", "mark_type": "Ordinary"},
{"number": "306785371", "text": "德鎂", "status": "Registered", "classes": [3], "owner": "Dermavon Holdings Limited", "filing_date": "10-08-2024", "mark_type": "Ordinary"},
{"number": "306454729", "text": "焙之玺", "status": "Registered", "classes": [29, 30, 35], "owner": "BakeMark USA LLC", "filing_date": "20-01-2024", "mark_type": "Ordinary"},
{"number": "306671485", "text": "YES! GOLF", "status": "Registered", "classes": [28], "owner": "Ram Golf Limited", "filing_date": "01-07-2024", "mark_type": "Ordinary"},
{"number": "306735835", "text": "CALIFORNIA BABY", "status": "Registered", "classes": [3, 5, 35], "owner": "RALPH, PACO & ROBERTO, INC.", "filing_date": "01-08-2024", "mark_type": "Ordinary"},
{"number": "306314003", "text": "MacGregor", "status": "Registered", "classes": [28], "owner": "Ram Golf Limited", "filing_date": "01-10-2023", "mark_type": "Ordinary"}
],
"statuses": [
"Registered",
"Application Opposed",
"Application Published",
"Examined - First Examination Report Issued",
"Examined - Further Examination Report Issued",
"Refusal Letter Issued",
"Application Details Checked, Application Pending",
"Application Received"
],
"nice_classes": {
"3": "Cosmetics, cleaning preparations, perfumery",
"5": "Pharmaceuticals, medical preparations, dietary supplements",
"9": "Electronics, software, scientific apparatus",
"10": "Medical devices, surgical instruments",
"14": "Jewelry, precious metals, watches, clocks",
"16": "Paper, printed matter, stationery",
"18": "Leather goods, bags, umbrellas",
"21": "Household utensils, kitchenware, glassware",
"25": "Clothing, footwear, headwear",
"28": "Games, toys, sporting goods",
"29": "Meat, fish, preserved foods, dairy",
"30": "Coffee, flour, rice, confectionery, sauces",
"31": "Agricultural products, live animals, fresh produce",
"32": "Non-alcoholic beverages, beer",
"33": "Alcoholic beverages (except beer)",
"35": "Advertising, business management, retail services",
"36": "Financial, insurance, real estate services",
"38": "Telecommunications services",
"41": "Education, entertainment, sporting activities",
"42": "Scientific/tech services, software, R&D",
"43": "Food services, restaurants, accommodation",
"44": "Medical, veterinary, beauty care services",
"45": "Legal services, security, personal services"
}
}
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""
Generate content pools for JingTian sample documents using Azure Claude API.
Run this once to create data/content_pools.json.
Makes multiple smaller API calls to avoid timeouts.
"""
import json
import os
import sys
import time
from pathlib import Path
try:
from anthropic import AnthropicFoundry
except ImportError:
print("Error: anthropic package not installed. Run: pip install anthropic")
sys.exit(1)
ENDPOINT = "https://admin-ml6rv1i3-swedencentral.services.ai.azure.com/anthropic/"
MODEL = "claude-sonnet-4-6"
API_KEY = os.environ.get("AZURE_CLAUDE_API_KEY", "")
OUTPUT_PATH = Path(__file__).parent / "data" / "content_pools.json"
SYSTEM = """You are generating realistic content for sample documents used by a Hong Kong IP/trademark law firm called Jingtian & Gongcheng LLP. The sole practitioner is Benjamin Choi (蔡明睿), Partner. His office is at Suites 3203-3207, 32/F, Edinburgh Tower, The Landmark, 15 Queen's Road Central, Central, Hong Kong.
Return ONLY valid JSON arrays, no markdown code blocks, no explanation. Use placeholders like {deadline}, {tm_number}, {tm_text}, {client_name}, {contact_person}, {class} where indicated."""
PROMPTS = {
"letter_to_client_bodies": """Generate a JSON array of 10 different letter bodies FROM Jingtian & Gongcheng TO clients regarding trademark matters. Each should be 2-4 paragraphs, formal HK legal style. Varied topics: opposition response needed, examination report received, renewal reminder, registration confirmed, filing update, amendment required, deadline approaching, evidence submission required, hearing notice, costs estimate.
Each MUST contain placeholders: {deadline}, {tm_number}, {tm_text}, {client_name}, {contact_person}.
Start each with "Dear {contact_person},".
Mix some bilingual (EN/CN) content naturally.""",
"letter_from_client_bodies": """Generate a JSON array of 10 different letter bodies FROM clients TO Benjamin Choi at Jingtian. These are client instructions/responses, slightly less formal. Varied topics: proceed with filing, approve response, query about status, provide evidence, confirm renewal, change instructions, budget concerns, urgent request, new trademark idea, withdrawal request.
Use placeholders: {tm_number}, {tm_text} where relevant. About 70% should include {deadline}.
Start each with "Dear Mr. Choi," or "Dear Benjamin,".""",
"email_from_client_bodies": """Generate a JSON array of 10 different SHORT email bodies FROM clients TO Benjamin Choi. Casual/professional email style, 1-3 short paragraphs each. Varied topics: quick follow-up, status check, forwarding a document, asking about costs, confirming a meeting, deadline reminder, new matter inquiry, sending signed docs, travel affecting timeline, board meeting deadline.
About 60% should include {deadline}. Use {tm_number} or {tm_text} where natural.
Start with "Hi Benjamin," or "Dear Benjamin," or "Hi Mr. Choi,".""",
"memo_bodies": """Generate a JSON array of 8 different internal memo bodies. These are Benjamin Choi's internal notes/reminders about trademark matters. Each should list 2-3 items using numbered placeholders like {tm_number_1}, {tm_text_1}, {deadline_1}, {client_1}, {tm_number_2}, {tm_text_2}, {deadline_2}, {client_2}, etc.
Topics: upcoming renewals batch, overdue items, priority matters, quarterly review, opposition deadlines, examination response deadlines, new filings status, billing follow-up.""",
"short_pools": """Generate a JSON object with these keys:
"email_subjects": array of 15 realistic email subject lines for HK trademark correspondence. Use {tm_number} or {tm_text} placeholders. Mix EN/CN.
"deadline_phrases": array of 15 different ways to express a deadline. Each contains {deadline}. Mix formal/informal, EN/CN. Examples: "Please respond by {deadline}", "The statutory deadline is {deadline}", "請於{deadline}前回覆".
"letter_closings": array of 8 formal letter closing paragraphs (before "Yours faithfully"). HK legal style.
"invoice_descriptions": array of 12 invoice line item descriptions for trademark services. Use {class} placeholder where relevant. e.g. "Professional fees for trademark application filing - Class {class}", "Government filing fee for trademark renewal".""",
}
def call_api(client, prompt, label):
"""Make a single API call and return parsed JSON."""
print(f" Generating {label}...", end=" ", flush=True)
start = time.time()
message = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=[{"role": "user", "content": prompt}],
max_tokens=8000,
)
raw = message.content[0].text.strip()
# Strip markdown code blocks if present
if raw.startswith("```"):
lines = raw.split("\n")
raw = "\n".join(lines[1:-1])
elapsed = time.time() - start
print(f"done ({elapsed:.1f}s, {len(raw)} chars)")
return json.loads(raw)
def main():
if not API_KEY:
print("Error: AZURE_CLAUDE_API_KEY environment variable not set.")
print("Usage: AZURE_CLAUDE_API_KEY=<key> python generate_content_pools.py")
sys.exit(1)
print(f"Connecting to Azure Claude ({MODEL})...")
client = AnthropicFoundry(
api_key=API_KEY,
base_url=ENDPOINT,
)
content_pools = {}
# Generate each pool separately
for key, prompt in PROMPTS.items():
try:
result = call_api(client, prompt, key)
if key == "short_pools":
# This returns an object with multiple keys
content_pools.update(result)
else:
content_pools[key] = result
except json.JSONDecodeError as e:
print(f"\n ERROR parsing {key}: {e}")
print(f" Skipping {key}, continuing...")
continue
except Exception as e:
print(f"\n ERROR calling API for {key}: {e}")
print(f" Skipping {key}, continuing...")
continue
# Summary
print("\nContent pools summary:")
for key, val in content_pools.items():
if isinstance(val, list):
print(f" {key}: {len(val)} items")
# Save
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_PATH.write_text(
json.dumps(content_pools, indent=2, ensure_ascii=False),
encoding="utf-8",
)
print(f"\nSaved to {OUTPUT_PATH}")
if __name__ == "__main__":
main()
+553
View File
@@ -0,0 +1,553 @@
#!/usr/bin/env python3
"""
JingTian-Tracker Sample Document Generator
Generates 7 realistic sample documents for testing the JingTian document
processing pipeline. Uses pre-generated content pools (no LLM needed at runtime).
Usage:
python generate_samples.py --output ./outputs
python generate_samples.py --output ./outputs --seed 42
Documents generated:
1. DOCX - Letter TO client (Client/{name}/)
2. PDF - Scanned letter FROM client (Client/{name}/)
3. PNG - Email screenshot FROM client (Client/{name}/)
4. PDF - IPD filing receipt (Admin/IPD e-filing/)
5. DOCX - Internal memo (Admin/General Matter/)
6. XLSX - Invoice schedule (Billing/Draft Bills/)
7. PDF - TM registry record (IP/)
+ Copies 2 real PDFs to IP/
"""
import argparse
import json
import random
import shutil
import sys
from datetime import datetime, timedelta
from pathlib import Path
# Document generators
from generators.docx_letter_to_client import generate as gen_letter_to
from generators.pdf_scanned_letter import generate as gen_scanned_letter
from generators.image_email import generate as gen_email_image
from generators.pdf_filing_receipt import generate as gen_filing_receipt
from generators.docx_memo import generate as gen_memo
from generators.xlsx_invoice import generate as gen_invoice
from generators.pdf_tm_record import generate as gen_tm_record
DATA_DIR = Path(__file__).parent / "data"
REAL_PDFS_DIR = Path(__file__).parent / "real_pdfs"
def load_pools():
"""Load all data pools."""
pools = {}
for name in ["clients", "trademarks", "names", "content_pools"]:
path = DATA_DIR / f"{name}.json"
if not path.exists():
print(f"Error: {path} not found. Run generate_content_pools.py first.")
sys.exit(1)
pools[name] = json.loads(path.read_text(encoding="utf-8"))
return pools
def random_future_date(min_days=30, max_days=180):
"""Generate a random date in the future."""
delta = timedelta(days=random.randint(min_days, max_days))
return (datetime.now() + delta).strftime("%d-%m-%Y")
def random_tm_number():
"""Generate a realistic 9-digit TM number."""
prefix = random.choice(["306", "307"])
suffix = str(random.randint(100000, 999999))
return prefix + suffix
def sanitize_dirname(name):
"""Sanitize a string for use as a directory/file name."""
# Replace dots at end (Windows issue), replace spaces with underscores
name = name.replace(" ", "_")
name = name.rstrip(".")
# Remove other problematic chars
for ch in ["<", ">", ":", '"', "/", "\\", "|", "?", "*"]:
name = name.replace(ch, "")
return name
def pick_client(pools):
"""Pick a random client with contact info."""
client = random.choice(pools["clients"]["clients"])
return client
def pick_trademark(pools):
"""Pick a random trademark from the real data."""
tm = random.choice(pools["trademarks"]["trademarks"])
return tm
def get_body(item):
"""Extract body text from a content pool item (str or dict with 'body' key)."""
if isinstance(item, dict):
return item.get("body", str(item))
return str(item)
def fill_template(template, replacements):
"""Fill placeholders in a template string."""
result = get_body(template) if not isinstance(template, str) else template
for key, val in replacements.items():
result = result.replace(f"{{{key}}}", str(val))
return result
def generate_all(output_dir, pools):
"""Generate all 7 documents and copy real PDFs."""
manifest = {
"generated_at": datetime.now().isoformat(),
"documents": [],
}
firm = pools["names"]["firm"]
ipd = pools["names"]["ipd"]
content = pools["content_pools"]
# ── 1. DOCX: Letter TO client ──────────────────────────────────
client1 = pick_client(pools)
tm1 = pick_trademark(pools)
deadline1 = random_future_date(30, 120)
body1 = random.choice(content["letter_to_client_bodies"])
closing1 = random.choice(content["letter_closings"])
replacements1 = {
"deadline": deadline1,
"tm_number": tm1["number"],
"tm_text": tm1["text"],
"client_name": client1["name"],
"contact_person": client1["contact_person"],
}
client_dir = output_dir / "Client" / sanitize_dirname(client1["name"])
client_dir.mkdir(parents=True, exist_ok=True)
fname1 = f"Letter_Re_TM{tm1['number']}.docx"
gen_letter_to(
output_path=client_dir / fname1,
firm=firm,
client=client1,
body=fill_template(body1, replacements1),
closing=fill_template(closing1, replacements1),
ref_number=f"JT/{datetime.now().year}/{random.randint(1000, 9999)}",
date=datetime.now().strftime("%d %B %Y"),
re_line=f"Trademark Application No. {tm1['number']} - {tm1['text']}",
)
manifest["documents"].append(
{
"filename": fname1,
"path": f"Client/{sanitize_dirname(client1['name'])}/{fname1}",
"type": "Letter to Client",
"format": "docx",
"expected_extraction": {
"document_type": "Client Correspondence",
"deadline": deadline1,
"client": client1["name"],
"tm_number": tm1["number"],
},
}
)
print(f" [1/7] DOCX letter to client: {fname1}")
# ── 2. PDF: Scanned letter FROM client ─────────────────────────
client2 = pick_client(pools)
tm2 = pick_trademark(pools)
deadline2 = random_future_date(14, 90)
body2 = random.choice(content["letter_from_client_bodies"])
has_deadline2 = "{deadline}" in body2
replacements2 = {
"deadline": deadline2,
"tm_number": tm2["number"],
"tm_text": tm2["text"],
"client_name": client2["name"],
"contact_person": client2["contact_person"],
}
client2_dir = output_dir / "Client" / sanitize_dirname(client2["name"])
client2_dir.mkdir(parents=True, exist_ok=True)
fname2 = f"Client_Instructions_{sanitize_dirname(client2['name'])}.pdf"
gen_scanned_letter(
output_path=client2_dir / fname2,
from_name=client2["contact_person"],
from_company=client2["name"],
from_address=client2["address"],
to_name=firm["attorney"]["name"],
to_firm=firm["name"],
body=fill_template(body2, replacements2),
date=datetime.now().strftime("%d %B %Y"),
)
manifest["documents"].append(
{
"filename": fname2,
"path": f"Client/{sanitize_dirname(client2['name'])}/{fname2}",
"type": "Letter from Client (Scanned)",
"format": "pdf_scanned",
"ocr_required": True,
"expected_extraction": {
"document_type": "Client Instructions",
"deadline": deadline2 if has_deadline2 else None,
"client": client2["name"],
"tm_number": tm2["number"] if "{tm_number}" in body2 else None,
},
}
)
print(f" [2/7] PDF scanned letter from client: {fname2}")
# ── 3. PNG: Email FROM client ──────────────────────────────────
client3 = pick_client(pools)
tm3 = pick_trademark(pools)
deadline3 = random_future_date(7, 60)
email_body = random.choice(content["email_from_client_bodies"])
email_subject = random.choice(content["email_subjects"])
has_deadline3 = "{deadline}" in email_body
replacements3 = {
"deadline": deadline3,
"tm_number": tm3["number"],
"tm_text": tm3["text"],
"client_name": client3["name"],
"contact_person": client3["contact_person"],
}
client3_dir = output_dir / "Client" / sanitize_dirname(client3["name"])
client3_dir.mkdir(parents=True, exist_ok=True)
fname3 = f"Email_{sanitize_dirname(client3['name'])}_{datetime.now().strftime('%Y%m%d')}.png"
gen_email_image(
output_path=client3_dir / fname3,
from_email=client3.get(
"email", f"info@{client3['name'].lower().replace(' ', '')}.com"
),
from_name=client3["contact_person"],
to_email="benjamin.choi@jingtian.com",
to_name=firm["attorney"]["name"],
subject=fill_template(email_subject, replacements3),
body=fill_template(email_body, replacements3),
date=datetime.now().strftime("%A, %d %B %Y %H:%M"),
)
manifest["documents"].append(
{
"filename": fname3,
"path": f"Client/{sanitize_dirname(client3['name'])}/{fname3}",
"type": "Email from Client (Screenshot)",
"format": "png",
"ocr_required": True,
"expected_extraction": {
"document_type": "Client Email",
"deadline": deadline3 if has_deadline3 else None,
"client": client3["name"],
"tm_number": tm3["number"]
if "{tm_number}" in email_body or "{tm_number}" in email_subject
else None,
},
}
)
print(f" [3/7] PNG email screenshot: {fname3}")
# ── 4. PDF: IPD Filing Receipt ─────────────────────────────────
client4 = pick_client(pools)
tm_number4 = random_tm_number()
tm_text4 = random.choice(
[
client4["name"].split()[0].upper(),
random.choice(["NOVA", "APEX", "STELLAR", "ZENITH", "PRIMEX", "VANTAGE"]),
]
)
filing_date = datetime.now().strftime("%d-%m-%Y")
response_deadline4 = random_future_date(60, 120)
nice_class = random.choice(list(pools["trademarks"]["nice_classes"].keys()))
filing_dir = output_dir / "Admin" / "IPD e-filing"
filing_dir.mkdir(parents=True, exist_ok=True)
fname4 = f"Filing_Receipt_{tm_number4}.pdf"
gen_filing_receipt(
output_path=filing_dir / fname4,
ipd=ipd,
tm_number=tm_number4,
tm_text=tm_text4,
applicant=client4["name"],
applicant_address=client4["address"],
agent=firm["name"],
agent_address=firm["address"],
filing_date=filing_date,
response_deadline=response_deadline4,
nice_class=nice_class,
class_description=pools["trademarks"]["nice_classes"][nice_class],
)
manifest["documents"].append(
{
"filename": fname4,
"path": f"Admin/IPD e-filing/{fname4}",
"type": "IPD Filing Receipt",
"format": "pdf_native",
"expected_extraction": {
"document_type": "Filing Receipt",
"deadline": response_deadline4,
"client": client4["name"],
"tm_number": tm_number4,
},
}
)
print(f" [4/7] PDF filing receipt: {fname4}")
# ── 5. DOCX: Internal Memo ─────────────────────────────────────
memo_body = random.choice(content["memo_bodies"])
clients_for_memo = random.sample(
pools["clients"]["clients"], min(3, len(pools["clients"]["clients"]))
)
tms_for_memo = random.sample(
pools["trademarks"]["trademarks"],
min(3, len(pools["trademarks"]["trademarks"])),
)
memo_replacements = {}
memo_deadlines = []
nice_classes_list = list(pools["trademarks"]["nice_classes"].keys())
for i in range(3):
dl = random_future_date(14 + i * 30, 60 + i * 60)
memo_deadlines.append(dl)
memo_replacements[f"tm_number_{i + 1}"] = (
tms_for_memo[i]["number"] if i < len(tms_for_memo) else random_tm_number()
)
# Use TM number as the mark reference (Chinese text causes rendering issues in memos)
tm_text = tms_for_memo[i]["text"] if i < len(tms_for_memo) else "N/A"
tm_num = (
tms_for_memo[i]["number"] if i < len(tms_for_memo) else random_tm_number()
)
# If text is CJK, show as "No. XXXXXXX (text)", otherwise just the text
memo_replacements[f"tm_text_{i + 1}"] = (
tm_text if tm_text.isascii() else f"No. {tm_num}"
)
memo_replacements[f"deadline_{i + 1}"] = dl
memo_replacements[f"client_{i + 1}"] = (
clients_for_memo[i]["name"] if i < len(clients_for_memo) else "Various"
)
memo_replacements[f"class_{i + 1}"] = random.choice(nice_classes_list)
memo_replacements[f"contact_person_{i + 1}"] = (
clients_for_memo[i]["contact_person"]
if i < len(clients_for_memo)
else "N/A"
)
# Also fill generic placeholders
memo_replacements["deadline"] = memo_deadlines[0]
memo_replacements["contact_person"] = clients_for_memo[0]["contact_person"]
memo_replacements["client_name"] = clients_for_memo[0]["name"]
memo_dir = output_dir / "Admin" / "General Matter"
memo_dir.mkdir(parents=True, exist_ok=True)
fname5 = f"Memo_{datetime.now().strftime('%Y%m%d')}_{random.randint(100, 999)}.docx"
gen_memo(
output_path=memo_dir / fname5,
firm=firm,
body=fill_template(memo_body, memo_replacements),
date=datetime.now().strftime("%d %B %Y"),
subject="Upcoming Trademark Deadlines - Action Required",
)
manifest["documents"].append(
{
"filename": fname5,
"path": f"Admin/General Matter/{fname5}",
"type": "Internal Memo",
"format": "docx",
"expected_extraction": {
"document_type": "Internal Memo",
"deadlines": memo_deadlines,
"tm_numbers": [
r.get(f"tm_number_{i + 1}")
for i, r in enumerate([memo_replacements] * 3)
],
},
}
)
print(f" [5/7] DOCX internal memo: {fname5}")
# ── 6. XLSX: Invoice Schedule ──────────────────────────────────
invoice_clients = random.sample(
pools["clients"]["clients"], min(6, len(pools["clients"]["clients"]))
)
invoice_tms = random.sample(
pools["trademarks"]["trademarks"],
min(6, len(pools["trademarks"]["trademarks"])),
)
invoice_descs = content["invoice_descriptions"]
invoice_rows = []
for i in range(min(6, len(invoice_clients))):
cls = random.choice(list(pools["trademarks"]["nice_classes"].keys()))
desc = fill_template(random.choice(invoice_descs), {"class": cls})
due = random_future_date(14, 90)
amount = random.randint(5, 150) * 1000
invoice_rows.append(
{
"client": invoice_clients[i]["name"],
"matter_ref": f"JT/{datetime.now().year}/{random.randint(1000, 9999)}",
"tm_number": invoice_tms[i]["number"]
if i < len(invoice_tms)
else random_tm_number(),
"description": desc,
"amount_hkd": amount,
"due_date": due,
"status": random.choice(["Draft", "Sent", "Overdue", "Paid"]),
}
)
billing_dir = output_dir / "Billing" / "Draft Bills"
billing_dir.mkdir(parents=True, exist_ok=True)
fname6 = f"Invoice_Schedule_{datetime.now().strftime('%Y')}Q{(datetime.now().month - 1) // 3 + 1}.xlsx"
gen_invoice(
output_path=billing_dir / fname6,
rows=invoice_rows,
firm=firm,
)
manifest["documents"].append(
{
"filename": fname6,
"path": f"Billing/Draft Bills/{fname6}",
"type": "Invoice Schedule",
"format": "xlsx",
"expected_extraction": {
"document_type": "Invoice Schedule",
"deadlines": [r["due_date"] for r in invoice_rows],
"clients": [r["client"] for r in invoice_rows],
},
}
)
print(f" [6/7] XLSX invoice schedule: {fname6}")
# ── 7. PDF: TM Registry Record ─────────────────────────────────
tm7 = pick_trademark(pools)
client7 = pick_client(pools)
status_type = random.choice(pools["trademarks"]["statuses"])
# Some statuses imply deadlines
has_implicit_deadline = status_type in [
"Application Opposed",
"Examined - First Examination Report Issued",
"Examined - Further Examination Report Issued",
]
ip_dir = output_dir / "IP"
ip_dir.mkdir(parents=True, exist_ok=True)
fname7 = f"TM_Record_{tm7['number']}.pdf"
gen_tm_record(
output_path=ip_dir / fname7,
ipd=ipd,
tm_number=tm7["number"],
tm_text=tm7["text"],
status=status_type,
nice_class=str(tm7["classes"][0]) if tm7["classes"] else "5",
class_description=pools["trademarks"]["nice_classes"].get(
str(tm7["classes"][0]), "General goods and services"
),
applicant=client7["name"],
applicant_address=client7["address"],
agent=firm["name"],
agent_address=firm["address"],
filing_date=tm7.get("filing_date", datetime.now().strftime("%d-%m-%Y")),
publication_date=(
datetime.now() - timedelta(days=random.randint(30, 180))
).strftime("%d-%m-%Y"),
)
manifest["documents"].append(
{
"filename": fname7,
"path": f"IP/{fname7}",
"type": "TM Registry Record",
"format": "pdf_native",
"expected_extraction": {
"document_type": "Trademark Registry Record",
"deadline": None,
"deadline_note": "Implicit deadline based on status"
if has_implicit_deadline
else "No deadline",
"client": client7["name"],
"tm_number": tm7["number"],
"status": status_type,
},
}
)
print(f" [7/7] PDF TM registry record: {fname7}")
# ── Copy real PDFs ─────────────────────────────────────────────
if REAL_PDFS_DIR.exists():
for pdf in REAL_PDFS_DIR.glob("*.pdf"):
dest = ip_dir / pdf.name
shutil.copy2(pdf, dest)
manifest["documents"].append(
{
"filename": pdf.name,
"path": f"IP/{pdf.name}",
"type": "Real Document (not generated)",
"format": "pdf",
"expected_extraction": None,
}
)
print(f" [+] Copied real PDF: {pdf.name}")
# ── Save manifest ──────────────────────────────────────────────
llm_dir = output_dir / "_LLM"
llm_dir.mkdir(parents=True, exist_ok=True)
manifest_path = llm_dir / "manifest.json"
manifest_path.write_text(
json.dumps(manifest, indent=2, ensure_ascii=False),
encoding="utf-8",
)
print(f"\n Manifest saved to {manifest_path}")
return manifest
def main():
parser = argparse.ArgumentParser(description="Generate JingTian sample documents")
parser.add_argument(
"--output",
"-o",
default=str(Path(__file__).parent / "outputs"),
help="Output directory (default: ./outputs)",
)
parser.add_argument(
"--seed", "-s", type=int, default=None, help="Random seed for reproducible runs"
)
args = parser.parse_args()
if args.seed is not None:
random.seed(args.seed)
print(f"Using seed: {args.seed}")
output_dir = Path(args.output)
output_dir.mkdir(parents=True, exist_ok=True)
print(f"Generating JingTian sample documents...")
print(f"Output: {output_dir}\n")
pools = load_pools()
manifest = generate_all(output_dir, pools)
print(f"\nDone! Generated {len(manifest['documents'])} documents.")
if __name__ == "__main__":
main()
@@ -0,0 +1 @@
# JingTian sample document generators
+60
View File
@@ -0,0 +1,60 @@
"""CJK font registration helper for reportlab using WenQuanYi Micro Hei TTF."""
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
_REGISTERED = False
CJK_FONT = "Helvetica" # fallback
def register_cjk_font():
"""Register a CJK font with reportlab. Returns the font name to use."""
global _REGISTERED, CJK_FONT
if _REGISTERED:
return CJK_FONT
# WenQuanYi Micro Hei: TTC with TrueType outlines (reportlab compatible)
# subfontIndex 0 = regular
candidates = [
"/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc",
"/usr/share/fonts/wqy-microhei/wqy-microhei.ttc",
]
for path in candidates:
try:
pdfmetrics.registerFont(TTFont("WenQuanYi", path, subfontIndex=0))
CJK_FONT = "WenQuanYi"
_REGISTERED = True
return CJK_FONT
except Exception:
continue
print("WARNING: No CJK font available, Chinese text may not render correctly")
_REGISTERED = True
return CJK_FONT
def _has_cjk(text: str) -> bool:
"""Check if text contains CJK characters."""
for ch in text:
cp = ord(ch)
if (
0x4E00 <= cp <= 0x9FFF # CJK Unified Ideographs
or 0x3400 <= cp <= 0x4DBF # CJK Extension A
or 0x3000 <= cp <= 0x303F # CJK Symbols
or 0xFF00 <= cp <= 0xFFEF # Fullwidth Forms
or 0x2E80 <= cp <= 0x2EFF # CJK Radicals
or 0xF900 <= cp <= 0xFAFF # CJK Compatibility
):
return True
return False
def draw_cjk_text(c, x, y, text, font_name="Helvetica", font_size=10):
"""Draw text, switching to CJK font if needed."""
cjk_font = register_cjk_font()
if _has_cjk(text) and cjk_font != "Helvetica":
c.setFont(cjk_font, font_size)
else:
c.setFont(font_name, font_size)
c.drawString(x, y, text)
@@ -0,0 +1,102 @@
"""Generate DOCX: Letter TO client from Jingtian & Gongcheng."""
from pathlib import Path
from docx import Document
from docx.shared import Pt, Inches, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
def generate(
output_path: Path,
firm: dict,
client: dict,
body: str,
closing: str,
ref_number: str,
date: str,
re_line: str,
):
"""Generate a formal letter from the firm to a client."""
doc = Document()
style = doc.styles["Normal"]
font = style.font
font.name = "Times New Roman"
font.size = Pt(11)
# Firm letterhead
header = doc.add_paragraph()
header.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = header.add_run(firm["name"])
run.bold = True
run.font.size = Pt(14)
run.font.color.rgb = RGBColor(0, 51, 102)
subheader = doc.add_paragraph()
subheader.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = subheader.add_run(firm.get("chinese_name", ""))
run.font.size = Pt(12)
run.font.color.rgb = RGBColor(0, 51, 102)
addr = doc.add_paragraph()
addr.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = addr.add_run(firm["address"])
run.font.size = Pt(9)
run.font.color.rgb = RGBColor(100, 100, 100)
# Divider line
doc.add_paragraph("_" * 72).runs[0].font.color.rgb = RGBColor(180, 180, 180)
# Reference and date
ref_para = doc.add_paragraph()
ref_para.add_run(f"Our Ref: {ref_number}").font.size = Pt(10)
date_para = doc.add_paragraph()
date_para.add_run(f"Date: {date}").font.size = Pt(10)
doc.add_paragraph() # spacer
# Recipient
recipient = doc.add_paragraph()
recipient.add_run(f"{client['contact_person']}").font.size = Pt(11)
recipient.add_run("\n")
recipient.add_run(f"{client['name']}").font.size = Pt(11)
recipient.add_run("\n")
recipient.add_run(client["address"]).font.size = Pt(11)
doc.add_paragraph()
# RE line
re_para = doc.add_paragraph()
run = re_para.add_run(f"RE: {re_line}")
run.bold = True
run.underline = True
doc.add_paragraph()
# Body paragraphs — strip any closing already in body text
body_clean = body
for strip_phrase in [
"Yours sincerely,",
"Yours faithfully,",
"Kind regards,",
"Best regards,",
]:
if strip_phrase in body_clean:
body_clean = body_clean[: body_clean.index(strip_phrase)].rstrip()
for para_text in body_clean.split("\n\n"):
if para_text.strip():
doc.add_paragraph(para_text.strip())
doc.add_paragraph()
# Signature block
doc.add_paragraph("Yours faithfully,")
doc.add_paragraph()
sig = doc.add_paragraph()
run = sig.add_run(firm["attorney"]["name"])
run.bold = True
doc.add_paragraph("Partner")
doc.add_paragraph(firm["name"])
doc.save(str(output_path))
@@ -0,0 +1,68 @@
"""Generate DOCX: Internal memo."""
from pathlib import Path
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
def generate(output_path: Path, firm: dict, body: str, date: str, subject: str):
"""Generate an internal memo document."""
doc = Document()
style = doc.styles["Normal"]
font = style.font
font.name = "Arial"
font.size = Pt(11)
# MEMO header
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = title.add_run("INTERNAL MEMORANDUM")
run.bold = True
run.font.size = Pt(16)
run.font.color.rgb = RGBColor(0, 51, 102)
# Confidential marker
conf = doc.add_paragraph()
conf.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = conf.add_run("CONFIDENTIAL")
run.font.size = Pt(9)
run.font.color.rgb = RGBColor(200, 0, 0)
doc.add_paragraph("_" * 60).runs[0].font.color.rgb = RGBColor(180, 180, 180)
# Memo fields
fields = [
("TO:", firm["attorney"]["name"]),
("FROM:", firm["attorney"]["name"]),
("DATE:", date),
("RE:", subject),
]
for label, value in fields:
p = doc.add_paragraph()
run_label = p.add_run(f"{label}\t")
run_label.bold = True
run_label.font.size = Pt(11)
run_value = p.add_run(value)
run_value.font.size = Pt(11)
doc.add_paragraph("_" * 60).runs[0].font.color.rgb = RGBColor(180, 180, 180)
doc.add_paragraph()
# Body
for para_text in body.split("\n\n"):
if para_text.strip():
doc.add_paragraph(para_text.strip())
doc.add_paragraph()
# Sign-off
p = doc.add_paragraph()
run = p.add_run(firm["attorney"]["name"])
run.bold = True
doc.add_paragraph("Partner")
doc.add_paragraph(firm["name"])
doc.save(str(output_path))
@@ -0,0 +1,140 @@
"""Generate PNG: Email screenshot from client (Outlook-style)."""
from pathlib import Path
try:
from PIL import Image, ImageDraw, ImageFont
HAS_PIL = True
except ImportError:
HAS_PIL = False
def _get_font(size, bold=False):
"""Get a font with CJK support, falling back to default if unavailable."""
# Prefer WenQuanYi Micro Hei for CJK support
cjk_names = [
"/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc",
]
latin_names = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
if bold
else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
if bold
else "/usr/share/fonts/TTF/DejaVuSans.ttf",
]
# Try CJK font first (it includes Latin glyphs too)
for name in cjk_names:
try:
return ImageFont.truetype(name, size, index=0)
except (OSError, IOError):
continue
# Fall back to Latin fonts
for name in latin_names:
try:
return ImageFont.truetype(name, size)
except (OSError, IOError):
continue
return ImageFont.load_default()
def generate(
output_path: Path,
from_email: str,
from_name: str,
to_email: str,
to_name: str,
subject: str,
body: str,
date: str,
):
"""Generate an Outlook-style email screenshot as PNG."""
if not HAS_PIL:
# Fallback: write a text file
output_path.with_suffix(".txt").write_text(
f"From: {from_name} <{from_email}>\n"
f"To: {to_name} <{to_email}>\n"
f"Date: {date}\n"
f"Subject: {subject}\n\n{body}"
)
return
# Image dimensions (simulate a screen capture)
width, height = 800, 600
bg_color = (255, 255, 255)
header_bg = (242, 242, 242)
accent_color = (0, 120, 212) # Outlook blue
text_color = (51, 51, 51)
label_color = (130, 130, 130)
img = Image.new("RGB", (width, height), bg_color)
draw = ImageDraw.Draw(img)
font_header = _get_font(11, bold=True)
font_label = _get_font(10)
font_body = _get_font(11)
font_subject = _get_font(13, bold=True)
# Top bar (Outlook-style)
draw.rectangle([0, 0, width, 45], fill=accent_color)
draw.text((15, 12), "Mail - Outlook", fill=(255, 255, 255), font=font_header)
# Email header area
y = 55
draw.rectangle([0, 45, width, 200], fill=header_bg)
# Subject
draw.text((20, y), subject, fill=text_color, font=font_subject)
y += 30
# From
draw.text((20, y), "From:", fill=label_color, font=font_label)
draw.text((80, y), f"{from_name} <{from_email}>", fill=text_color, font=font_body)
y += 22
# Sent
draw.text((20, y), "Sent:", fill=label_color, font=font_label)
draw.text((80, y), date, fill=text_color, font=font_body)
y += 22
# To
draw.text((20, y), "To:", fill=label_color, font=font_label)
draw.text((80, y), f"{to_name} <{to_email}>", fill=text_color, font=font_body)
y += 22
# Subject line in header
draw.text((20, y), "Subject:", fill=label_color, font=font_label)
draw.text((80, y), subject[:60], fill=text_color, font=font_body)
y += 30
# Divider
draw.line([(15, y), (width - 15, y)], fill=(220, 220, 220), width=1)
y += 15
# Body text
for para in body.split("\n\n"):
for line in _wrap_text(para.strip(), 90):
if y > height - 30:
break
draw.text((25, y), line, fill=text_color, font=font_body)
y += 18
y += 8
img.save(str(output_path), "PNG")
def _wrap_text(text, max_chars):
"""Simple word-wrap."""
words = text.split()
lines = []
current = ""
for word in words:
if len(current) + len(word) + 1 > max_chars:
lines.append(current)
current = word
else:
current = f"{current} {word}" if current else word
if current:
lines.append(current)
return lines or [""]
@@ -0,0 +1,138 @@
"""Generate PDF: IPD Filing Receipt (native text PDF, bilingual)."""
from pathlib import Path
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
from generators.cjk_font import register_cjk_font
def _set_font(c, bold=False, size=10):
"""Set font with CJK support."""
cjk = register_cjk_font()
if cjk != "Helvetica":
c.setFont(cjk, size)
else:
c.setFont("Helvetica-Bold" if bold else "Helvetica", size)
def _has_cjk(text):
"""Check if text contains CJK characters."""
return any(ord(ch) > 0x2E80 for ch in text)
def generate(
output_path: Path,
ipd: dict,
tm_number: str,
tm_text: str,
applicant: str,
applicant_address: str,
agent: str,
agent_address: str,
filing_date: str,
response_deadline: str,
nice_class: str,
class_description: str,
):
"""Generate an IPD filing receipt PDF."""
cjk = register_cjk_font()
c = canvas.Canvas(str(output_path), pagesize=A4)
w, h = A4
def draw_text(x, y_pos, text, size=10, bold=False, centered=False):
"""Draw text, auto-switching to CJK font if needed."""
if _has_cjk(text) and cjk != "Helvetica":
c.setFont(cjk, size)
else:
c.setFont("Helvetica-Bold" if bold else "Helvetica", size)
if centered:
c.drawCentredString(x, y_pos, text)
else:
c.drawString(x, y_pos, text)
# Header
draw_text(w / 2, h - 50, ipd["name"], 12, bold=True, centered=True)
draw_text(w / 2, h - 66, ipd["chinese_name"], 10, centered=True)
draw_text(
w / 2,
h - 82,
"The Government of the Hong Kong Special Administrative Region",
10,
centered=True,
)
# Divider
c.setStrokeColor(HexColor("#333333"))
c.setLineWidth(1)
c.line(50, h - 95, w - 50, h - 95)
# Title
draw_text(
w / 2,
h - 120,
"E-Filing Receipt / Acknowledgment",
14,
bold=True,
centered=True,
)
# Receipt details
y = h - 160
fields = [
("Receipt No.:", f"EF-{tm_number[-6:]}"),
("Application No. / 申請編號:", tm_number),
("Trade Mark Text / 商標文字:", tm_text),
("Mark Type / 商標種類:", "Ordinary"),
("Class No. / 類別編號:", nice_class),
("Specification / 貨品/服務說明:", class_description),
("", ""),
("Applicant / 申請人:", applicant),
("Address / 地址:", applicant_address),
("", ""),
("Agent / 代理人:", agent),
("Agent Address / 代理人地址:", agent_address),
("", ""),
("Date of Filing / 提交日期:", filing_date),
("Date of Receipt / 確認日期:", filing_date),
]
for label, value in fields:
if not label and not value:
y -= 10
continue
draw_text(50, y, label, 10, bold=True)
# Wrap long values
if len(value) > 60:
draw_text(250, y, value[:60], 10)
y -= 16
draw_text(250, y, value[60:120], 10)
else:
draw_text(250, y, value, 10)
y -= 18
# Response deadline (highlighted)
y -= 15
c.setStrokeColor(HexColor("#cc0000"))
c.setLineWidth(0.5)
c.rect(40, y - 10, w - 80, 40, stroke=1, fill=0)
c.setFillColor(HexColor("#cc0000"))
draw_text(50, y + 12, "IMPORTANT / 重要通知:", 11, bold=True)
c.setFillColor(HexColor("#000000"))
draw_text(50, y - 4, f"Response deadline / 回覆限期: {response_deadline}", 10)
# Footer
y -= 50
c.setFillColor(HexColor("#666666"))
draw_text(
w / 2,
y,
"This is a computer-generated receipt. No signature is required.",
8,
centered=True,
)
draw_text(w / 2, y - 14, "此為電腦自動產生之收據,毋須簽署。", 8, centered=True)
c.save()
@@ -0,0 +1,144 @@
"""Generate PDF: Scanned letter FROM client (simulated scan effect)."""
import io
from pathlib import Path
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
try:
from PIL import Image, ImageFilter, ImageEnhance
import random as _random
HAS_PIL = True
except ImportError:
HAS_PIL = False
def _create_clean_pdf(
buffer, from_name, from_company, from_address, to_name, to_firm, body, date
):
"""Create a clean PDF letter."""
c = canvas.Canvas(buffer, pagesize=A4)
w, h = A4
# Company header
c.setFont("Helvetica-Bold", 14)
c.drawString(50, h - 60, from_company)
c.setFont("Helvetica", 9)
y = h - 78
for line in from_address.split(","):
c.drawString(50, y, line.strip())
y -= 14
# Date
y -= 20
c.setFont("Helvetica", 11)
c.drawString(50, y, date)
# Recipient
y -= 30
c.drawString(50, y, f"Mr. {to_name}")
y -= 16
c.drawString(50, y, to_firm)
y -= 16
c.drawString(50, y, "Suites 3203-3207, 32/F, Edinburgh Tower, The Landmark")
y -= 16
c.drawString(50, y, "15 Queen's Road Central, Central, Hong Kong")
# Body
y -= 35
c.setFont("Helvetica", 11)
for para in body.split("\n\n"):
for line_text in _wrap_text(para.strip(), 85):
if y < 80:
c.showPage()
y = h - 60
c.setFont("Helvetica", 11)
c.drawString(50, y, line_text)
y -= 16
y -= 10
# Signature area
y -= 20
c.drawString(50, y, "Yours sincerely,")
y -= 40
# Simulate a signature squiggle
c.setStrokeColor(HexColor("#1a1a8a"))
c.setLineWidth(1.5)
import random
random.seed(hash(from_name))
sx = 50
sy = y + 10
c.line(sx, sy, sx + 30, sy + 8)
c.line(sx + 30, sy + 8, sx + 50, sy - 5)
c.line(sx + 50, sy - 5, sx + 80, sy + 3)
c.setStrokeColor(HexColor("#000000"))
y -= 10
c.setFont("Helvetica-Bold", 11)
c.drawString(50, y, from_name)
y -= 16
c.setFont("Helvetica", 10)
c.drawString(50, y, from_company)
c.save()
def _wrap_text(text, max_chars):
"""Simple word-wrap."""
words = text.split()
lines = []
current = ""
for word in words:
if len(current) + len(word) + 1 > max_chars:
lines.append(current)
current = word
else:
current = f"{current} {word}" if current else word
if current:
lines.append(current)
return lines
def generate(
output_path: Path,
from_name: str,
from_company: str,
from_address: str,
to_name: str,
to_firm: str,
body: str,
date: str,
):
"""Generate a scanned-looking PDF letter."""
# First create a clean PDF in memory
buf = io.BytesIO()
_create_clean_pdf(
buf, from_name, from_company, from_address, to_name, to_firm, body, date
)
buf.seek(0)
if HAS_PIL:
# Convert to image, degrade slightly, save back as PDF
try:
from pdf2image import convert_from_bytes
images = convert_from_bytes(buf.read(), dpi=150)
if images:
img = images[0]
# Add slight grey tint (paper simulation)
enhancer = ImageEnhance.Brightness(img)
img = enhancer.enhance(0.95)
# Slight blur for scan effect
img = img.filter(ImageFilter.GaussianBlur(radius=0.3))
# Save as PDF
img.save(str(output_path), "PDF", resolution=150)
return
except Exception:
pass
# Fallback: save clean PDF as-is
output_path.write_bytes(buf.getvalue())
@@ -0,0 +1,165 @@
"""Generate PDF: Trademark registry record (mimics HK IPD format)."""
from pathlib import Path
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
from .cjk_font import register_cjk_font, _has_cjk
def generate(
output_path: Path,
ipd: dict,
tm_number: str,
tm_text: str,
status: str,
nice_class: str,
class_description: str,
applicant: str,
applicant_address: str,
agent: str,
agent_address: str,
filing_date: str,
publication_date: str,
):
"""Generate a TM registry record PDF (mimicking 306457384.pdf)."""
cjk = register_cjk_font()
c = canvas.Canvas(str(output_path), pagesize=A4)
w, h = A4
def draw_text(x, y_pos, text, size=10, bold=False, centered=False):
if _has_cjk(text) and cjk != "Helvetica":
c.setFont(cjk, size)
else:
c.setFont("Helvetica-Bold" if bold else "Helvetica", size)
if centered:
c.drawCentredString(x, y_pos, text)
else:
c.drawString(x, y_pos, text)
# ── Page 1: Basic Information ──────────────────────────────────
# Header
draw_text(w / 2, h - 45, ipd["chinese_name"], 11, bold=True, centered=True)
draw_text(w / 2, h - 60, ipd["name"], 10, centered=True)
draw_text(
w / 2,
h - 75,
"The Government of the Hong Kong Special Administrative Region",
9,
centered=True,
)
# Divider
c.setStrokeColor(HexColor("#333333"))
c.line(50, h - 88, w - 50, h - 88)
# Titles
draw_text(w / 2, h - 108, "商標記錄", 12, bold=True, centered=True)
draw_text(w / 2, h - 124, "Trade Mark Records", 12, bold=True, centered=True)
# Section: Basic Information
y = h - 150
draw_text(50, y, "基本資料 Basic information", 10, bold=True)
y -= 5
c.line(50, y, w - 50, y)
y -= 20
value_x = 280
fields_page1 = [
("[210/111]", "商標編號:\nTrade Mark No.:", tm_number),
("", "狀況:\nStatus:", status),
("", "商標文字:\nTrade Mark Text:", tm_text),
("[550]", "商標種類:\nMark Type:", "Ordinary"),
("[511]", "類別編號:\nClass No.:", nice_class),
("[511]", "貨品 / 服務說明:\nSpecification:", class_description),
]
for code, label, value in fields_page1:
if code:
c.setFont("Helvetica", 8)
c.drawString(50, y, code)
# Draw bilingual label
label_lines = label.split("\n")
for i, ll in enumerate(label_lines):
draw_text(105, y - (i * 14), ll, 9, bold=True)
# Draw value
draw_text(value_x, y, value, 10)
if len(value) > 55:
# Wrap
draw_text(value_x, y, value[:55], 10)
y -= 16
draw_text(value_x, y, value[55:110], 10)
y -= max(len(label_lines) * 14, 18) + 8
# Dates section
y -= 10
draw_text(50, y, "日期 (日日-月月-年年年年)", 10, bold=True)
c.setFont("Helvetica-Bold", 10)
c.drawString(50, y - 14, "Dates (DD-MM-YYYY)")
y -= 19
c.line(50, y, w - 50, y)
y -= 20
date_fields = [
("[220]", "提交日期:\nDate of Filing:", filing_date),
("[442]", "公布獲接納註冊申請日期:\nDate of Publication:", publication_date),
]
for code, label, value in date_fields:
c.setFont("Helvetica", 8)
c.drawString(50, y, code)
label_lines = label.split("\n")
for i, ll in enumerate(label_lines):
draw_text(105, y - (i * 14), ll, 9, bold=True)
draw_text(value_x, y, value, 10)
y -= max(len(label_lines) * 14, 18) + 8
# ── Page 2: Applicant/Owner ────────────────────────────────────
c.showPage()
y = h - 60
draw_text(50, y, "申請人/擁有人", 10, bold=True)
c.setFont("Helvetica-Bold", 10)
c.drawString(50, y - 14, "Applicant/Owner")
y -= 19
c.line(50, y, w - 50, y)
y -= 20
owner_fields = [
("[730]", "姓名/名稱:\nName:", applicant),
("[730]", "地址:\nAddress:", applicant_address),
("[842]", "類別:\nType:", "Incorporated"),
("[842]", "公司成立為法團的所在地:\nPlace of Incorporation:", "HONG KONG"),
(
"[750]",
"供送達文件的地址:\nAddress for Service:",
f"{agent}\n{agent_address}",
),
("[740]", "代理人詳情:\nAgent's Details:", f"{agent}\n{agent_address}"),
]
for code, label, value in owner_fields:
c.setFont("Helvetica", 8)
c.drawString(50, y, code)
label_lines = label.split("\n")
for i, ll in enumerate(label_lines):
draw_text(105, y - (i * 14), ll, 9, bold=True)
value_lines = value.split("\n")
for i, vl in enumerate(value_lines):
if len(vl) > 50:
draw_text(value_x, y - (i * 14), vl[:50], 10)
draw_text(value_x, y - ((i + 1) * 14), vl[50:100], 10)
else:
draw_text(value_x, y - (i * 14), vl, 10)
y -= max(len(label_lines), len(value_lines)) * 14 + 12
c.save()
@@ -0,0 +1,111 @@
"""Generate XLSX: Invoice/billing schedule."""
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
def generate(output_path: Path, rows: list, firm: dict):
"""Generate an invoice schedule Excel file."""
wb = Workbook()
ws = wb.active
ws.title = "Invoice Schedule"
# Styles
header_font = Font(name="Arial", size=12, bold=True, color="003366")
col_header_font = Font(name="Arial", size=10, bold=True, color="FFFFFF")
col_header_fill = PatternFill(
start_color="003366", end_color="003366", fill_type="solid"
)
data_font = Font(name="Arial", size=10)
currency_format = "#,##0"
thin_border = Border(
left=Side(style="thin"),
right=Side(style="thin"),
top=Side(style="thin"),
bottom=Side(style="thin"),
)
# Title
ws.merge_cells("A1:G1")
ws["A1"] = f"{firm['name']} - Invoice Schedule"
ws["A1"].font = header_font
ws["A1"].alignment = Alignment(horizontal="center")
ws.merge_cells("A2:G2")
ws["A2"] = firm["address"]
ws["A2"].font = Font(name="Arial", size=8, color="666666")
ws["A2"].alignment = Alignment(horizontal="center")
# Column headers
headers = [
"Client",
"Matter Ref",
"TM Number",
"Description",
"Amount (HKD)",
"Due Date",
"Status",
]
col_widths = [25, 15, 14, 40, 15, 14, 12]
for i, (header, width) in enumerate(zip(headers, col_widths), start=1):
cell = ws.cell(row=4, column=i, value=header)
cell.font = col_header_font
cell.fill = col_header_fill
cell.alignment = Alignment(horizontal="center")
cell.border = thin_border
ws.column_dimensions[get_column_letter(i)].width = width
# Data rows
for row_idx, row_data in enumerate(rows, start=5):
ws.cell(row=row_idx, column=1, value=row_data["client"]).font = data_font
ws.cell(row=row_idx, column=2, value=row_data["matter_ref"]).font = data_font
ws.cell(row=row_idx, column=3, value=row_data["tm_number"]).font = data_font
ws.cell(row=row_idx, column=4, value=row_data["description"]).font = data_font
amount_cell = ws.cell(row=row_idx, column=5, value=row_data["amount_hkd"])
amount_cell.font = data_font
amount_cell.number_format = currency_format
amount_cell.alignment = Alignment(horizontal="right")
ws.cell(row=row_idx, column=6, value=row_data["due_date"]).font = data_font
status_cell = ws.cell(row=row_idx, column=7, value=row_data["status"])
status_cell.font = data_font
status_cell.alignment = Alignment(horizontal="center")
# Color-code status
status_colors = {
"Draft": "FFF3CD",
"Sent": "D1ECF1",
"Overdue": "F8D7DA",
"Paid": "D4EDDA",
}
if row_data["status"] in status_colors:
status_cell.fill = PatternFill(
start_color=status_colors[row_data["status"]],
end_color=status_colors[row_data["status"]],
fill_type="solid",
)
# Apply borders
for col in range(1, 8):
ws.cell(row=row_idx, column=col).border = thin_border
# Total row
total_row = 5 + len(rows)
ws.cell(row=total_row, column=4, value="TOTAL").font = Font(
name="Arial", size=10, bold=True
)
total_cell = ws.cell(
row=total_row,
column=5,
value=sum(r["amount_hkd"] for r in rows),
)
total_cell.font = Font(name="Arial", size=10, bold=True)
total_cell.number_format = currency_format
total_cell.alignment = Alignment(horizontal="right")
wb.save(str(output_path))
Binary file not shown.
Binary file not shown.
View File
+16
View File
@@ -0,0 +1,16 @@
@echo off
:: JingTian LLM Setup
:: Double-click this file to set up automatic file syncing.
:: Requires administrator privileges (will prompt for elevation).
:: Check for admin privileges
net session >nul 2>&1
if %errorlevel% neq 0 (
echo Requesting administrator privileges...
powershell -Command "Start-Process -Verb RunAs -FilePath '%~f0'"
exit /b
)
:: Run the setup script
powershell -ExecutionPolicy Bypass -File "%~dp0Code\Sync\Win-Setup.ps1" -SyncRoot "%~dp0Code\Sync"
pause