From 84a40ed0f288901c6ae8397d07da20a0b263bf20 Mon Sep 17 00:00:00 2001 From: Gerald Fruhmann Date: Sat, 4 Jul 2026 20:27:47 +0200 Subject: [PATCH 1/2] chore: package as installable CLI + complete docs for first real deploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Packaging: - Add [build-system] hatchling + tool.uv.package=true so qms-kit is a proper pip-installable CLI (was skipped before, entry point ignored) - hatchling added to dev dependencies Docs: - readme: fix docker-compose path, replace python -m cli with qms-kit, full 6-step quickstart, Phase 1 marked complete - docs/configuration.md: rewrite from TODO stub to full reference — all Pydantic model keys documented with types and descriptions, tracker_mapping example, validation error guide - docs/architecture.md: fix CLI invocation to qms-kit deploy - .markdownlint.json: disable MD033/MD060/MD013 (inline HTML and table pipe style are intentional in this repo) Co-Authored-By: Claude Sonnet 4.6 --- .markdownlint.json | 6 ++ docs/architecture.md | 2 +- docs/configuration.md | 178 ++++++++++++++++++++++++++++++++++-------- pyproject.toml | 11 +++ readme.md | 59 ++++++++------ uv.lock | 28 ++++++- 6 files changed, 223 insertions(+), 61 deletions(-) create mode 100644 .markdownlint.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..fb16fb9 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD033": false, + "MD060": false, + "MD013": false +} diff --git a/docs/architecture.md b/docs/architecture.md index 7a7034e..610f995 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -58,7 +58,7 @@ Running `deploy` twice produces the same result without duplicates. ### CLI layer -Single entry point (`python -m cli deploy --target --config `). +Single entry point (`qms-kit deploy --target --config `). Orchestrates: load config → merge overlay → render templates → call adapter. ## Self-hosted Stack diff --git a/docs/configuration.md b/docs/configuration.md index a608d46..daed269 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,49 +1,159 @@ # Configuration Reference -> TODO: Full reference will be written once the core config loader (Pydantic schema) is complete. +Configuration is split into two layers that are deep-merged at runtime (overlay wins on conflict). -## Overview - -Configuration is split into two layers: +## Files | File | Purpose | |------|---------| -| `config/core.yaml` | Tool-neutral QMS substance — clauses, documents, roles, records, KPIs | -| `config/.yaml` | Tool overlay template — copy and fill per client | -| `config/clients/.yaml` | Per-client filled overlay (gitignored, not in this repo) | +| `config/core.yaml` | Tool-neutral QMS substance — clauses, documents, roles, records, KPIs, CAPA states | +| `config/selfhosted.yaml` | Self-hosted overlay template (copy per client) | +| `config/clients/.yaml` | Per-client filled overlay (gitignored — never committed) | +| `config/clients/example.yaml` | Reference template showing all available keys | + +## Core config keys (`config/core.yaml`) + +These keys define the QMS substance and are shared across all targets. + +### `meta` + +| Key | Type | Description | +|-----|------|-------------| +| `meta.version` | string | Config schema version | +| `meta.standard` | string | QMS standard (e.g. `ISO 9001:2015`) | + +### `organisation` + +| Key | Type | Description | +|-----|------|-------------| +| `organisation.name` | string | Full legal name of the client organisation | +| `organisation.short` | string | Short identifier used in document IDs | +| `organisation.quality_officer` | string | Full name of the QMO | +| `organisation.management` | string | Full name of the approving manager | + +### `clauses` + +List of ISO 9001 clause objects (4–10). Each clause has: + +| Key | Type | Description | +|-----|------|-------------| +| `id` | string | Clause number (e.g. `"4"`, `"7.5"`) | +| `title` | string | Clause title | +| `documents` | list[string] | Document IDs that belong to this clause | + +### `documents` + +List of document definitions. Each document has: + +| Key | Type | Description | +|-----|------|-------------| +| `id` | string | Unique document ID (e.g. `quality-policy`) | +| `title` | string | Human-readable title | +| `template` | string or null | Template filename without `.md.j2` extension; null = no template | +| `clause` | string | Parent clause ID | + +### `record_types` + +List of record type definitions (NC, CAPA, Internal Audit, KPI). Each has: + +| Key | Type | Description | +|-----|------|-------------| +| `id` | string | Record type ID (e.g. `nc`, `capa`) | +| `label` | string | Human-readable label | +| `clause` | string | ISO clause this record type belongs to | +| `fields` | list[string] | Custom fields required on this record | + +### `kpis` + +List of KPI definitions. Each has: + +| Key | Type | Description | +|-----|------|-------------| +| `id` | string | KPI identifier | +| `label` | string | Human-readable name | +| `unit` | string | Measurement unit (e.g. `%`, `days`) | +| `target` | string | Target value | +| `frequency` | string | Measurement frequency | +| `clause` | string | ISO clause | + +### `capa_states` + +CAPA state machine definition. Each state has: + +| Key | Type | Description | +|-----|------|-------------| +| `id` | string | State identifier | +| `label` | string | Display name | +| `transitions` | list[string] | Valid next state IDs | + +--- + +## Overlay keys + +### Common + +| Key | Type | Description | +|-----|------|-------------| +| `target` | string | `selfhosted` / `atlassian` / `m365` | +| `organisation.*` | — | Overrides any `organisation` key from core | + +### Self-hosted (`selfhosted:`) + +#### XWiki (`selfhosted.xwiki`) + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `base_url` | string | — | XWiki instance URL (e.g. `https://wiki.acme.example`) | +| `space_key` | string | `QMS` | XWiki space key for the QMS | +| `username` | string | — | XWiki admin username | +| `parent_page` | string | `QM Manual` | Parent page for all QMS pages | + +Password via env var: `XWIKI_PASSWORD` + +#### Redmine (`selfhosted.redmine`) + +| Key | Type | Description | +|-----|------|-------------| +| `base_url` | string | Redmine instance URL (e.g. `https://redmine.acme.example`) | +| `project_key` | string | Redmine project identifier (e.g. `qms-acme`) | +| `tracker_mapping` | map | Maps record type IDs to Redmine tracker names | + +API key via env var: `REDMINE_API_KEY` + +**Tracker mapping example:** + +```yaml +tracker_mapping: + nc: "Nonconformity" # must match the tracker name in Redmine exactly + capa: "CAPA" + audit: "Internal Audit" + kpi: "KPI Measurement" +``` -## Core Config Keys +> **API limitation (verified):** Redmine tracker and custom field *definitions* cannot be created +> via the API. Run the one-time manual setup first — see [redmine-setup.md](redmine-setup.md). -See [`config/core.yaml`](../config/core.yaml) — all keys are documented inline. +--- -## Overlay Keys +## Environment variables -| Key | Description | -|-----|-------------| -| `target` | `selfhosted` / `atlassian` / `m365` | -| `organisation.name` | Full legal name of the client organisation | -| `organisation.short` | Short identifier used in document IDs | -| `organisation.quality_officer` | Full name of the QMO | -| `organisation.management` | Full name of the approving manager | +Secrets are never stored in config files. Pass them as environment variables before running `qms-kit deploy`. -### Self-hosted specific +| Variable | Required for | Description | +|----------|-------------|-------------| +| `XWIKI_PASSWORD` | Self-hosted | XWiki admin password | +| `REDMINE_API_KEY` | Self-hosted | Redmine API key (from My Account → API access key) | +| `CONFLUENCE_API_TOKEN` | Atlassian (Phase 2) | Confluence Cloud API token | +| `JIRA_API_TOKEN` | Atlassian (Phase 2) | Jira Cloud API token | +| `SHAREPOINT_CLIENT_SECRET` | M365 (Phase 3) | SharePoint Graph API client secret | -| Key | Description | -|-----|-------------| -| `xwiki.base_url` | XWiki instance URL | -| `xwiki.space_key` | XWiki space key for the QMS (default: `QMS`) | -| `redmine.base_url` | Redmine instance URL | -| `redmine.project_key` | Redmine project identifier | -| `redmine.tracker_mapping` | Maps record type IDs to Redmine tracker names | +--- -## Environment Variables +## Validation -Secrets are never stored in config files — pass them as environment variables: +The config is validated with Pydantic at load time. Common errors: -| Variable | Used by | -|----------|---------| -| `XWIKI_PASSWORD` | XWiki REST authentication | -| `REDMINE_API_KEY` | Redmine REST authentication | -| `CONFLUENCE_API_TOKEN` | Confluence REST authentication (Phase 2) | -| `JIRA_API_TOKEN` | Jira REST authentication (Phase 2) | -| `SHAREPOINT_CLIENT_SECRET` | SharePoint Graph API (Phase 3) | +- **Duplicate document IDs** → `ValueError: Duplicate document IDs: {...}` +- **Missing required field** → `ValidationError: Field required` +- **Missing selfhosted block** → `ValueError: selfhosted config block is required` +- **Unknown template variable** → `jinja2.UndefinedError: ... is undefined` diff --git a/pyproject.toml b/pyproject.toml index d329601..0f0b5cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,13 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src", "cli", "adapters"] + +[tool.uv] +package = true + [project] name = "qms-kit" version = "0.1.0" @@ -17,6 +27,7 @@ dev = [ "pytest-cov>=5.0", "mypy>=1.10", "bandit>=1.8", + "hatchling>=1.25", "types-pyyaml>=6.0", "types-requests>=2.31", ] diff --git a/readme.md b/readme.md index f15d8cf..ccf31f0 100644 --- a/readme.md +++ b/readme.md @@ -25,10 +25,9 @@ Fill in your organisation's content over the following two weeks. **qms-kit deploys the structure. You fill in the content.** -One command wires up spaces, document templates, issue trackers, approval workflows, and KPI -dashboards for ISO 9001 clauses 4–10 — in XWiki + Redmine (self-hosted), Confluence + Jira -(Atlassian), or SharePoint + Power Automate (M365). -Reuse the same kit for every client; swap the overlay config. +One command wires up spaces, document templates, issue trackers, and KPI dashboards for +ISO 9001 clauses 4–10 — in XWiki + Redmine (self-hosted), Confluence + Jira (Atlassian), +or SharePoint + Power Automate (M365). Reuse the same kit for every client; swap the overlay config. > ⚠️ **Scope:** qms-kit delivers a documentation scaffold for internal maturity and > customer/second-party audits. Accredited certification requires an additional operating phase @@ -41,8 +40,8 @@ Reuse the same kit for every client; swap the overlay config. - 📋 **ISO 9001 clauses 4–10 as structure** — every space, page, and tracker maps to a clause. - ⚙️ **Config-driven, idempotent** — declare your QMS in YAML; run deploy as often as needed. - 🔁 **Multi-tenant** — one core config, one overlay per client. No copy-paste between engagements. -- 🧩 **Adapter model** — self-hosted, Atlassian, and M365 are interchangeable modules; the core never changes. -- 📄 **Jinja2 templates** — policy, procedure, management review agenda, audit checklist, CAPA form — all with placeholders, ready to hand to your client. +- 🧩 **Adapter model** — self-hosted, Atlassian, and M365 are interchangeable modules. +- 📄 **Jinja2 templates** — quality policy, procedures, management review agenda, internal audit checklist, CAPA form — all with placeholders, ready to hand to your client. --- @@ -50,33 +49,44 @@ Reuse the same kit for every client; swap the overlay config. | Layer | What | |-------|------| -| **Structure** | Space / site / project, document tree, naming conventions | +| **Structure** | Space / project, document tree, naming conventions | | **Document templates** | Quality policy, procedures, MR agenda, internal audit checklist, CAPA/NC form | -| **Record containers** | Issue schemas for NC, CAPA, audits, objectives/KPIs | -| **Workflows** | Approval/review routing, CAPA escalation, review reminders | -| **Dashboards** | KPI and status overview | +| **Record containers** | Issue trackers for NC, CAPA, audits, KPIs | --- ## 🚀 Quickstart -> Self-hosted target (XWiki + Redmine via Docker Compose): +Self-hosted target (XWiki + Redmine via Docker Compose): ```bash -# 1. Copy and fill in your client overlay -cp config/selfhosted.yaml config/clients/acme.yaml -# edit acme.yaml: org name, XWiki URL, Redmine URL, credentials +# 1. Clone and install +git clone https://github.com/gerfru/qms-kit.git +cd qms-kit +uv sync -# 2. Start the stack (first time only — brings up XWiki + Redmine) -docker compose -f adapters/selfhosted/docker-compose.yml up -d +# 2. Start XWiki + Redmine +cp docker/.env.example docker/.env # fill in passwords +docker compose -f docker/docker-compose.yml up -d -# 3. Seed the QMS scaffold -python -m cli deploy --target selfhosted --config config/clients/acme.yaml +# 3. One-time Redmine setup (manual — API limitation) +# See docs/redmine-setup.md + +# 4. Create your client overlay +cp config/clients/example.yaml config/clients/acme.yaml +# edit acme.yaml: org name, URLs, credentials + +# 5. Dry-run first (no changes written) +export XWIKI_PASSWORD=Admin +export REDMINE_API_KEY= +qms-kit deploy --target selfhosted --config config/clients/acme.yaml --dry-run + +# 6. Deploy +qms-kit deploy --target selfhosted --config config/clients/acme.yaml ``` -> **Note on Redmine custom fields:** Field *definitions* (Root Cause, Effectiveness Check, Status) -> must be created once manually or via plugin — the Redmine API does not support creating them. -> The deployer seeds projects, issues, and field *values* automatically. +> **Redmine custom fields** (Root Cause, Effectiveness Check, etc.) must be created once +> manually — the Redmine API does not support creating field definitions. > See [docs/redmine-setup.md](docs/redmine-setup.md). → Full walkthrough: **[docs/setup.md](docs/setup.md)** @@ -87,7 +97,7 @@ python -m cli deploy --target selfhosted --config config/clients/acme.yaml | Target | Status | Stack | |--------|--------|-------| -| **Self-hosted** | ✅ Phase 1 | XWiki (docs) + Redmine (records) via Docker | +| **Self-hosted** | ✅ Phase 1 complete | XWiki (docs) + Redmine (records) via Docker | | **Atlassian** | 🔜 Phase 2 | Confluence + Jira via REST API | | **Microsoft 365** | 🔜 Phase 3 | SharePoint + Power Automate via PnP PowerShell | @@ -111,11 +121,10 @@ python -m cli deploy --target selfhosted --config config/clients/acme.yaml ## 🧱 Stack -Python · Jinja2 · YAML · Docker Compose · XWiki REST API · Redmine REST API +Python · Jinja2 · Pydantic · YAML · Click · Docker Compose · XWiki REST API · Redmine REST API Three layers above the adapters: a config loader with Pydantic schema validation, a Jinja2 -template renderer, and a single CLI entry point (`deploy --target`). -M365 adapter shells out to PnP PowerShell; everything else is pure Python. +template renderer, and a single CLI entry point (`qms-kit deploy --target`). --- diff --git a/uv.lock b/uv.lock index 9e2c912..f434f7c 100644 --- a/uv.lock +++ b/uv.lock @@ -243,6 +243,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, ] +[[package]] +name = "hatchling" +version = "1.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/4c/8717ccb844b4fa5a5ba6352e97d743ed24e9a22cf90b7c109c17030a46a1/hatchling-1.30.1.tar.gz", hash = "sha256:eee4fd45357f72ebb3d7a42e5d72cfb5e29ed426d79e8836288926c4258d5f2e", size = 56929, upload-time = "2026-06-02T00:09:41.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl", hash = "sha256:161eacafb3c6f91526e92116d21426369f2c36e98c36a864f11a96345ad4ee31", size = 77489, upload-time = "2026-06-02T00:09:40.139Z" }, +] + [[package]] name = "idna" version = "3.18" @@ -677,7 +692,7 @@ wheels = [ [[package]] name = "qms-kit" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "click" }, { name = "jinja2" }, @@ -689,6 +704,7 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "bandit" }, + { name = "hatchling" }, { name = "mypy" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -700,6 +716,7 @@ dev = [ requires-dist = [ { name = "bandit", marker = "extra == 'dev'", specifier = ">=1.8" }, { name = "click", specifier = ">=8.1" }, + { name = "hatchling", marker = "extra == 'dev'", specifier = ">=1.25" }, { name = "jinja2", specifier = ">=3.1" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" }, { name = "pydantic", specifier = ">=2.0" }, @@ -749,6 +766,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/8d/008761f6e1000600e5303db30d05724bdcf3d2d186cbb59fac79b52e39ed/stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7", size = 54463, upload-time = "2026-07-02T11:38:07.43Z" }, ] +[[package]] +name = "trove-classifiers" +version = "2026.6.1.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/e3/7ca82ee24c82d344584abd5b8637b3bd056f2900226e8d82fc22f1184b92/trove_classifiers-2026.6.1.19.tar.gz", hash = "sha256:c5132b4b61a829d11cfbd2d72e97f20a45ed6edb95e45c5efdeb5e00836b2745", size = 17059, upload-time = "2026-06-01T19:41:34.649Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl", hash = "sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3", size = 14211, upload-time = "2026-06-01T19:41:33.434Z" }, +] + [[package]] name = "types-pyyaml" version = "6.0.12.20260518" From 248903d6cef68679b769610b3ee44f588db63422 Mon Sep 17 00:00:00 2001 From: Gerald Fruhmann Date: Sat, 4 Jul 2026 21:02:22 +0200 Subject: [PATCH 2/2] fix: convert templates to XWiki 2.1 syntax, no extensions required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename *.md.j2 → *.xwiki.j2 (5 templates) - Convert Markdown headings/tables/lists to XWiki 2.1 syntax - Replace {{info}} macros (conflict with Jinja2 + requires extension) with **⚠ TODO:** bold markers — no XWiki extension needed - XWiki adapter: syntax markdown/1.2 → xwiki/2.1 - CLI: template extension .md.j2 → .xwiki.j2 - Tests updated to reference new template filenames Co-Authored-By: Claude Sonnet 4.6 --- adapters/selfhosted/xwiki.py | 2 +- cli/main.py | 2 +- templates/capa_form.xwiki.j2 | 48 +++++++++++++++++++++ templates/internal_audit_checklist.xwiki.j2 | 42 ++++++++++++++++++ templates/mgmt_review_agenda.xwiki.j2 | 35 +++++++++++++++ templates/policy.xwiki.j2 | 31 +++++++++++++ templates/procedure.xwiki.j2 | 44 +++++++++++++++++++ tests/test_renderer.py | 6 +-- 8 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 templates/capa_form.xwiki.j2 create mode 100644 templates/internal_audit_checklist.xwiki.j2 create mode 100644 templates/mgmt_review_agenda.xwiki.j2 create mode 100644 templates/policy.xwiki.j2 create mode 100644 templates/procedure.xwiki.j2 diff --git a/adapters/selfhosted/xwiki.py b/adapters/selfhosted/xwiki.py index eebd4d3..8a43c6e 100644 --- a/adapters/selfhosted/xwiki.py +++ b/adapters/selfhosted/xwiki.py @@ -43,7 +43,7 @@ def page_exists(self, page_name: str) -> bool: def create_or_update_page(self, page_name: str, title: str, content: str) -> None: """Idempotent: creates the page if absent, updates content if present.""" - payload = {"title": title, "content": content, "syntax": "markdown/1.2"} + payload = {"title": title, "content": content, "syntax": "xwiki/2.1"} self._put(self._page_url(page_name), payload) def deploy(self, rendered_pages: dict[str, tuple[str, str]]) -> None: diff --git a/cli/main.py b/cli/main.py index 77c1d41..0542351 100644 --- a/cli/main.py +++ b/cli/main.py @@ -54,7 +54,7 @@ def deploy(target: str, config_path: Path, dry_run: bool) -> None: for doc in config.documents: if doc.template is None: continue - template_file = f"{doc.template}.md.j2" + template_file = f"{doc.template}.xwiki.j2" content = render_template( template_name=template_file, config=config, diff --git a/templates/capa_form.xwiki.j2 b/templates/capa_form.xwiki.j2 new file mode 100644 index 0000000..4e1c321 --- /dev/null +++ b/templates/capa_form.xwiki.j2 @@ -0,0 +1,48 @@ += CAPA Form — Corrective Action = + +|=Field|=Value +|**CAPA ID**|{{ capa_id }} +|**Organisation**|{{ org_name }} +|**Opened**|{{ opened_date }} +|**Opened by**|{{ opened_by }} +|**ISO Clause**|10.2 + +---- + +== 1. Background == + +**Source (NC ID / Audit Finding / other trigger):** {{ source_ref }} + +**Problem Description:** **⚠ TODO:** describe the problem + +== 2. Immediate Correction == + +**Action:** TODO +**Completed by:** TODO +**Completed on:** TODO + +== 3. Root Cause Analysis == + +**Method:** ☐ 5-Why ☐ Ishikawa ☐ Other: TODO + +**Root Cause Identified:** **⚠ TODO:** describe root cause + +== 4. Corrective Action == + +|=Action|=Responsible|=Due Date|=Done +|TODO|TODO|TODO|☐ + +== 5. Effectiveness Review == + +**Review planned for:** TODO +**Review conducted on:** TODO +**Result:** ☐ Effective ☐ Not effective → new CAPA required + +== 6. Closure == + +**Closed on:** TODO +**Closed by:** TODO + +---- + +//ISO 9001:2015 · Clause 10.2 · {{ org_name }}// diff --git a/templates/internal_audit_checklist.xwiki.j2 b/templates/internal_audit_checklist.xwiki.j2 new file mode 100644 index 0000000..979fb8c --- /dev/null +++ b/templates/internal_audit_checklist.xwiki.j2 @@ -0,0 +1,42 @@ += Internal Audit — Checklist = + +|=Field|=Value +|**Organisation**|{{ org_name }} +|**Audit Date**|{{ audit_date }} +|**Auditor**|{{ auditor }} +|**Audit Scope**|{{ audit_scope }} +|**Contact Person**|{{ contact }} + +---- + +== ISO 9001:2015 Checklist == + +|=Clause|=Check Point|=Compliant|=Observation / Finding +|4.1|Context analysis documented and up to date?|☐ Yes ☐ No ☐ N/A| +|4.2|Interested parties register maintained?|☐ Yes ☐ No ☐ N/A| +|4.3|QMS scope defined?|☐ Yes ☐ No ☐ N/A| +|5.2|Quality policy communicated and understood?|☐ Yes ☐ No ☐ N/A| +|5.3|Roles and responsibilities clearly assigned?|☐ Yes ☐ No ☐ N/A| +|6.1|Risks and opportunities assessed?|☐ Yes ☐ No ☐ N/A| +|6.2|Quality objectives measurable and documented?|☐ Yes ☐ No ☐ N/A| +|7.2|Competency matrix current, training evidenced?|☐ Yes ☐ No ☐ N/A| +|7.4|Communication plan implemented?|☐ Yes ☐ No ☐ N/A| +|7.5|Document control procedure followed?|☐ Yes ☐ No ☐ N/A| +|8.1|Processes planned and controlled?|☐ Yes ☐ No ☐ N/A| +|8.2|Customer requirements determined and met?|☐ Yes ☐ No ☐ N/A| +|8.7|Nonconformities recorded and addressed?|☐ Yes ☐ No ☐ N/A| +|9.1|KPIs measured and evaluated?|☐ Yes ☐ No ☐ N/A| +|9.3|Management review conducted?|☐ Yes ☐ No ☐ N/A| +|10.2|CAPAs closed on time and verified effective?|☐ Yes ☐ No ☐ N/A| + +== Audit Result == + +**Overall Assessment:** ☐ Conforming ☐ Conditionally conforming ☐ Non-conforming + +**Key Findings:** **⚠ TODO:** describe findings + +**Recommendations:** **⚠ TODO:** recommendations + +---- + +//ISO 9001:2015 · Clause 9.2 · {{ org_name }}// diff --git a/templates/mgmt_review_agenda.xwiki.j2 b/templates/mgmt_review_agenda.xwiki.j2 new file mode 100644 index 0000000..c0a748d --- /dev/null +++ b/templates/mgmt_review_agenda.xwiki.j2 @@ -0,0 +1,35 @@ += Management Review — Agenda and Minutes = + +|=Field|=Value +|**Organisation**|{{ org_name }} +|**Date**|{{ date }} +|**Attendees**|{{ participants }} +|**Facilitated by**|{{ quality_officer }} + +---- + +== Mandatory Agenda Items (ISO 9001:2015 Cl. 9.3.2) == + +|=No.|=Topic|=Input|=Responsible +|1|Status of actions from previous review|Previous MR minutes|{{ quality_officer }} +|2|Changes in external / internal context|Context analysis|Management +|3|Customer satisfaction & feedback|KPI dashboard|{{ quality_officer }} +|4|Achievement of quality objectives / KPIs|KPI dashboard|{{ quality_officer }} +|5|Process performance & product conformity|Process reports|Process owners +|6|Nonconformities & CAPA status|NC/CAPA list|{{ quality_officer }} +|7|Internal audit results|Audit reports|{{ quality_officer }} +|8|Resource needs|**⚠ TODO:** source|Management +|9|Opportunities for improvement|All inputs|All + +== Decisions and Actions == + +|=No.|=Decision / Action|=Responsible|=Due Date +|1|**⚠ TODO**|TODO|TODO + +== Next Management Review == + +**Planned:** **⚠ TODO:** date + +---- + +//ISO 9001:2015 · Clause 9.3 · {{ org_name }}// diff --git a/templates/policy.xwiki.j2 b/templates/policy.xwiki.j2 new file mode 100644 index 0000000..5499dda --- /dev/null +++ b/templates/policy.xwiki.j2 @@ -0,0 +1,31 @@ += Quality Policy = + +|=Field|=Value +|**Organisation**|{{ org_name }} +|**Date**|{{ date }} +|**Version**|{{ version }} +|**Approved by**|{{ management }} + +---- + +== Our Quality Commitment == + +{{ org_name }} is committed to ... + +**⚠ TODO:** Insert quality policy statement. Describe what quality means to the organisation, +what commitments management makes, and how customer needs are addressed. +Aim for 3–5 concise sentences that reflect the company culture. + +== Quality Objectives == + +Specific quality objectives are defined in the objectives and action plan (Clause 6.2) +and reviewed regularly during the Management Review (Clause 9.3). + +== Communication == + +This Quality Policy is communicated to all employees and is available to +interested parties upon request. + +---- + +//ISO 9001:2015 · Clause 5.2 · {{ org_name }}// diff --git a/templates/procedure.xwiki.j2 b/templates/procedure.xwiki.j2 new file mode 100644 index 0000000..3484163 --- /dev/null +++ b/templates/procedure.xwiki.j2 @@ -0,0 +1,44 @@ += Procedure: {{ procedure_title }} = + +|=Field|=Value +|**Document ID**|{{ doc_id }} +|**Version**|{{ version }} +|**Date**|{{ date }} +|**Process Owner**|{{ owner_role }} +|**Approved by**|{{ approver }} +|**ISO Clause**|{{ clause }} + +---- + +== 1. Purpose == + +**⚠ TODO:** Describe the purpose of this procedure. + +== 2. Scope == + +**⚠ TODO:** Who and what does this procedure apply to? + +== 3. Terms and Abbreviations == + +|=Term|=Definition +|TODO|TODO + +== 4. Process == + +**⚠ TODO:** Describe the process steps. Tip: export a draw.io diagram as PNG and embed it here. + +|=Step|=Activity|=Responsible|=Document / Record +|1|TODO|TODO|TODO + +== 5. Related Documents == + +* **⚠ TODO:** Reference relevant forms, records, and other procedures + +== 6. Change History == + +|=Version|=Date|=Change|=Author +|1.0|{{ date }}|Initial release|{{ author }} + +---- + +//ISO 9001:2015 · Clause {{ clause }} · {{ org_name }}// diff --git a/tests/test_renderer.py b/tests/test_renderer.py index 26598f2..8ea6027 100644 --- a/tests/test_renderer.py +++ b/tests/test_renderer.py @@ -38,7 +38,7 @@ def test_extra_overrides_base(self) -> None: class TestRenderTemplate: def test_policy_renders_org_name(self) -> None: result = render_template( - "policy.md.j2", + "policy.xwiki.j2", _make_config(), TEMPLATES_DIR, extra={"date": "2024-01-01"}, @@ -48,7 +48,7 @@ def test_policy_renders_org_name(self) -> None: def test_procedure_renders_title(self) -> None: result = render_template( - "procedure.md.j2", + "procedure.xwiki.j2", _make_config(), TEMPLATES_DIR, extra={ @@ -66,7 +66,7 @@ def test_procedure_renders_title(self) -> None: def test_capa_form_renders(self) -> None: result = render_template( - "capa_form.md.j2", + "capa_form.xwiki.j2", _make_config(), TEMPLATES_DIR, extra={