docsynth 0.0.0__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docsynth-0.1.0/.env.example +92 -0
- docsynth-0.1.0/.github/CODEOWNERS +8 -0
- docsynth-0.1.0/.github/pull_request_template.md +76 -0
- docsynth-0.1.0/.github/workflows/ci.yml +52 -0
- docsynth-0.1.0/.github/workflows/docs.yml +29 -0
- docsynth-0.1.0/.github/workflows/release.yml +50 -0
- docsynth-0.1.0/.gitignore +70 -0
- docsynth-0.1.0/CONTRIBUTING.md +74 -0
- docsynth-0.1.0/Dockerfile +67 -0
- docsynth-0.1.0/LICENSE +21 -0
- docsynth-0.1.0/PKG-INFO +223 -0
- docsynth-0.1.0/README.md +166 -0
- docsynth-0.1.0/TODO.md +665 -0
- docsynth-0.1.0/deploy/gcp/deploy.sh +824 -0
- docsynth-0.1.0/deploy/gcp/run.example.yaml +239 -0
- docsynth-0.1.0/deploy/gcp/verify_coherence.py +65 -0
- docsynth-0.1.0/pyproject.toml +117 -0
- docsynth-0.1.0/samples/00_meta-sidebar-01_en-US_b2b_saas_USD_wm.pdf +0 -0
- docsynth-0.1.0/samples/01_meta-sidebar-01_en-CA_wholesale_CAD_mark-nologo.pdf +0 -0
- docsynth-0.1.0/samples/02_receipt-compact-01_en-CA_retail_CAD_mark.pdf +0 -0
- docsynth-0.1.0/samples/03_meta-sidebar-01_en-CA_wholesale_CAD_mark-nologo.pdf +0 -0
- docsynth-0.1.0/samples/04_receipt-compact-01_en-CA_auto_repair_CAD_mark.pdf +0 -0
- docsynth-0.1.0/samples/05_banner-header-06_en-CA_wholesale_CAD_mark.pdf +0 -0
- docsynth-0.1.0/samples/06_telecom-itemized-37_en-US_telecom_USD_plain.pdf +0 -0
- docsynth-0.1.0/samples/07_banner-header-06_en-CA_wholesale_CAD_mark.pdf +0 -0
- docsynth-0.1.0/samples/08_boxed-form-01_en-GB_ai_platform_GBP_mark.pdf +0 -0
- docsynth-0.1.0/samples/09_banner-header-06_en-GB_auto_repair_GBP_nologo.pdf +0 -0
- docsynth-0.1.0/samples/10_boxed-form-01_en-CA_auto_repair_CAD_mark-nologo.pdf +0 -0
- docsynth-0.1.0/samples/11_fullbleed-05_en-CA_auto_repair_CAD_mark.pdf +0 -0
- docsynth-0.1.0/samples/12_boxed-form-01_en-GB_retail_GBP_plain.pdf +0 -0
- docsynth-0.1.0/samples/13_fullbleed-05_en-GB_retail_GBP_mark.pdf +0 -0
- docsynth-0.1.0/samples/14_banner-header-06_en-GB_retail_GBP_mark-wm-nologo.pdf +0 -0
- docsynth-0.1.0/samples/15_banner-header-06_en-US_b2b_saas_USD_nologo.pdf +0 -0
- docsynth-0.1.0/samples/16_meta-sidebar-01_en-US_b2b_saas_USD_wm.pdf +0 -0
- docsynth-0.1.0/samples/17_receipt-compact-01_en-GB_retail_GBP_plain.pdf +0 -0
- docsynth-0.1.0/samples/18_fullbleed-05_en-GB_ai_platform_GBP_mark.pdf +0 -0
- docsynth-0.1.0/samples/19_receipt-compact-01_en-GB_retail_GBP_plain.pdf +0 -0
- docsynth-0.1.0/samples/20_boxed-form-01_en-CA_auto_repair_CAD_mark-nologo.pdf +0 -0
- docsynth-0.1.0/samples/21_banner-header-06_en-US_auto_repair_USD_mark.pdf +0 -0
- docsynth-0.1.0/samples/22_telecom-itemized-37_en-CA_telecom_CAD_mark.pdf +0 -0
- docsynth-0.1.0/samples/23_meta-sidebar-01_en-GB_auto_repair_GBP_plain.pdf +0 -0
- docsynth-0.1.0/samples/24_receipt-compact-01_en-US_b2b_saas_USD_mark.pdf +0 -0
- docsynth-0.1.0/samples/25_meta-sidebar-01_en-GB_ai_platform_GBP_mark.pdf +0 -0
- docsynth-0.1.0/samples/26_boxed-form-01_en-US_b2b_saas_USD_plain.pdf +0 -0
- docsynth-0.1.0/samples/27_boxed-form-01_en-US_b2b_saas_USD_plain.pdf +0 -0
- docsynth-0.1.0/samples/28_fullbleed-05_en-CA_retail_CAD_mark-nologo.pdf +0 -0
- docsynth-0.1.0/samples/29_receipt-compact-01_en-US_retail_USD_plain.pdf +0 -0
- docsynth-0.1.0/samples/30_meta-sidebar-01_fr-CA_wholesale_CAD_nologo.pdf +0 -0
- docsynth-0.1.0/samples/31_meta-sidebar-01_fr-CA_retail_CAD_plain.pdf +0 -0
- docsynth-0.1.0/samples/32_meta-sidebar-01_fr-FR_ai_platform_EUR_mark.pdf +0 -0
- docsynth-0.1.0/samples/33_meta-sidebar-01_fr-FR_ai_platform_EUR_mark.pdf +0 -0
- docsynth-0.1.0/samples/34_banner-header-06_fr-FR_accounting_EUR_mark-wm.pdf +0 -0
- docsynth-0.1.0/samples/35_banner-header-06_fr-FR_accounting_EUR_mark-wm.pdf +0 -0
- docsynth-0.1.0/samples/36_boxed-form-01_fr-FR_auto_repair_EUR_mark.pdf +0 -0
- docsynth-0.1.0/samples/37_boxed-form-01_fr-FR_auto_repair_EUR_mark.pdf +0 -0
- docsynth-0.1.0/samples/38_bundled-noto-serif_en-US.pdf +0 -0
- docsynth-0.1.0/samples/39_bundled-jetbrains-mono_en-US.pdf +0 -0
- docsynth-0.1.0/samples/40_telecom-contd_en_multipage.pdf +0 -0
- docsynth-0.1.0/samples/41_condition-clean.pdf +0 -0
- docsynth-0.1.0/samples/42_condition-light-scan.pdf +0 -0
- docsynth-0.1.0/samples/43_condition-heavy-scan.pdf +0 -0
- docsynth-0.1.0/samples/44_condition-handwritten.pdf +0 -0
- docsynth-0.1.0/samples/45_handwritten_fr_neat.pdf +0 -0
- docsynth-0.1.0/samples/46_handwritten_en_scrawl.pdf +0 -0
- docsynth-0.1.0/samples/47_handwritten_crisp.pdf +0 -0
- docsynth-0.1.0/samples/48_handwritten_goods-receipt.pdf +0 -0
- docsynth-0.1.0/samples/README.md +50 -0
- docsynth-0.1.0/scripts/smoke_catalogue.py +213 -0
- docsynth-0.1.0/site-docs/README.md +38 -0
- docsynth-0.1.0/site-docs/docs/architecture/concurrency.md +68 -0
- docsynth-0.1.0/site-docs/docs/architecture/contract.md +128 -0
- docsynth-0.1.0/site-docs/docs/architecture/data-model.md +47 -0
- docsynth-0.1.0/site-docs/docs/architecture/flows.md +74 -0
- docsynth-0.1.0/site-docs/docs/architecture/overview.md +100 -0
- docsynth-0.1.0/site-docs/docs/core/misc.md +60 -0
- docsynth-0.1.0/site-docs/docs/core/overview.md +51 -0
- docsynth-0.1.0/site-docs/docs/core/pipeline.md +83 -0
- docsynth-0.1.0/site-docs/docs/core/providers.md +77 -0
- docsynth-0.1.0/site-docs/docs/core/state.md +82 -0
- docsynth-0.1.0/site-docs/docs/core/storage.md +69 -0
- docsynth-0.1.0/site-docs/docs/design-decisions.md +97 -0
- docsynth-0.1.0/site-docs/docs/index.md +45 -0
- docsynth-0.1.0/site-docs/docs/invoice/catalogue.md +69 -0
- docsynth-0.1.0/site-docs/docs/invoice/composition.md +59 -0
- docsynth-0.1.0/site-docs/docs/invoice/content.md +64 -0
- docsynth-0.1.0/site-docs/docs/invoice/export.md +54 -0
- docsynth-0.1.0/site-docs/docs/invoice/generation.md +72 -0
- docsynth-0.1.0/site-docs/docs/invoice/overview.md +67 -0
- docsynth-0.1.0/site-docs/docs/invoice/reference.md +39 -0
- docsynth-0.1.0/site-docs/docs/invoice/rendering.md +64 -0
- docsynth-0.1.0/site-docs/docs/operations/deploy-tool.md +60 -0
- docsynth-0.1.0/site-docs/docs/operations/docs-site.md +38 -0
- docsynth-0.1.0/site-docs/docs/operations/gcp.md +53 -0
- docsynth-0.1.0/site-docs/docs/operations/publishing.md +79 -0
- docsynth-0.1.0/site-docs/docs/operations/running.md +51 -0
- docsynth-0.1.0/site-docs/docs/quickstart.md +106 -0
- docsynth-0.1.0/site-docs/docs/studio/catalogue.md +58 -0
- docsynth-0.1.0/site-docs/docs/studio/dispatch.md +43 -0
- docsynth-0.1.0/site-docs/docs/studio/overview.md +58 -0
- docsynth-0.1.0/site-docs/docs/studio/projects.md +52 -0
- docsynth-0.1.0/site-docs/docs/studio/steps.md +45 -0
- docsynth-0.1.0/site-docs/docs/studio/tuning.md +50 -0
- docsynth-0.1.0/site-docs/mkdocs.yml +96 -0
- docsynth-0.1.0/site-docs/requirements.txt +3 -0
- docsynth-0.1.0/src/docsynth/__init__.py +16 -0
- docsynth-0.1.0/src/docsynth/__main__.py +10 -0
- docsynth-0.1.0/src/docsynth/cli.py +1134 -0
- docsynth-0.1.0/src/docsynth/core/__init__.py +55 -0
- docsynth-0.1.0/src/docsynth/core/content.py +137 -0
- docsynth-0.1.0/src/docsynth/core/enums.py +62 -0
- docsynth-0.1.0/src/docsynth/core/fonts/ATTRIBUTION.md +38 -0
- docsynth-0.1.0/src/docsynth/core/fonts/OFL.txt +109 -0
- docsynth-0.1.0/src/docsynth/core/fonts/__init__.py +187 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/hand-casual-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/hand-neat-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/hand-print-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/hand-scrawl-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/mono-invoice-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/mono-invoice-700.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/sans-neutral-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/sans-neutral-700.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/serif-classic-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/serif-classic-700.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/signature-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/slab-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/slab-700.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/fonts/files/stamp-400.woff2 +0 -0
- docsynth-0.1.0/src/docsynth/core/locale/__init__.py +34 -0
- docsynth-0.1.0/src/docsynth/core/locale/enums.py +67 -0
- docsynth-0.1.0/src/docsynth/core/locale/formatting.py +201 -0
- docsynth-0.1.0/src/docsynth/core/locale/labels.py +90 -0
- docsynth-0.1.0/src/docsynth/core/logging.py +179 -0
- docsynth-0.1.0/src/docsynth/core/money.py +42 -0
- docsynth-0.1.0/src/docsynth/core/pack.py +143 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/__init__.py +51 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/degrade.py +180 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/export.py +72 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/golden.py +69 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/manifest.py +414 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/pdf.py +309 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/planner.py +36 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/renderer.py +50 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/run.py +184 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/source.py +64 -0
- docsynth-0.1.0/src/docsynth/core/pipeline/worker.py +162 -0
- docsynth-0.1.0/src/docsynth/core/providers/__init__.py +50 -0
- docsynth-0.1.0/src/docsynth/core/providers/anthropic_provider.py +178 -0
- docsynth-0.1.0/src/docsynth/core/providers/base.py +80 -0
- docsynth-0.1.0/src/docsynth/core/providers/budget.py +210 -0
- docsynth-0.1.0/src/docsynth/core/providers/catalogue_runner.py +292 -0
- docsynth-0.1.0/src/docsynth/core/providers/factory.py +112 -0
- docsynth-0.1.0/src/docsynth/core/providers/mix.py +196 -0
- docsynth-0.1.0/src/docsynth/core/providers/openai_compatible.py +153 -0
- docsynth-0.1.0/src/docsynth/core/providers/pricing.py +69 -0
- docsynth-0.1.0/src/docsynth/core/record.py +47 -0
- docsynth-0.1.0/src/docsynth/core/registry.py +73 -0
- docsynth-0.1.0/src/docsynth/core/render/__init__.py +5 -0
- docsynth-0.1.0/src/docsynth/core/render/environment.py +115 -0
- docsynth-0.1.0/src/docsynth/core/selection.py +272 -0
- docsynth-0.1.0/src/docsynth/core/sinks/__init__.py +62 -0
- docsynth-0.1.0/src/docsynth/core/sinks/arrow.py +85 -0
- docsynth-0.1.0/src/docsynth/core/sinks/base.py +41 -0
- docsynth-0.1.0/src/docsynth/core/sinks/bigquery.py +89 -0
- docsynth-0.1.0/src/docsynth/core/sinks/duckdb_sink.py +67 -0
- docsynth-0.1.0/src/docsynth/core/sinks/parquet.py +54 -0
- docsynth-0.1.0/src/docsynth/core/state/__init__.py +50 -0
- docsynth-0.1.0/src/docsynth/core/state/base.py +274 -0
- docsynth-0.1.0/src/docsynth/core/state/dynamodb.py +500 -0
- docsynth-0.1.0/src/docsynth/core/state/firestore.py +434 -0
- docsynth-0.1.0/src/docsynth/core/state/sqlite.py +361 -0
- docsynth-0.1.0/src/docsynth/core/storage/__init__.py +50 -0
- docsynth-0.1.0/src/docsynth/core/storage/base.py +50 -0
- docsynth-0.1.0/src/docsynth/core/storage/gcs.py +67 -0
- docsynth-0.1.0/src/docsynth/core/storage/local.py +68 -0
- docsynth-0.1.0/src/docsynth/core/storage/s3.py +81 -0
- docsynth-0.1.0/src/docsynth/core/usage/__init__.py +107 -0
- docsynth-0.1.0/src/docsynth/core/usage/base.py +236 -0
- docsynth-0.1.0/src/docsynth/core/usage/dynamodb.py +113 -0
- docsynth-0.1.0/src/docsynth/core/usage/firestore.py +106 -0
- docsynth-0.1.0/src/docsynth/core/usage/shard.py +73 -0
- docsynth-0.1.0/src/docsynth/packs/__init__.py +13 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/__init__.py +205 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/artifact.py +567 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/build_run.py +295 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/catalog.py +489 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/composition.py +228 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/context.py +183 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/enums.py +271 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/handwriting.py +347 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/jurisdictions.py +177 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/labels.py +286 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/llm_build.py +464 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/logos.py +112 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/procedural.py +960 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/record.py +614 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/sampler.py +414 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/stamps.py +324 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/_base.html.j2 +41 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/_macros.j2 +224 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/_styles.css.j2 +190 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/banner-header-06.html.j2 +87 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/boxed-form-01.html.j2 +82 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/fullbleed-05.html.j2 +86 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/handwritten-form-01.html.j2 +298 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/meta-sidebar-01.html.j2 +19 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/receipt-compact-01.html.j2 +84 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/templates/archetypes/telecom-itemized-37.html.j2 +95 -0
- docsynth-0.1.0/src/docsynth/packs/invoice/validation.py +240 -0
- docsynth-0.1.0/src/docsynth/studio/__init__.py +42 -0
- docsynth-0.1.0/src/docsynth/studio/app.py +109 -0
- docsynth-0.1.0/src/docsynth/studio/gcp.py +275 -0
- docsynth-0.1.0/src/docsynth/studio/local.py +202 -0
- docsynth-0.1.0/src/docsynth/studio/mixes.py +105 -0
- docsynth-0.1.0/src/docsynth/studio/progress.py +72 -0
- docsynth-0.1.0/src/docsynth/studio/prompts.py +137 -0
- docsynth-0.1.0/src/docsynth/studio/registry.py +116 -0
- docsynth-0.1.0/src/docsynth/studio/targets.py +39 -0
- docsynth-0.1.0/src/docsynth/studio/types.py +152 -0
- docsynth-0.1.0/src/docsynth/studio/wizard.py +280 -0
- docsynth-0.1.0/templates/manifest.yaml +260 -0
- docsynth-0.1.0/tests/factories.py +190 -0
- docsynth-0.1.0/tests/fakes.py +100 -0
- docsynth-0.1.0/tests/test_archetypes.py +226 -0
- docsynth-0.1.0/tests/test_bigquery.py +80 -0
- docsynth-0.1.0/tests/test_bigquery_integration.py +62 -0
- docsynth-0.1.0/tests/test_build_run.py +396 -0
- docsynth-0.1.0/tests/test_catalogue_artifact.py +245 -0
- docsynth-0.1.0/tests/test_catalogue_runner.py +478 -0
- docsynth-0.1.0/tests/test_cli.py +489 -0
- docsynth-0.1.0/tests/test_cloud_storage.py +81 -0
- docsynth-0.1.0/tests/test_content.py +130 -0
- docsynth-0.1.0/tests/test_degrade.py +181 -0
- docsynth-0.1.0/tests/test_dynamodb.py +401 -0
- docsynth-0.1.0/tests/test_export.py +94 -0
- docsynth-0.1.0/tests/test_firestore.py +399 -0
- docsynth-0.1.0/tests/test_fonts.py +132 -0
- docsynth-0.1.0/tests/test_gcs_emulator.py +68 -0
- docsynth-0.1.0/tests/test_goods_receipt.py +172 -0
- docsynth-0.1.0/tests/test_handwriting.py +225 -0
- docsynth-0.1.0/tests/test_i18n.py +164 -0
- docsynth-0.1.0/tests/test_llm_build.py +335 -0
- docsynth-0.1.0/tests/test_logging.py +244 -0
- docsynth-0.1.0/tests/test_logos.py +59 -0
- docsynth-0.1.0/tests/test_pack.py +200 -0
- docsynth-0.1.0/tests/test_pdf.py +316 -0
- docsynth-0.1.0/tests/test_pipeline.py +274 -0
- docsynth-0.1.0/tests/test_procedural_catalogue.py +246 -0
- docsynth-0.1.0/tests/test_providers.py +488 -0
- docsynth-0.1.0/tests/test_render.py +303 -0
- docsynth-0.1.0/tests/test_run_manifest.py +229 -0
- docsynth-0.1.0/tests/test_run_planning.py +209 -0
- docsynth-0.1.0/tests/test_sampler.py +216 -0
- docsynth-0.1.0/tests/test_schema.py +357 -0
- docsynth-0.1.0/tests/test_selection.py +399 -0
- docsynth-0.1.0/tests/test_sinks.py +163 -0
- docsynth-0.1.0/tests/test_spend_rollup.py +261 -0
- docsynth-0.1.0/tests/test_stamps.py +125 -0
- docsynth-0.1.0/tests/test_state.py +275 -0
- docsynth-0.1.0/tests/test_storage.py +70 -0
- docsynth-0.1.0/tests/test_studio.py +923 -0
- docsynth-0.1.0/tests/test_usage.py +264 -0
- docsynth-0.1.0/tests/test_usage_adapters.py +208 -0
- docsynth-0.1.0/tests/test_validation.py +189 -0
- docsynth-0.0.0/PKG-INFO +0 -5
- docsynth-0.0.0/docsynth/__init__.py +0 -1
- docsynth-0.0.0/pyproject.toml +0 -12
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# docsynth — environment configuration
|
|
2
|
+
#
|
|
3
|
+
# cp .env.example .env then fill in the blanks.
|
|
4
|
+
#
|
|
5
|
+
# .env is gitignored. Never commit real credentials.
|
|
6
|
+
# Values marked [required] must be set before the relevant mode will start;
|
|
7
|
+
# startup validation fails fast rather than erroring mid-run.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
11
|
+
# LLM providers — catalogue generation (Step 2, one-time)
|
|
12
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
# DeepSeek V4-Flash — 40% of catalogue. https://platform.deepseek.com
|
|
15
|
+
DEEPSEEK_API_KEY=
|
|
16
|
+
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
|
17
|
+
DEEPSEEK_MODEL=deepseek-v4-flash
|
|
18
|
+
|
|
19
|
+
# Qwen3.5-Flash via Alibaba DashScope — 40% of catalogue.
|
|
20
|
+
# Use the Singapore endpoint unless your account is China-mainland.
|
|
21
|
+
DASHSCOPE_API_KEY=
|
|
22
|
+
DASHSCOPE_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1
|
|
23
|
+
DASHSCOPE_MODEL=qwen3.5-flash
|
|
24
|
+
|
|
25
|
+
# Anthropic Claude Haiku 4.5 — 20% of catalogue. https://console.anthropic.com
|
|
26
|
+
# Leave ANTHROPIC_API_KEY blank to fall back to an `ant auth login` profile.
|
|
27
|
+
ANTHROPIC_API_KEY=
|
|
28
|
+
ANTHROPIC_MODEL=claude-haiku-4-5
|
|
29
|
+
# Batches API halves cost; catalogue generation is offline so keep this on.
|
|
30
|
+
ANTHROPIC_USE_BATCH=true
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
34
|
+
# Logo generation (Step 2b) — abstract marks only; wordmarks are programmatic
|
|
35
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
+
|
|
37
|
+
# Black Forest Labs FLUX 2 Pro. https://api.bfl.ai
|
|
38
|
+
BFL_API_KEY=
|
|
39
|
+
BFL_MODEL=flux-2-pro
|
|
40
|
+
LOGO_ATTEMPTS_PER_COMPANY=3
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
# Google Cloud
|
|
45
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
46
|
+
|
|
47
|
+
GOOGLE_CLOUD_PROJECT= # [required] GCP project ID
|
|
48
|
+
GCP_REGION=us-central1
|
|
49
|
+
|
|
50
|
+
# Local dev only. On Cloud Run, leave blank and use the attached service account.
|
|
51
|
+
GOOGLE_APPLICATION_CREDENTIALS=
|
|
52
|
+
|
|
53
|
+
GCS_BUCKET= # [required] e.g. docsynth-artifacts
|
|
54
|
+
FIRESTORE_DATABASE=(default)
|
|
55
|
+
BQ_DATASET=docsynth_golden # external tables land here
|
|
56
|
+
|
|
57
|
+
# Artifact Registry path for the container image
|
|
58
|
+
ARTIFACT_REGISTRY= # e.g. us-central1-docker.pkg.dev/PROJECT/docsynth
|
|
59
|
+
CLOUD_RUN_SERVICE=docsynth-generate
|
|
60
|
+
CLOUD_RUN_JOB=docsynth-export
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
64
|
+
# Run behaviour
|
|
65
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
66
|
+
|
|
67
|
+
CATALOG_VERSION=v1 # pinned per run; bump = regeneration
|
|
68
|
+
WORK_UNIT_SIZE=1000 # invoices per task / per JSONL shard
|
|
69
|
+
RENDER_CONCURRENCY=8 # parallel Chromium pages per instance
|
|
70
|
+
MAX_LINE_ITEMS=8000 # hard cap — validation error, not a timeout
|
|
71
|
+
LARGE_INVOICE_THRESHOLD=250 # above this, route to the slow queue
|
|
72
|
+
|
|
73
|
+
# Playwright resolves its own bundled Chromium if left blank.
|
|
74
|
+
CHROMIUM_PATH=
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
+
# Budget guards — generation aborts rather than silently overrunning
|
|
79
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
80
|
+
|
|
81
|
+
CATALOG_BUDGET_USD=50.00
|
|
82
|
+
LOGO_BUDGET_USD=25.00
|
|
83
|
+
TEST_BUDGET_USD=10.00 # separate pot for smoke runs
|
|
84
|
+
ABORT_ON_BUDGET_EXCEEDED=true
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
88
|
+
# Observability
|
|
89
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
90
|
+
|
|
91
|
+
LOG_LEVEL=INFO
|
|
92
|
+
LOG_FORMAT=json # json | console
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Code owners for docsynth.
|
|
2
|
+
# https://docs.github.com/articles/about-code-owners
|
|
3
|
+
#
|
|
4
|
+
# @kashodev owns everything in this repo by default — every path, one owner.
|
|
5
|
+
# Add more specific rules below the global one if that ever changes (later rules
|
|
6
|
+
# win over earlier ones for a matching path).
|
|
7
|
+
|
|
8
|
+
* @kashodev
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Fill in every section that applies and delete the ones that do not. A section
|
|
3
|
+
left as an unedited prompt is worse than a deleted one — it reads as answered
|
|
4
|
+
when it isn't.
|
|
5
|
+
|
|
6
|
+
Keep it honest: if something is untested, partially done, or a known trade-off,
|
|
7
|
+
say so here rather than letting a reviewer discover it.
|
|
8
|
+
-->
|
|
9
|
+
|
|
10
|
+
## What
|
|
11
|
+
|
|
12
|
+
<!-- A high-level summary of the change and its scope. What does this PR do, and
|
|
13
|
+
just as importantly, what is deliberately *not* in it? -->
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
<!-- The problem being solved and why this solution. Link the ticket/issue.
|
|
18
|
+
If this is a fix, describe the failure it addresses — ideally the symptom a
|
|
19
|
+
user or a test would see. If a previous approach was replaced, say what was
|
|
20
|
+
wrong with it. -->
|
|
21
|
+
|
|
22
|
+
Closes #
|
|
23
|
+
|
|
24
|
+
## How
|
|
25
|
+
|
|
26
|
+
<!-- High-level implementation notes: the shape of the change, key design
|
|
27
|
+
decisions, and the trade-offs behind them.
|
|
28
|
+
|
|
29
|
+
- Alternatives considered, and why they were rejected.
|
|
30
|
+
- Anything that constrains the design (a protocol, determinism, the exactness
|
|
31
|
+
of the golden data, a licence).
|
|
32
|
+
- Follow-ups deliberately left out of scope (link the TODO entry). -->
|
|
33
|
+
|
|
34
|
+
## Testing
|
|
35
|
+
|
|
36
|
+
<!-- How the change was verified. Be specific enough that a reviewer could repeat it.
|
|
37
|
+
|
|
38
|
+
- New/changed automated tests and what property each pins down.
|
|
39
|
+
- Full-suite result (e.g. `NNN passed, N skipped`).
|
|
40
|
+
- Manual verification steps, with the commands used.
|
|
41
|
+
- Anything gated/skipped in CI (emulators, Chromium, cloud creds) and how to
|
|
42
|
+
run it locally.
|
|
43
|
+
- What is *not* covered, and why. -->
|
|
44
|
+
|
|
45
|
+
- [ ] Full test suite passes locally
|
|
46
|
+
- [ ] New behaviour has tests that fail without the change
|
|
47
|
+
|
|
48
|
+
## Visuals
|
|
49
|
+
|
|
50
|
+
<!-- Rendering or UI changes: before/after images. For generated documents,
|
|
51
|
+
attach or reference the sample files that demonstrate the change.
|
|
52
|
+
For complex features, a diagram of the flow or data model.
|
|
53
|
+
Delete this section if the change has no visible output. -->
|
|
54
|
+
|
|
55
|
+
| Before | After |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| | |
|
|
58
|
+
|
|
59
|
+
## Reviewer focus
|
|
60
|
+
|
|
61
|
+
<!-- Direct attention where it is most needed.
|
|
62
|
+
|
|
63
|
+
- The files/functions with the real complexity, and what to check about them.
|
|
64
|
+
- Assumptions worth challenging.
|
|
65
|
+
- What can be skimmed (mechanical renames, generated files, moved code with
|
|
66
|
+
no behaviour change).
|
|
67
|
+
- Any part you are unsure about and want a second opinion on. -->
|
|
68
|
+
|
|
69
|
+
## Risk & rollout
|
|
70
|
+
|
|
71
|
+
<!-- Delete if not applicable.
|
|
72
|
+
|
|
73
|
+
- Backwards compatibility: schema/protocol/config changes, migrations.
|
|
74
|
+
- Effect on existing runs or already-generated data.
|
|
75
|
+
- New dependencies and their licences.
|
|
76
|
+
- How to roll back if this goes wrong. -->
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
# Runs the test suite on every push to main and every PR, and lints the Python
|
|
4
|
+
# files a PR changed. Chromium- and emulator-backed tests skip themselves when
|
|
5
|
+
# those services aren't present, and the cloud extras aren't installed, so the
|
|
6
|
+
# core suite runs green on a plain runner.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
pull_request:
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
test:
|
|
21
|
+
name: test (py${{ matrix.python-version }})
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
python-version: ["3.12", "3.13"]
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
cache: pip
|
|
33
|
+
cache-dependency-path: pyproject.toml
|
|
34
|
+
- run: pip install -e '.[dev]'
|
|
35
|
+
# `python -m pytest` (not bare `pytest`) puts the repo root on sys.path, which
|
|
36
|
+
# the suite needs — some tests import helpers via `from tests.… import …`.
|
|
37
|
+
- run: python -m pytest -q
|
|
38
|
+
|
|
39
|
+
lint:
|
|
40
|
+
name: ruff
|
|
41
|
+
# Repo-wide gate: the pre-existing lint debt was cleared, so the whole tree
|
|
42
|
+
# must stay clean. Rule scoping lives in pyproject's [tool.ruff.lint].
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
- uses: actions/setup-python@v5
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.12"
|
|
49
|
+
# Pinned so a new ruff release can't fail the repo-wide gate on rules the
|
|
50
|
+
# tree was never checked against. Bump deliberately.
|
|
51
|
+
- run: pip install ruff==0.16.0
|
|
52
|
+
- run: ruff check .
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
# Build the mkdocs-material site and deploy it to GitHub Pages (the gh-pages
|
|
4
|
+
# branch) whenever the docs change on main. Enable Pages once in
|
|
5
|
+
# Settings → Pages → Deploy from branch: gh-pages.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
paths:
|
|
10
|
+
- "site-docs/**"
|
|
11
|
+
- ".github/workflows/docs.yml"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.x"
|
|
27
|
+
- run: pip install -r site-docs/requirements.txt
|
|
28
|
+
- name: Build & deploy to GitHub Pages
|
|
29
|
+
run: mkdocs gh-deploy --force --config-file site-docs/mkdocs.yml
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI via Trusted Publishing (OIDC) — no stored API token.
|
|
4
|
+
# Fires when a GitHub Release is published; build the sdist+wheel and upload.
|
|
5
|
+
# One-time PyPI + GitHub setup is documented in
|
|
6
|
+
# site-docs/docs/operations/publishing.md.
|
|
7
|
+
on:
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- name: Tag must match [project].version
|
|
23
|
+
run: |
|
|
24
|
+
ver=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
25
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
26
|
+
if [ "$ver" != "$tag" ]; then
|
|
27
|
+
echo "release tag '$tag' != pyproject version '$ver'"; exit 1
|
|
28
|
+
fi
|
|
29
|
+
echo "publishing version $ver"
|
|
30
|
+
- run: pip install build
|
|
31
|
+
- run: python -m build
|
|
32
|
+
- run: pipx run twine check dist/*
|
|
33
|
+
- uses: actions/upload-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
|
|
38
|
+
publish:
|
|
39
|
+
needs: build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
# The trusted publisher on PyPI is scoped to this environment.
|
|
42
|
+
environment: pypi
|
|
43
|
+
permissions:
|
|
44
|
+
id-token: write # mint the OIDC token PyPI verifies
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/download-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
50
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# ─── Secrets ────────────────────────────────────────────────────────────────
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
*-service-account*.json
|
|
6
|
+
gcp-credentials*.json
|
|
7
|
+
|
|
8
|
+
# ─── Design scratch (never committed) ───────────────────────────────────────
|
|
9
|
+
# Feature-exploration write-ups: thinking-in-progress, superseded as soon as the
|
|
10
|
+
# decision lands in DESIGN.md / docs/. Keeping them out of git stops stale
|
|
11
|
+
# analysis being mistaken for the current design.
|
|
12
|
+
feature_explorations/
|
|
13
|
+
|
|
14
|
+
# ─── Reference material (never committed) ───────────────────────────────────
|
|
15
|
+
# Third-party images used only as visual reference while building the
|
|
16
|
+
# procedural equivalents. Not ours to redistribute, and not needed to build.
|
|
17
|
+
templates/stamps/
|
|
18
|
+
|
|
19
|
+
# ─── Generated artefacts ────────────────────────────────────────────────────
|
|
20
|
+
out/
|
|
21
|
+
catalog/
|
|
22
|
+
*.parquet
|
|
23
|
+
*.jsonl
|
|
24
|
+
*.jsonl.gz
|
|
25
|
+
|
|
26
|
+
# ─── Python ─────────────────────────────────────────────────────────────────
|
|
27
|
+
__pycache__/
|
|
28
|
+
*.py[cod]
|
|
29
|
+
.venv/
|
|
30
|
+
venv/
|
|
31
|
+
.mypy_cache/
|
|
32
|
+
.ruff_cache/
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
*.egg-info/
|
|
35
|
+
dist/
|
|
36
|
+
build/
|
|
37
|
+
.coverage
|
|
38
|
+
htmlcov/
|
|
39
|
+
|
|
40
|
+
# ─── Tooling / OS ───────────────────────────────────────────────────────────
|
|
41
|
+
.DS_Store
|
|
42
|
+
.idea/
|
|
43
|
+
.vscode/
|
|
44
|
+
|
|
45
|
+
# Belt-and-suspenders: ignore PDFs anywhere by default. The original reason was
|
|
46
|
+
# the source templates, which carried real personal data and have since been
|
|
47
|
+
# removed from the repo — but the blanket rule stays, so a stray real-world PDF
|
|
48
|
+
# dropped anywhere in the tree can never be committed by accident.
|
|
49
|
+
# Case-insensitive matching is on by default on macOS but NOT on Linux, so the
|
|
50
|
+
# explicit glob protects a Linux clone too.
|
|
51
|
+
*.pdf
|
|
52
|
+
*.Pdf
|
|
53
|
+
*.PDF
|
|
54
|
+
|
|
55
|
+
# ...with one deliberate exception: samples/ holds generated showcase documents.
|
|
56
|
+
# They are synthetic end to end — procedural catalogue data, no personal data —
|
|
57
|
+
# and are committed so the rendering can be reviewed without running a job.
|
|
58
|
+
!samples/**/*.pdf
|
|
59
|
+
!samples/**/*.PDF
|
|
60
|
+
|
|
61
|
+
# Real run configurations. `run.example.yaml` is the committed template; every
|
|
62
|
+
# other run.yaml is one operator's actual run — project ids, bucket names, doc
|
|
63
|
+
# counts and secret *names* — which is noise in review at best and a leak at
|
|
64
|
+
# worst. Copy the example, never commit the copy.
|
|
65
|
+
run.yaml
|
|
66
|
+
run.*.yaml
|
|
67
|
+
!run.example.yaml
|
|
68
|
+
|
|
69
|
+
# mkdocs build output for the docs site
|
|
70
|
+
site-docs/site/
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Branching
|
|
4
|
+
|
|
5
|
+
`main` is protected — it does not accept direct pushes. **All work goes on a
|
|
6
|
+
branch and lands through a pull request**, however small.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git switch -c <type>/<short-description> # branch from an up-to-date main
|
|
10
|
+
# ...work, commit...
|
|
11
|
+
git push -u origin <branch>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Branch names are `<type>/<short-description>`, where type is one of:
|
|
15
|
+
|
|
16
|
+
| Type | For |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `feat/` | new capability |
|
|
19
|
+
| `fix/` | a defect |
|
|
20
|
+
| `refactor/` | restructuring with no behaviour change |
|
|
21
|
+
| `docs/` | documentation only |
|
|
22
|
+
| `chore/` | tooling, packaging, housekeeping |
|
|
23
|
+
| `test/` | tests only |
|
|
24
|
+
|
|
25
|
+
One PR per logical change. Two unrelated changes on one branch make review
|
|
26
|
+
harder and a revert riskier — branch twice instead.
|
|
27
|
+
|
|
28
|
+
## Pull requests
|
|
29
|
+
|
|
30
|
+
[`.github/pull_request_template.md`](.github/pull_request_template.md) is applied
|
|
31
|
+
automatically. Fill in every section that applies and **delete the ones that do
|
|
32
|
+
not** — a section left as an unedited prompt reads as answered when it isn't.
|
|
33
|
+
|
|
34
|
+
The template asks for the *What*, *Why*, *How* (including trade-offs and
|
|
35
|
+
alternatives), *Testing*, *Visuals*, *Reviewer focus*, and *Risk & rollout*. The
|
|
36
|
+
two that most often get skipped and matter most:
|
|
37
|
+
|
|
38
|
+
- **How** — the reasoning, not a restatement of the diff. A reviewer can read the
|
|
39
|
+
code; they cannot read why you rejected the other approach.
|
|
40
|
+
- **Reviewer focus** — say what to scrutinise and what to skim. A 900-line diff
|
|
41
|
+
that is 850 lines of moved code deserves that note.
|
|
42
|
+
|
|
43
|
+
Be honest in the PR body about anything untested, partially done, or knowingly
|
|
44
|
+
traded off. It is much cheaper to flag it than to have a reviewer find it.
|
|
45
|
+
|
|
46
|
+
## Before opening a PR
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
PYTHONPATH=src pytest -q # full suite
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- New behaviour needs a test that fails without the change.
|
|
53
|
+
- Some tests are gated and skip without their dependency (Chromium, the
|
|
54
|
+
Firestore/GCS emulators, boto3/moto, a real BigQuery project). If your change
|
|
55
|
+
touches those paths, run them locally and say so in *Testing*.
|
|
56
|
+
- Rendering changes: regenerate the affected files in `samples/` and reference
|
|
57
|
+
them in *Visuals*.
|
|
58
|
+
|
|
59
|
+
## Things that will fail review
|
|
60
|
+
|
|
61
|
+
- Committing anything with real personal data. PDFs are gitignored by default;
|
|
62
|
+
the `samples/` exception exists only because that content is wholly synthetic.
|
|
63
|
+
- Committing third-party reference material (see `templates/stamps/`).
|
|
64
|
+
- Changing a golden value. The record is the ground truth an extraction pipeline
|
|
65
|
+
is scored against — rendering may change freely, the computed figures may not.
|
|
66
|
+
- Changing how the RNG is consumed **without saying so in the PR**. Drawing an
|
|
67
|
+
extra value, drawing in a different order, or drawing a different number of
|
|
68
|
+
values shifts every subsequent draw, so every document changes for a given
|
|
69
|
+
`(run_id, index)`. That is sometimes the right thing to do — making line items
|
|
70
|
+
distinct was — but it means a run cannot be resumed across the change, and an
|
|
71
|
+
old run id no longer reproduces its old corpus. Call it out explicitly under
|
|
72
|
+
*Risk & rollout* so a reviewer can weigh it — reproducibility is per code
|
|
73
|
+
version, not absolute (see the docs site → Architecture → Concurrency &
|
|
74
|
+
coordination).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# docsynth container.
|
|
2
|
+
#
|
|
3
|
+
# NOT required to try docsynth. The local-first path — filesystem storage,
|
|
4
|
+
# SQLite state, Parquet + DuckDB golden — runs from a plain `pip install docsynth`
|
|
5
|
+
# with no container and no cloud account.
|
|
6
|
+
#
|
|
7
|
+
# This image exists for the two things that genuinely benefit from one:
|
|
8
|
+
#
|
|
9
|
+
# 1. Reproducible rendering. PDF generation drives headless Chromium via
|
|
10
|
+
# Playwright, whose output is sensitive to browser and font versions.
|
|
11
|
+
# Pinning them in an image makes 250k documents byte-reproducible across
|
|
12
|
+
# machines. The Playwright base image ships a matched Chromium.
|
|
13
|
+
#
|
|
14
|
+
# 2. The Cloud Run deploy artifact (GCP path). The same image runs as the
|
|
15
|
+
# generation Service and the export Job — one build, two entry commands.
|
|
16
|
+
#
|
|
17
|
+
# Build: docker build -t docsynth .
|
|
18
|
+
# Local: docker run --rm -v "$PWD/out:/data" docsynth generate --config ...
|
|
19
|
+
# Cloud: pushed to Artifact Registry, deployed to Cloud Run (see deploy.sh).
|
|
20
|
+
|
|
21
|
+
# The base image bundles a Chromium build matched to ONE Playwright version, and
|
|
22
|
+
# the pip package MUST be that exact version or launch fails with
|
|
23
|
+
# "Executable doesn't exist". They are declared together here, from a single arg,
|
|
24
|
+
# so they can never drift — an unpinned `playwright>=…` floating to a newer
|
|
25
|
+
# release than the base image is what silently broke every rendered document.
|
|
26
|
+
# Bump both by bumping this one number (and picking the matching `vX.Y.Z-noble`).
|
|
27
|
+
ARG PLAYWRIGHT_VERSION=1.61.0
|
|
28
|
+
FROM mcr.microsoft.com/playwright/python:v1.61.0-noble
|
|
29
|
+
ARG PLAYWRIGHT_VERSION
|
|
30
|
+
|
|
31
|
+
# Stream stdout/stderr immediately, so a long job's progress reaches Cloud
|
|
32
|
+
# Logging as it happens rather than only when the process exits.
|
|
33
|
+
ENV PYTHONUNBUFFERED=1
|
|
34
|
+
|
|
35
|
+
WORKDIR /app
|
|
36
|
+
|
|
37
|
+
# Install dependencies first so the layer caches across code changes.
|
|
38
|
+
COPY pyproject.toml README.md ./
|
|
39
|
+
COPY src ./src
|
|
40
|
+
|
|
41
|
+
# Pin Playwright to the base image's version *before* installing the package, so
|
|
42
|
+
# `.[gcp,anthropic]` (whose `playwright>=…` is already satisfied) cannot pull a
|
|
43
|
+
# newer, browser-mismatched release. `[gcp]` adds the GCS / Firestore / BigQuery
|
|
44
|
+
# SDKs for the cloud path; drop it for a purely local image, add `[aws]` for S3.
|
|
45
|
+
RUN pip install --no-cache-dir "playwright==${PLAYWRIGHT_VERSION}" \
|
|
46
|
+
&& pip install --no-cache-dir ".[gcp,anthropic]" \
|
|
47
|
+
&& test "$(python -c 'import importlib.metadata as m; print(m.version("playwright"))')" = "${PLAYWRIGHT_VERSION}"
|
|
48
|
+
|
|
49
|
+
# Ship the invoice pack's templates (they live inside the package).
|
|
50
|
+
COPY templates/manifest.yaml ./templates/manifest.yaml
|
|
51
|
+
|
|
52
|
+
# Smoke-test the wiring that actually breaks in production. `import` alone passed
|
|
53
|
+
# happily while rendering was completely broken — the pack registered, but every
|
|
54
|
+
# document failed to launch a browser. So this renders ONE real PDF end to end
|
|
55
|
+
# (record → HTML → Chromium → PDF bytes) and fails the build if it cannot. It is
|
|
56
|
+
# the one assertion standing between a broken image and a green run over zero
|
|
57
|
+
# documents.
|
|
58
|
+
#
|
|
59
|
+
# Written as `python -c` one-liners rather than a heredoc on purpose: Cloud
|
|
60
|
+
# Build's daemon has no BuildKit heredoc support, and a `RUN … <<'PY'` block gets
|
|
61
|
+
# parsed as Dockerfile instructions (a Python `from x import y` reads as a second
|
|
62
|
+
# `FROM`).
|
|
63
|
+
RUN python -c "import docsynth.packs; print('packs:', docsynth.available_packs())"
|
|
64
|
+
RUN python -c "import docsynth.packs; from docsynth.core import get_pack; from docsynth.core.pipeline.pdf import PdfRenderer; pack=get_pack('invoice'); rec=pack.default_source().generate('docker-build-check', 0); pdf=PdfRenderer(pack).render(rec).data; assert pdf[:5]==b'%PDF-', 'renderer did not return a PDF'; assert len(pdf) > 1000, f'suspiciously small PDF ({len(pdf)} bytes)'; print(f'render check: {len(pdf)} byte PDF OK')"
|
|
65
|
+
|
|
66
|
+
ENTRYPOINT ["docsynth"]
|
|
67
|
+
CMD ["--help"]
|
docsynth-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adam Okasha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|