Phase 0: lock platform contract, naming convention, and style enforcement
Establishes the framework's foundation as a multi-site adoptable platform. ADRs (migrations/adr/): - ADR-001 (ACCEPTED): Asset is the platform contract; Machine retires. Three relationship types (partof, controls, connectedto) with free-text label, position-resolution chain (asset > related > location), hierarchical locations, sibling-bay propagation. - ADR-002 (ACCEPTED): Plugin contract semver via __contract_version__. - ADR-003 (ACCEPTED): Hybrid plugin distribution (in-tree bundled + filesystem-based external). - ADR-004 (ACCEPTED): Per-site instances, not multi-tenant. - ADR-005 (ACCEPTED): Equipment plugin (manufacturing) split from measuringtools plugin (metrology). Subtype-table pattern for protocol data (FOCAS, CLM, MTConnect). - ADR-006 (ACCEPTED): Plugin collector contract via get_collector_schema hook with API-key auth and identity-based upsert. Naming convention v1 (CONTRIBUTING.md): - DB tables/columns: lowercase concatenated, no underscores or dashes - DB-mirrored Python/JS variables match column names exactly; pure code follows host-language convention (PEP 8 / camelCase) - Closed acronym allowlist (universal + shop-floor domain), banned shorthand list with suffix exception (printers_bp etc allowed) - Plain ASCII everywhere: chat, docs, comments, string literals Style enforcement (scripts/check-naming-and-style.sh): - Pre-commit-runnable check script: non-ASCII, banned shorthand, snake_case DB names, snake_case API params in frontend - Fixes 14 violations across 11 files (Unicode arrows, snake_case params, ctx -> canvasContext, res -> response, req -> request_obj) Project state (CLAUDE.md, README.md, frontend/CLAUDE.md): - De-staled CLAUDE.md to reflect actual current state - README unifies DB story (MySQL canonical, SQLite test-only) - frontend/CLAUDE.md points at root convention Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
184
CONTRIBUTING.md
184
CONTRIBUTING.md
@@ -2,93 +2,141 @@
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### Database Naming Convention
|
||||
### Naming principles
|
||||
|
||||
**IMPORTANT: No underscores in database identifiers**
|
||||
1. Spell it out. If a non-technical user looking at a report would not understand a column at a glance, rename it.
|
||||
2. No project-specific shorthand. A name either uses a word in full, or uses an acronym from the allowed list below.
|
||||
3. Consistency beats cleverness. Once a name is committed and used in code, do not rename it for marginal improvement.
|
||||
4. When unsure, ask: "would a new shop-floor IT person understand this?"
|
||||
|
||||
All database table names and column names must use lowercase concatenated words (no underscores).
|
||||
### Database
|
||||
|
||||
| Pattern | Good | Bad |
|
||||
|---------|------|-----|
|
||||
| Table names | `printerdata` | `printer_data` |
|
||||
| Column names | `passwordhash` | `password_hash` |
|
||||
| Foreign keys | `machinetypeid` | `machine_type_id` |
|
||||
| Index names | `idx_printer_zabbix` | Allowed for indexes |
|
||||
- **Table names**: lowercase, concatenated, plural. No dashes, no underscores.
|
||||
- Good: `machines`, `pctypes`, `networkdevices`, `businessunits`, `auditlogs`
|
||||
- Bad: `machine_types`, `network-devices`, `BusinessUnits`
|
||||
- **Column names**: lowercase, concatenated, singular. No dashes, no underscores.
|
||||
- Good: `machineid`, `machinenumber`, `lastzabbixsync`, `isactive`
|
||||
- Bad: `machine_id`, `last_zabbix_sync`, `IsActive`
|
||||
- **Foreign keys**: referenced table name (singular) + `id`.
|
||||
- Good: `locationid` references `locations`, `vendorid` references `vendors`
|
||||
- **Booleans**: prefix with `is` or `has`.
|
||||
- Good: `isactive`, `isshopfloor`, `hasprinter`
|
||||
- **Index names**: `idx_<table>_<column>`. Underscores allowed here only because indexes are infrastructure, not data.
|
||||
- Good: `idx_machines_locationid`
|
||||
|
||||
### Intuitive Naming for Non-Technical Users
|
||||
### Allowed acronyms (closed list, expand by PR)
|
||||
|
||||
Database tables and columns should use **simple, intuitive names** that non-technical users can understand when viewing data or reports.
|
||||
- **Universal**: `id`, `url`, `api`, `http`, `https`, `json`, `jwt`, `sql`, `os`, `ip`, `dns`, `csv`, `pdf`, `cors`, `ttl`, `uuid`, `html`, `css`, `orm`
|
||||
- **Domain (shop floor / IT infra)**: `cmm`, `cnc`, `pc`, `usb`, `vnc`, `winrm`, `ssh`, `ssl`, `tls`, `tcp`, `udp`, `smtp`, `ldap`, `vlan`, `sso`, `dnc`, `focas`, `clm`, `mtconnect`
|
||||
|
||||
| Avoid | Prefer | Why |
|
||||
|-------|--------|-----|
|
||||
| `printerextensions` | `printerdata` | "data" is clearer than "extensions" |
|
||||
| `machinerelationships` | `machinelinks` | "links" is simpler (consider) |
|
||||
| `comtypeid` | `connectiontypeid` | Spell it out when unclear |
|
||||
Any acronym not on these lists must be spelled out. Proposing a new acronym is a separate PR that updates this list.
|
||||
|
||||
**Guiding principle:** If someone unfamiliar with the system looked at a table name, would they understand what's in it?
|
||||
**Third-party imports are exempt.** Library and stdlib module paths cannot be renamed. `from sqlalchemy.orm import ...`, `import importlib.util`, `from werkzeug.utils import ...` are all fine even if they contain words that would otherwise be banned.
|
||||
|
||||
Examples:
|
||||
### Banned shorthand
|
||||
|
||||
`cfg`, `ctx`, `mgr`, `req`, `res`, `env`, `util`, `helper`, and any other domain-specific shortening. Spell it out: `config`, `context`, `manager`, `request`, `response`, `environment`, `utilities`. Note: `db` as a standalone variable name is banned, but `db` as a prefix in `database` or as part of project name `shopdb` is fine.
|
||||
|
||||
**Suffix exception.** Banned shorthand applies to standalone identifiers. As a suffix where the prefix establishes meaning, it is acceptable:
|
||||
|
||||
- `printers_bp`, `auth_bp`, `network_bp` are fine. The prefix names what blueprint, the `_bp` suffix is conventional for Flask blueprints.
|
||||
- `request_obj` is fine when disambiguating from imported `request`.
|
||||
- `bp` alone as a variable name is not fine. Always pair with a meaningful prefix.
|
||||
|
||||
### Code
|
||||
|
||||
#### Python
|
||||
|
||||
- **Variables and functions holding a database value**: match the column name exactly. No conversion to snake_case.
|
||||
- DB column `machineid` -> Python variable `machineid`, attribute `Machine.machineid`, dict key `{"machineid": 1}`
|
||||
- DB column `lastzabbixsync` -> Python variable `lastzabbixsync`
|
||||
- **Pure code variables and functions** (loop counters, helpers, anything that does not mirror a DB field): snake_case per PEP 8.
|
||||
- Good: `loop_count`, `current_user`, `validate_input()`
|
||||
- **Classes**: PascalCase. Spell out fully.
|
||||
- Good: `PrinterData`, `AssetType`, `NetworkDevice`
|
||||
- **Modules / file names**: lowercase. Underscores allowed where it improves readability (PEP 8 permits this for modules).
|
||||
- Good: `employee_service.py`, `asset_routes.py`
|
||||
|
||||
#### JavaScript / Vue
|
||||
|
||||
- **Variables**: camelCase per JS convention.
|
||||
- Good: `currentUser`, `isLoading`
|
||||
- **Variables holding API field values**: match the API field name exactly. Do NOT convert to camelCase.
|
||||
- API returns `{"machineid": 1}` -> JS `response.machineid`, NOT `response.machineId`
|
||||
- API returns `{"lastzabbixsync": "..."}` -> JS `device.lastzabbixsync`
|
||||
- **Components**: PascalCase. Spell out fully.
|
||||
- Good: `AssetDetail.vue`, `MachineForm.vue`, `PrinterList.vue`
|
||||
- **CSS classes**: lowercase with dashes.
|
||||
- Good: `asset-detail`, `machine-form`
|
||||
|
||||
#### API
|
||||
|
||||
- **Endpoints**: lowercase, plural nouns. No underscores or dashes.
|
||||
- Good: `/api/machines`, `/api/networkdevices`, `/api/businessunits`
|
||||
- **Query parameters**: lowercase, concatenated. Match column names where applicable.
|
||||
- Good: `?locationid=5`, `?isactive=true`
|
||||
- **Response keys**: match database column names exactly.
|
||||
- Good: `{"machineid": 1, "lastzabbixsync": "2026-01-12T10:00:00Z"}`
|
||||
|
||||
### Examples
|
||||
|
||||
```python
|
||||
# Good
|
||||
class PrinterExtension(db.Model):
|
||||
__tablename__ = 'printerextensions'
|
||||
# Good - DB-mirrored fields keep column names; pure code uses snake_case
|
||||
class PrinterData(db.Model):
|
||||
__tablename__ = 'printerdata'
|
||||
|
||||
machineid = db.Column(db.Integer, db.ForeignKey('machines.machineid'))
|
||||
lastzabbixsync = db.Column(db.DateTime)
|
||||
isnetworkprinter = db.Column(db.Boolean)
|
||||
|
||||
# Bad
|
||||
class PrinterExtension(db.Model):
|
||||
__tablename__ = 'printer_extensions'
|
||||
|
||||
machine_id = db.Column(db.Integer, db.ForeignKey('machines.machine_id'))
|
||||
last_zabbix_sync = db.Column(db.DateTime)
|
||||
is_network_printer = db.Column(db.Boolean)
|
||||
def list_printers_by_location(locationid: int) -> list[dict]:
|
||||
printers = PrinterData.query.filter_by(locationid=locationid).all()
|
||||
result_count = len(printers)
|
||||
return [{"machineid": p.machineid, "isnetworkprinter": p.isnetworkprinter} for p in printers]
|
||||
```
|
||||
|
||||
### API Response Keys
|
||||
|
||||
API JSON responses should also use lowercase concatenated keys to match database columns:
|
||||
|
||||
```json
|
||||
{
|
||||
"machineid": 1,
|
||||
"machinenumber": "M001",
|
||||
"lastzabbixsync": "2026-01-12T10:00:00Z",
|
||||
"isnetworkprinter": true
|
||||
}
|
||||
```
|
||||
|
||||
### Python Code
|
||||
|
||||
Python variable and function names follow standard Python conventions (snake_case for variables/functions, PascalCase for classes):
|
||||
|
||||
```python
|
||||
# Variables and functions use snake_case
|
||||
machine_type = get_machine_type()
|
||||
is_valid = validate_input(data)
|
||||
# Bad - PEP 8-style underscores on DB fields, banned shorthand, project acronym not on allowed list
|
||||
class PrinterData(db.Model):
|
||||
__tablename__ = 'printer_data'
|
||||
|
||||
# Classes use PascalCase
|
||||
class PrinterExtension:
|
||||
pass
|
||||
machine_id = db.Column(db.Integer)
|
||||
last_zabbix_sync = db.Column(db.DateTime)
|
||||
is_net_prn = db.Column(db.Boolean)
|
||||
|
||||
|
||||
def list_prns_by_loc(loc_id):
|
||||
bp_ctx = get_context()
|
||||
cfg = load_cfg()
|
||||
return PrinterData.query.filter_by(loc_id=loc_id).all()
|
||||
```
|
||||
|
||||
### File Structure
|
||||
### Style policy (non-naming)
|
||||
|
||||
- Models: `shopdb/core/models/` or `plugins/<plugin>/models/`
|
||||
- API routes: `shopdb/core/api/` or `plugins/<plugin>/api/`
|
||||
- Services: `shopdb/core/services/` or `plugins/<plugin>/services/`
|
||||
- No emojis in code, comments, documentation, string literals, or UI.
|
||||
- No em-dashes (U+2014), en-dashes (U+2013), Unicode arrows, or smart quotes anywhere. Plain ASCII only.
|
||||
- Comments default to none. Add one only when the WHY is non-obvious. Keep comments terse (lite caveman style is fine for inline `#` and `//` comments). Docstrings stay normal English.
|
||||
- Dark theme is default. Keep UI functional and professional.
|
||||
|
||||
### Plugin Development
|
||||
### File structure
|
||||
|
||||
- Core models: `shopdb/core/models/`
|
||||
- Core API routes: `shopdb/core/api/`
|
||||
- Core services: `shopdb/core/services/`
|
||||
- Plugin code: `plugins/<plugin>/{models,api,services,schemas}/`
|
||||
|
||||
### Plugin development
|
||||
|
||||
When creating a new plugin:
|
||||
|
||||
1. Create directory structure in `plugins/<name>/`
|
||||
2. Include `manifest.json` with metadata
|
||||
3. Extend `BasePlugin` class
|
||||
4. Follow naming conventions above
|
||||
5. Run `flask plugin install <name>` to install
|
||||
1. Create directory `plugins/<name>/`. Use the scaffold (`flask plugin new <name>`) when available.
|
||||
2. Include `manifest.json` with metadata. This is the single source of truth for plugin name, version, dependencies, api_prefix.
|
||||
3. Extend `BasePlugin` class.
|
||||
4. Follow naming conventions above.
|
||||
5. Run `flask plugin install <name>` to install.
|
||||
|
||||
See `docs/PLUGIN-QUICKSTART.md` (when published) for the 30-minute walkthrough.
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -98,9 +146,17 @@ Run tests with:
|
||||
pytest tests/
|
||||
```
|
||||
|
||||
## Code Review Checklist
|
||||
Tests are required for:
|
||||
- New routes (smoke + happy path + 1-2 edge cases)
|
||||
- New plugin hooks (contract test)
|
||||
- Schema migrations (before/after data integrity)
|
||||
|
||||
- [ ] No underscores in table/column names
|
||||
- [ ] API responses use consistent key naming
|
||||
- [ ] Plugin follows BasePlugin interface
|
||||
## Code review checklist
|
||||
|
||||
- [ ] No underscores in table or column names
|
||||
- [ ] No banned shorthand or unapproved acronyms in any new identifier
|
||||
- [ ] DB-mirrored Python and JS variables match column names exactly
|
||||
- [ ] API response keys match column names exactly
|
||||
- [ ] No em-dashes, en-dashes, smart quotes, Unicode arrows, or emojis (run `grep -rP '[^\x00-\x7F]' --include='*.py' --include='*.vue' --include='*.js' .`)
|
||||
- [ ] Plugin follows `BasePlugin` interface and contract version
|
||||
- [ ] Tests included for new functionality
|
||||
|
||||
Reference in New Issue
Block a user