Metadata-Version: 2.4
Name: penquify
Version: 0.1.0
Summary: Synthetic logistics document & photo dataset generator
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: google-genai>=1.0
Requires-Dist: Pillow>=10.0
Requires-Dist: Jinja2>=3.1
Requires-Dist: playwright>=1.40
Provides-Extra: api
Requires-Dist: fastapi>=0.104; extra == "api"
Requires-Dist: uvicorn>=0.24; extra == "api"
Provides-Extra: db
Requires-Dist: psycopg2-binary>=2.9; extra == "db"
Requires-Dist: sqlalchemy>=2.0; extra == "db"
Provides-Extra: all
Requires-Dist: penquify[api,db]; extra == "all"
Dynamic: license-file

<div align="center">

# penquify

**OCR in reverse. Make your documents worse.**

<img src="https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExYnFsOGJ3ODRyMWFieXZwM3o1MnZzNnkyMjlncWg1N3hld2VuYWl4ZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/l2JecQs22YOIPMnoA/giphy.gif" width="220" alt="penquify"/>

A Python toolkit that generates photorealistic smartphone photos of logistics documents<br/>
with coffee stains, folds, blur, skew — and verified ground truth for every field.

[![GitHub stars](https://img.shields.io/github/stars/MAXMARDONES/penquify?style=flat-square&logo=github&color=114541)](https://github.com/MAXMARDONES/penquify/stargazers)
[![License](https://img.shields.io/github/license/MAXMARDONES/penquify?style=flat-square&color=114541)](LICENSE)
[![Last commit](https://img.shields.io/github/last-commit/MAXMARDONES/penquify?style=flat-square&color=114541)](https://github.com/MAXMARDONES/penquify/commits/main)
[![SmartUp](https://img.shields.io/badge/SmartUp-vibecheck%20pass-8B5CF6?style=flat-square)](https://getsmartup.ai)

<br/>

<img src="https://img.shields.io/badge/CLI-penquify-114541?style=flat-square&logo=gnubash&logoColor=white" alt="cli"/>
<img src="https://img.shields.io/badge/PyPI-pip%20install-3775A9?style=flat-square&logo=pypi&logoColor=white" alt="pypi"/>
<img src="https://img.shields.io/badge/REST-FastAPI-009688?style=flat-square&logo=fastapi&logoColor=white" alt="fastapi"/>
<img src="https://img.shields.io/badge/MCP-Server-F59E0B?style=flat-square" alt="mcp"/>
<img src="https://img.shields.io/badge/Agent%20SDK-Plugin-06B6D4?style=flat-square" alt="agent-sdk"/>
<img src="https://img.shields.io/badge/Gemini-Image%20Gen-8B5CF6?style=flat-square&logo=google" alt="gemini"/>
<img src="https://img.shields.io/badge/Ground%20Truth-Verified-22C55E?style=flat-square" alt="verified"/>

[penquify.com](https://penquify.com) · [Docs](https://docs.penquify.com) · [GitHub](https://github.com/MAXMARDONES/penquify)

<br/>

[![Open Source](https://img.shields.io/badge/Open%20Source-Self%20Host-22C55E?style=for-the-badge&logo=github)](https://github.com/MAXMARDONES/penquify)
[![AWS Ready](https://img.shields.io/badge/AWS-EKS%20Deploy-FF9900?style=for-the-badge&logo=amazon-web-services)](https://github.com/MAXMARDONES/penquify/tree/main/k8s)
[![Docker](https://img.shields.io/badge/Docker-One%20Command-2496ED?style=for-the-badge&logo=docker)](https://github.com/MAXMARDONES/penquify/blob/main/Dockerfile)
[![Hosted SaaS](https://img.shields.io/badge/Hosted-penquify.com-114541?style=for-the-badge)](https://penquify.com)

> *From Chilean slang **"penca"** (lousy, worse) — because your document photos should look realistically bad, not studio-perfect.*

</div>

---

## How it works

### You start with data. Penquify builds everything else.

```
ERP purchase order          penquify generates           penquify generates
(or any JSON/PDF)    ──►    dispatch guide PDF     ──►   realistic photos
                            with supplier jargon,        with verified
                            unit mismatches,             ground truth +
                            realistic discrepancies      occlusion manifest
```

You don't build the PDF. You don't design the document. You give penquify an OC number, a JSON payload, or upload an existing PDF — and it:

1. **Generates a realistic document** with supplier-style names (not your ERP master data names), realistic unit mismatches (CJ vs KG, UN vs L), and configurable quantity discrepancies
2. **Renders a clean PDF** from Jinja2 templates (dispatch guides, invoices, POs, BOLs)
3. **Produces N photorealistic photos** — each a different failure mode (blur, fold, stain, crop, angle)
4. **Verifies every field** by blind-extracting from the photo and comparing programmatically against source data
5. **Generates an occlusion manifest** explaining which fields are hidden in each variation and why

### Before → After

<div align="center">
<table>
<tr>
<td align="center"><strong>Clean PDF (auto-generated)</strong></td>
<td align="center"></td>
<td align="center"><strong>Realistic Photo (verified)</strong></td>
</tr>
<tr>
<td><img src="landing/assets/clean_document.png" width="280" alt="Clean PDF"/></td>
<td align="center">→</td>
<td><img src="landing/assets/ai_studio_photo_1.jpg" width="280" alt="Realistic photo"/></td>
</tr>
</table>
</div>

### Every variation from the same document

<div align="center">
<table>
<tr>
<td align="center"><img src="landing/assets/ai_studio_photo_1.jpg" width="200"/><br/><code>full_picture</code></td>
<td align="center"><img src="landing/assets/ai_studio_photo_3.jpg" width="200"/><br/><code>folded_skewed</code></td>
<td align="center"><img src="landing/assets/ai_studio_photo_5.jpg" width="200"/><br/><code>strong_oblique</code></td>
</tr>
<tr>
<td align="center"><img src="landing/assets/ai_studio_photo_7.jpg" width="200"/><br/><code>coffee_stain</code></td>
<td align="center"><img src="landing/assets/ai_studio_photo_8.jpg" width="200"/><br/><code>stain + angle</code></td>
<td align="center"><img src="landing/assets/ai_studio_photo_2.jpg" width="200"/><br/><code>galaxy_s7</code></td>
</tr>
</table>
</div>

8 built-in presets + infinite custom via JSON or natural language.

```bash
# From scratch — penquify generates the document AND the photos
penquify demo

# From an existing PDF — penquify detects the schema and generates variations
penquify upload --image existing_invoice.pdf

# From a description — no JSON needed
penquify config --text "folded paper with grease, shot on old Motorola"
```

---

## Getting Started

### Install

```bash
pip install penquify

# or from source
git clone https://github.com/MAXMARDONES/penquify.git
cd penquify && pip install -e ".[all]"

# browser engine for HTML → PDF rendering
playwright install chromium
```

### Environment

```bash
export GEMINI_API_KEY="your-key"   # required for photo generation
export PENQUIFY_OUTPUT="./output"  # where files go (default: ~/penquify-output)
```

### Run

```bash
# Full demo: PDF + 8 photo variations
penquify demo

# PDF only from JSON
penquify pdf --doc-json invoice.json

# Photos from any document image
penquify photos --image scan.png --presets full_picture blurry coffee_stain

# Full dataset: 10 documents x 3 variations each
penquify dataset --doc-json docs.json --presets full_picture folded_skewed blurry
```

### Python

```python
from penquify.models import Document, DocHeader, DocItem, PhotoVariation, Stain
from penquify.generators.pdf import generate_document_files
from penquify.generators.photo import generate_dataset

doc = Document(
    header=DocHeader(doc_type="guia_despacho", doc_number="00847291", date="16/04/2026",
                     emitter_name="ACME FOODS LTDA.", oc_number="4500000316"),
    items=[
        DocItem(pos=1, code="AF-001", description="FROZEN POTATO WEDGES",
                qty=12, unit="CJ", unit_price=15000, total=180000),
    ],
)

files = await generate_document_files(doc, "output/")
photos = await generate_dataset(files["png"], preset_names=["full_picture", "blurry"])
```

---

## Document Templates

| Template | Description | Status |
|---|---|---|
| `guia_despacho` | Chilean dispatch guide (guia de despacho electronica) | **Done** |
| `factura_sii` | Chilean tax invoice (DTE tipo 33, SII XML) | Planned |
| `purchase_order` | Standard purchase order | Planned |
| `bill_of_lading` | Transport bill of lading (BOL) | Planned |
| `nota_credito` | Credit note (DTE tipo 61) | Planned |
| `remito` | Argentine dispatch note | Planned |

Templates are **Jinja2 HTML** — add your own:

```bash
penquify pdf --template my_template.html --doc-json data.json
```

---

## Photo Variations

A fixed **system instruction** handles base realism (paper physics, camera behavior, operational context). The **variation config** controls specifics. Every field is optional — override only what you need.

### 8 Built-in Presets

| Preset | What it tests |
|---|---|
| `full_picture` | Baseline: clean handheld shot, 90% frame coverage |
| `folded_skewed` | Geometric distortion: dog-ear, crease, 6deg tilt |
| `zoomed_detail` | Close-up OCR: tight crop, oblique 25-30deg |
| `blurry` | Motion blur: rushed capture, partial legibility |
| `cropped_header` | Missing data: top 10-15% cut off |
| `strong_oblique` | Extreme angle: 45deg, strong curvature |
| `coffee_stain` | Contamination: stain over text |
| `stapled_stack` | Multi-page: stapled with sheets behind |

### Full Variation Schema

```json
{
  "name": "my_variation",
  "camera": "Samsung Galaxy S8",
  "year_device_style": "2017 Android",
  "aspect_ratio": "4:3",
  "document_coverage": "90% of frame",
  "background": "blurred warehouse at edges",
  "curvature": "slight",
  "folds": "dog_ear",
  "wrinkles": "medium",
  "angle": "45 degree oblique",
  "skew": "strong",
  "rotation_degrees": 8,
  "motion_blur": true,
  "glare": "strong",
  "shadow_from_hand": true,
  "jpeg_compression": "heavy",
  "hand_visible": true,
  "grip_type": "both hands",
  "glove": "warehouse glove",
  "stain": {"type": "coffee", "location": "upper_right", "opacity": "heavy", "text_obstruction": "partial"},
  "cropped_header": true,
  "stapled": true,
  "stacked_sheets_behind": 2
}
```

Every string field is **free text** — cameras, angles, backgrounds, grip types. Use presets or write whatever describes your scenario.

### 22 Camera Presets (+ free text)

`galaxy_s7` `galaxy_s8` `galaxy_a5_2017` `moto_g5` `iphone_7` `iphone_8` `pixel_2` `huawei_p10` `xiaomi_note4` `galaxy_s9` `iphone_xr` `galaxy_a10` `galaxy_a50` `iphone_11` `galaxy_a21s` `iphone_12` `pixel_4a` `galaxy_a13` `iphone_14` `pixel_7` `warehouse_generic` `field_worker`

Or any free text: `PhotoVariation(camera="Nokia 3310 with cracked screen")`

### Natural Language Config

Don't know the schema? Just describe it:

```python
from penquify.generators.config import text_to_variation

config = await text_to_variation(
    "blurry photo with coffee stain, strong angle, old Samsung, paper folded in half"
)
# → returns valid PhotoVariation JSON
```

---

## REST API

```bash
uvicorn penquify.api.server:app --port 8080
```

| Method | Path | Description |
|---|---|---|
| `POST` | `/generate/document` | Document JSON → PDF + PNG |
| `POST` | `/generate/photos` | Image → realistic photos |
| `POST` | `/generate/dataset` | Document → PDF → photos (full pipeline) |
| `POST` | `/generate/config` | Natural language → variation JSON |
| `GET` | `/documents` | List generated runs |
| `GET` | `/documents/{id}/{file}` | Download file |
| `GET` | `/presets` | Photo presets |
| `GET` | `/templates` | Document templates |

---

## MCP Server

5 tools for Claude Desktop, Cursor, Windsurf, or any MCP client:

```json
{
  "mcpServers": {
    "penquify": {
      "command": "python3",
      "args": ["-m", "penquify.mcp"],
      "env": {"GEMINI_API_KEY": "your-key"}
    }
  }
}
```

Tools: `penquify_generate_document` `penquify_generate_photos` `penquify_generate_dataset` `penquify_text_to_config` `penquify_list_presets`

---

## Claude Code Skills

```bash
/penquify          # Full reference: presets, cameras, variation schema
/generate          # Generate a document from description or JSON
/dataset           # Generate large synthetic datasets
/add-template      # Add a new document template
```

---

## Agent SDK Plugin

```python
from penquify.agent_plugin import penquify_tools

agent = Agent(model="claude-sonnet-4-6", tools=penquify_tools)
```

---

## Deployment

### Docker

```bash
docker build -t penquify .
docker run -p 8080:8080 -e GEMINI_API_KEY=xxx penquify
```

### docker-compose (with PostgreSQL)

```bash
GEMINI_API_KEY=xxx docker-compose up
```

### Kubernetes

```bash
kubectl apply -f k8s/secret.yaml   # set GEMINI_API_KEY first
kubectl apply -f k8s/deployment.yaml
```

---

## Architecture

```
penquify/
  templates/         Jinja2 HTML per doc type
  generators/
    pdf.py           HTML → PDF/PNG (Playwright)
    photo.py         PNG → realistic photo (Gemini image gen)
    config.py        text → variation JSON (Gemini text)
  models/
    document.py      DocHeader + DocItem + Document
    variation.py     PhotoVariation + Stain + 8 presets
    cameras.py       22 camera presets + free text
  api/server.py      FastAPI REST
  mcp.py             MCP server (5 tools)
  agent_plugin.py    Agent SDK plugin
  storage/s3.py      AWS S3 upload
  cli.py             CLI entry point
```

---

## Roadmap

- [x] Jinja2 templates + Playwright PDF/PNG
- [x] Gemini photo gen with system instruction + variation config
- [x] 8 photo presets + 22 camera presets
- [x] CLI (`penquify demo/pdf/photos/dataset`)
- [x] FastAPI REST server (8 endpoints)
- [x] MCP server (5 tools)
- [x] Agent SDK plugin
- [x] Claude Code skills (4 commands)
- [x] Natural language → variation JSON (Gemini)
- [x] S3 upload support
- [x] Dockerfile + docker-compose + K8s manifests
- [x] GitHub Actions CI
- [x] CODE_OF_CONDUCT + CONTRIBUTING + LICENSE
- [ ] PostgreSQL persistent storage
- [ ] PostgREST auto-API
- [ ] More templates: factura SII, PO, BOL
- [ ] SII DTE XML generation
- [ ] Batch dataset generation with progress bar
- [ ] PyPI publish
- [ ] Demo images in README

---

## License

MIT

---

<div align="center">

[penquify.com](https://penquify.com) | [Docs](https://docs.penquify.com) | [GitHub](https://github.com/MAXMARDONES/penquify)

<sub>Built by <a href="https://github.com/MAXMARDONES">Max Mardones</a></sub>
</div>
