Initialize Article Data Manager project

This commit is contained in:
2026-07-29 10:53:21 +02:00
commit 83639b9957
51 changed files with 1100 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{csv,json}]
indent_size = 2
+29
View File
@@ -0,0 +1,29 @@
.DS_Store
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
.ruff_cache/
dist/
build/
*.egg-info/
# Productive or unchecked source data
incoming/
data/source/**
data/manual/**
data/generated/**
data/reports/**
# Keep directory layout
!data/source/
!data/source/rollcalc/
!data/source/erp/
!data/source/rollcalc/.gitkeep
!data/source/erp/.gitkeep
!data/manual/
!data/manual/.gitkeep
!data/generated/
!data/generated/.gitkeep
!data/reports/
!data/reports/.gitkeep
+19
View File
@@ -0,0 +1,19 @@
# AGENTS.md
Arbeitsregeln fuer KI-gestuetzte Entwicklung in diesem Repository:
- Anforderungen vor Implementierung pruefen.
- Keine produktiven Daten in Git einchecken.
- Keine stillen Datenkorrekturen vornehmen.
- Artikelnummern niemals numerisch behandeln.
- Fuehrende Nullen bei Artikelnummern erhalten.
- Einheiten explizit dokumentieren.
- Bestehende Quelldaten niemals direkt ueberschreiben.
- Generierte Dateien nur reproduzierbar erzeugen.
- Konflikte berichten statt raten.
- Kleine, nachvollziehbare Aenderungen bevorzugen.
- Tests fuer Import-, Normalisierungs- und Merge-Regeln ergaenzen.
- Dokumentation bei Aenderungen am Datenmodell aktualisieren.
- Keine Weboberflaeche vor Abschluss der CLI-Grundlage.
- Keine zusaetzlichen Abhaengigkeiten ohne nachvollziehbaren Nutzen.
- Fachliche Bedeutung unbekannter ERP-Spalten ausdruecklich als offen kennzeichnen.
+8
View File
@@ -0,0 +1,8 @@
# Changelog
## Unreleased
- Initiale Projektstruktur angelegt.
- Dokumentation fuer Architektur, Datenmodell, Datenwoerterbuch und Merge-Regeln erstellt.
- Reduzierte Beispieldaten und Test-Fixtures ergaenzt.
- Phase-0-Python-Grundlagen fuer Artikelmodell, CSV-Erkennung und Normalisierung angelegt.
+21
View File
@@ -0,0 +1,21 @@
# Contributing
Dieses Projekt befindet sich in Phase 0. Aenderungen sollen klein, nachvollziehbar und reproduzierbar sein.
## Entwicklung
```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
ruff check .
pytest
```
## Datenregeln
- Produktive Daten bleiben in `incoming/` oder `data/` und werden nicht versioniert.
- Beispiele gehoeren nach `examples/` oder `tests/fixtures/`.
- Artikelnummern sind Strings.
- Einheiten und Override-Regeln muessen in `docs/data-dictionary.md` dokumentiert werden.
- Konflikte werden berichtet, nicht automatisch entschieden.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Article Data Manager contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+60
View File
@@ -0,0 +1,60 @@
# Project Knowledge
## Zweck
Article Data Manager erzeugt, validiert und pflegt langfristig die Artikeldaten fuer den bestehenden Roll Calculator.
## Abgrenzung
Das Projekt ist ein eigenstaendiges Repository. RollCalc konsumiert spaeter nur die generierte `article-data.json`.
## Zentrale Datenquellen
- ERP-CSV-Export als primaere Quelle fuer fachlich eindeutige Stammdaten.
- Bestehende RollCalc-JSON als Quelle fuer vorhandene Berechnungsparameter.
- Manuelle CSV-Daten fuer Zusatzdaten und explizite Korrekturen.
## Artikelnummer
`nr` und ERP-Artikelnummern werden immer als Strings behandelt. Fuehrende Nullen muessen erhalten bleiben.
## Einheiten
- `width`: Meter
- `thickness`: Millimeter
- `area_weight`: Gramm pro Quadratmeter
- `core_diameter`: Millimeter
- `core_type`: separater Wert, nicht mit Durchmesser vermischen
## Merge-Prioritaeten
1. ERP-Stammdaten
2. bestehende RollCalc-Berechnungsdaten
3. manuelle Ergaenzungen und ausdruecklich erlaubte Overrides
Konflikte duerfen nicht stillschweigend ueberschrieben werden.
## Sicherheitsregeln
Produktive ERP-Daten, lokale Quelldaten, generierte Dateien und Reports sind per `.gitignore` ausgeschlossen. Nur reduzierte oder anonymisierte Beispiele werden versioniert.
## Aktueller Stand
Phase 0: Struktur, Dokumentation, Fixtures, minimale Python-Bausteine und Tests.
## Offene fachliche Fragen
Siehe `docs/data-model.md` und `docs/merge-rules.md`.
## Naechste Schritte
- Importer fuer RollCalc-JSON, ERP-CSV und manuelle CSV-Datei implementieren.
- Validierungs- und Merge-Regeln produktiv ausbauen.
- Reports und deterministischen Export ergaenzen.
## Wichtige Dateien
- `docs/data-dictionary.md`
- `docs/merge-rules.md`
- `tests/fixtures/`
- `src/article_data_manager/`
+98
View File
@@ -0,0 +1,98 @@
# Article Data Manager
Article Data Manager ist ein eigenständiges Python-Projekt zur Pflege, Validierung und späteren Erzeugung der statischen `article-data.json` für den bestehenden Roll Calculator.
Status: frühe Entwicklungsphase. Phase 0 legt Struktur, Dokumentation, Beispieldaten und erste technische Leitplanken an. Der produktive Merge-Prozess und eine Weboberfläche sind noch nicht implementiert.
## Problemstellung
Die Artikeldaten für RollCalc sollen langfristig aus mehreren Quellen nachvollziehbar zusammengeführt werden:
- bestehende RollCalc-Artikeldaten
- ERP-Export im CSV-Format
- manuell gepflegte Zusatz- und Korrekturdaten
- später weitere technische oder produktionsbezogene Quellen
Das Projekt trennt Quelldaten, manuelle Daten und generierte Ergebnisse, damit keine stillen Datenkorrekturen oder unklare Überschreibungen entstehen.
## Abgrenzung zum Roll Calculator
Der Roll Calculator bleibt externer Konsument der erzeugten `article-data.json`. Article Data Manager wird nicht in das RollCalc-Repository integriert. Importlogik, Datenvalidierung, Merge-Regeln und Reports werden hier entwickelt, damit RollCalc nicht mit Datenpflege- und ERP-Komplexität belastet wird.
## Datenfluss
```text
ERP-Export
+
bestehende RollCalc-Daten
+
manuelle Zusatzdaten
|
v
Import -> Normalisierung -> Validierung -> Merge -> Export
|
+--> Reports
|
+--> article-data.json fuer RollCalc
```
## Verzeichnisstruktur
```text
src/article_data_manager/ Python-Paket
tests/ Tests und nicht vertrauliche Fixtures
examples/ reduzierte Beispieldaten
data/source/ lokale echte Quelldaten, nicht versioniert
data/manual/ lokale manuelle Daten, nicht versioniert
data/generated/ generierte Artefakte, nicht versioniert
data/reports/ Reports, nicht versioniert
docs/ Architektur, Datenmodell und Regeln
scripts/ spaetere Hilfsskripte
```
## Ausgangsdaten und Analyse
Die lokalen Ausgangsdateien wurden unter `incoming/` gefunden und werden nicht versioniert:
- `incoming/article-data_.json`
- `incoming/Liste Schluesseldaten Produktion.csv`
Beobachtungen:
- RollCalc-JSON: 274 Artikel, Felder `nr`, `name`, `thickness`, `area_weight`, `core_type`.
- `nr` ist im JSON durchgehend String; mindestens eine Artikelnummer beginnt mit `0`.
- ERP-CSV: 1786 Datenzeilen, UTF-8 mit BOM, Delimiter `,`.
- ERP-CSV enthaelt deutsche Dezimalzahlen wie `"6,00"` und Werte mit Punkt wie `1.548`.
- `SL_ITEM_NO` ist in allen ERP-Zeilen befuellt; mindestens eine Artikelnummer beginnt mit `0`.
- Es wurden 247 Artikelnummern mit mehrfachen ERP-Treffern erkannt. Die fachliche Bedeutung ist offen.
## Installation
Voraussetzung: Python 3.12 oder neuer.
```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
```
## Geplante CLI-Nutzung
```bash
article-data-manager inspect-csv data/source/erp/production-key-data.csv
article-data-manager merge
article-data-manager validate data/generated/article-data.json
```
Die CLI-Kommandos sind in Phase 0 nur als Platzhalter vorgesehen.
## Tests
```bash
ruff check .
pytest
```
## Umgang mit produktiven ERP-Daten
Produktive ERP-Exporte, RollCalc-Quelldateien, manuelle Arbeitsdateien, Reports und generierte Dateien duerfen nicht ungeprueft in Git eingecheckt werden. Versioniert werden nur anonymisierte oder reduzierte Beispiele und Fixtures.
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+20
View File
@@ -0,0 +1,20 @@
# ADR 0001 - Eigenstaendiges Repository
## Status
Akzeptiert
## Entscheidung
Der Article Data Manager wird als eigenstaendiges Projekt gefuehrt und nicht in das RollCalc-Repository integriert.
## Begruendung
- getrennte Verantwortlichkeiten
- unabhaengige Entwicklung
- Schutz des RollCalc vor Import- und Datenpflegekomplexitaet
- spaetere Wiederverwendung durch weitere Systeme
## Konsequenzen
RollCalc konsumiert nur ein definiertes Exportartefakt. Schnittstellen und Formatkompatibilitaet muessen dokumentiert und getestet werden.
+17
View File
@@ -0,0 +1,17 @@
# ADR 0002 - Trennung der Datenquellen
## Status
Akzeptiert
## Entscheidung
ERP-Daten, bestehende RollCalc-Daten, manuelle Daten und generierte Daten werden physisch und logisch getrennt.
## Begruendung
Die Trennung macht Herkunft, Verantwortlichkeit und Konflikte nachvollziehbar. Sie reduziert das Risiko, produktive Quelldaten versehentlich zu veraendern oder generierte Ergebnisse als Primaerdaten zu behandeln.
## Konsequenzen
Importer und Merge-Logik muessen Quelle und Prioritaet explizit beruecksichtigen. Produktive Datenverzeichnisse werden nicht standardmaessig versioniert.
+19
View File
@@ -0,0 +1,19 @@
# ADR 0003 - Artikelnummer als Zeichenkette
## Status
Akzeptiert
## Entscheidung
Artikelnummern werden durchgaengig als Strings behandelt.
## Begruendung
- fuehrende Nullen
- keine mathematische Bedeutung
- moegliche zukuenftige alphanumerische Werte
## Konsequenzen
Importer duerfen Artikelnummern nicht in numerische Typen konvertieren. Normalisierung darf fuehrende Nullen nicht entfernen.
@@ -0,0 +1,17 @@
# ADR 0004 - Generierte Datei ist kein Primaerdatenbestand
## Status
Akzeptiert
## Entscheidung
Die erzeugte `article-data.json` ist ein Build-Artefakt. Aenderungen duerfen nicht direkt ausschliesslich in dieser Datei vorgenommen werden.
## Begruendung
Primaere Aenderungen gehoeren in ERP, bestehende RollCalc-Quelldaten oder manuelle Zusatzdaten. Nur so bleiben Herkunft, Validierung und Konflikte nachvollziehbar.
## Konsequenzen
`data/generated/article-data.json` wird nicht standardmaessig versioniert. Relevante Aenderungen muessen reproduzierbar aus den Quellen erzeugt werden.
+3
View File
@@ -0,0 +1,3 @@
# Architecture Decision Records
ADRs dokumentieren zentrale Architekturentscheidungen. Neue ADRs werden fortlaufend nummeriert und sollen Entscheidung, Kontext und Konsequenzen enthalten.
+58
View File
@@ -0,0 +1,58 @@
# Architektur
## Systemkontext
Article Data Manager ist ein lokales Werkzeug zur Erzeugung einer RollCalc-kompatiblen `article-data.json`. RollCalc ist ein externer Konsument und bleibt technisch getrennt.
## Verantwortlichkeiten
- Import vorhandener RollCalc-Artikeldaten.
- Import eines ERP-CSV-Exports.
- Import manuell gepflegter Zusatzdaten.
- Normalisierung technischer Formate, ohne fachliche Bedeutungen zu erraten.
- Validierung und Konflikterkennung.
- Deterministischer Export fuer RollCalc.
- Reports fuer Treffer, Nicht-Treffer, Konflikte und Validierungsfehler.
## Modulgrenzen
- `models`: interne Datenmodelle.
- `importers`: Lesen von JSON- und CSV-Quellen.
- `validation`: Format-, Typ- und Fachvalidierung.
- `merge`: Merge-Regeln, Konflikterkennung und Prioritaeten.
- `exporters`: Ausgabe der RollCalc-kompatiblen JSON-Datei.
- `cli`: spaetere Kommandozeile.
## Datenfluss
```text
data/source/erp/production-key-data.csv
data/source/rollcalc/article-data.json
data/manual/article-manual-data.csv
|
v
Import -> Normalisierung -> Validierung -> Merge -> Export
|
+--> data/reports/
+--> data/generated/article-data.json
```
## Datentrennung
Quelldaten, manuelle Daten und generierte Daten bleiben physisch und logisch getrennt. Produktive Dateien in `data/` und `incoming/` werden nicht versioniert.
## Erweiterbarkeit
Weitere Quellen sollen als eigene Importer angebunden werden. Jede Quelle muss Originalfeldnamen, Einheiten, Prioritaet und Konfliktverhalten dokumentieren.
## Spaetere Weboberflaeche
Eine lokale Weboberflaeche ist erst nach einem reproduzierbaren CLI-Merge sinnvoll. Sie soll manuelle Zusatzdaten pflegen, ERP-Werte anzeigen und Eingaben validieren, aber keine Quelldaten direkt ueberschreiben.
## Spaetere Datenhaltung
Phase 4 kann SQLite als lokale Datenbasis einfuehren. PostgreSQL ist nur sinnvoll, wenn Mehrbenutzerbetrieb, zentrale Freigaben oder Integrationen dies tatsaechlich erfordern.
## RollCalc als externer Konsument
RollCalc konsumiert ausschliesslich die exportierte `article-data.json`. Aenderungen am Ausgabeformat muessen mit der RollCalc-Kompatibilitaet abgeglichen und dokumentiert werden.
+71
View File
@@ -0,0 +1,71 @@
# Datenwoerterbuch
## Zielmodell
| Feld | Bedeutung | Datentyp | Einheit | Quelle | Pflichtstatus | Zulaessige Werte | Override-Regel | Validierungsregel |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `nr` | Artikelnummer | String | keine | Matching-Schluessel aus allen Quellen | Pflicht | nicht leer, fuehrende Nullen erlaubt | kein Override | muss String bleiben, trimmen erlaubt |
| `name` | Artikelbezeichnung | String oder null | keine | ERP, alternativ RollCalc | offen fuer Export | beliebiger Text | manuell nur nach expliziter Freigabe | nicht stillschweigend bei Konflikt ueberschreiben |
| `width` | fuer RollCalc relevante Produktbreite | Float oder null | m | vermutlich ERP, genaue Spalte offen | optional bis bestaetigt | >= 0, Einheit zu klaeren | kein manueller Override in Phase 0 | Dezimalformat erkennen, Einheit dokumentieren |
| `thickness` | Dicke fuer Berechnung | Float oder null | mm | RollCalc | optional bis RollCalc-Pflichten geklaert | >= 0 | manueller Override noch offen | `0` nicht automatisch als unbekannt werten |
| `area_weight` | Flaechengewicht | Float oder null | g/m2 | RollCalc oder manuell | optional | >= 0 | manueller Override vorgesehen | `0` nicht automatisch als unbekannt werten |
| `core_diameter` | Kerndurchmesser | Integer oder null | mm | manuell | optional | positive mm-Werte | manuelle Pflege vorgesehen | muss getrennt von `core_type` bleiben |
| `core_type` | Kernart | String oder null | keine | manuell; bisher RollCalc numerisch | optional | vorlaeufig `cardboard`, `steel`, weitere offen | manuelle Pflege vorgesehen | bisherige numerische Bedeutung offen |
## Manuelle CSV-Quelle
Vorgesehene erste Struktur:
```csv
nr;core_diameter;core_type;area_weight;comment
214700;100;cardboard;;Standard-Pappkern
229800;168;steel;;Grosser Stahlkern
```
`comment` dient als fachlicher Hinweis und wird nicht automatisch in die RollCalc-Ausgabe uebernommen.
## Beobachtete ERP-CSV-Struktur
Lokale Datei: `incoming/Liste Schluesseldaten Produktion.csv`, nicht versioniert.
- Encoding-Indiz: UTF-8 mit BOM.
- Delimiter: `,`.
- Datenzeilen: 1786.
- Mehrfachtreffer: 247 Artikelnummern kommen mehrfach vor.
- Dezimalformate: Kommawerte wie `"6,00"` und Punktwerte wie `1.548` wurden beobachtet.
Originalspalten:
| Originalspalte | Beobachtung | Vorlaeufige Beschreibung |
| --- | --- | --- |
| `SL_COMPANY_NO` | befuellt | offen; vermutlich Mandant/Firma |
| `PHG` | befuellt | offen |
| `PG` | befuellt | offen |
| `SL_ITEM_NO` | 1786/1786 befuellt | Artikelnummer, fachlich als Matching-Kandidat |
| `SL_ITEM_TEXT` | 1786/1786 befuellt | Artikeltext, fachliche Verwendung offen |
| `SL_QUANTITY_UNIT2` | befuellt | offen; Mengeneinheit |
| `SL_PART_CATEGORY_NO2` | befuellt | offen |
| `SL_PLANNING_FLAG2` | befuellt | offen |
| `qm` | teilweise leer | offen; Einheit/Bezugsmenge zu klaeren |
| `ROP_PRODUCT_WIDTH` | 775/1786 befuellt | Breitenkandidat; Bedeutung und Einheit offen |
| `ROP_RATE_OF_PRODUCTION` | 775/1786 befuellt | offen; Produktionsrate |
| `WPL_WORKPLACE_TEXT` | 893/1786 befuellt | offen; Produktionsarbeitsplatz |
| `SL_NUMBER_OF_PERSON_MASCHINE` | teilweise befuellt | offen |
| `SL_MINIMUM_PRODUCTION_QUANTITY` | beobachtet | offen |
| `SL_PRODUCTION_SPEED` | 893/1786 befuellt | offen; Produktionsgeschwindigkeit |
| `kg_qm` | 1595/1786 befuellt | offen; moeglicher Gewichtsbezug, Einheit zu klaeren |
| `SL_PRODUCTION_REJECT_QUOTE` | 1786/1786 befuellt | offen; Ausschussquote |
| `SL_SETUP_TIME_MASCHINE` | 893/1786 befuellt | offen; Ruestzeit |
| `SL_CALCULATED_LOT_SIZE` | beobachtet | offen |
| `PRCO_CALCULATION_DATE` | beobachtet | offen; Kalkulationsdatum |
| `PRCO_CALCULATION_TYPE` | beobachtet | offen |
| `PRCO_CALCULATION_NO` | beobachtet | offen |
| `PRCO_PRODUCTION_COSTS` | beobachtet | offen |
| `PRCO_COST_OF_MATERIAL_VARIABLE` | beobachtet | offen |
| `PRCO_MATERIAL_OVERHEAD_COSTS` | beobachtet | offen |
| `PRCO_EXTERNAL_COSTS` | beobachtet | offen |
| `PRCO_MANUFACTURING_COSTS_FIXED_VARIABLE` | beobachtet | offen |
| `PRCO_MANUFACTURING_OVERHEAD_COSTS` | beobachtet | offen |
| `PRCO_PRODUCTION_COSTS_ADJUSTMENTS` | beobachtet | offen |
Alle vorlaeufigen Beschreibungen sind ohne fachliche Freigabe nicht als Mapping-Entscheidung zu verwenden.
+82
View File
@@ -0,0 +1,82 @@
# Datenmodell
## Internes kanonisches Artikelmodell
Phase 0 definiert ein flaches Modell:
| Feld | Typ | Einheit | Status |
| --- | --- | --- | --- |
| `nr` | `str` | keine | Pflichtfeld |
| `name` | `str | null` | keine | optional |
| `width` | `float | null` | m | optional |
| `thickness` | `float | null` | mm | optional |
| `area_weight` | `float | null` | g/m2 | optional |
| `core_diameter` | `int | null` | mm | optional |
| `core_type` | `str | null` | keine | optional |
`nr` wird immer als String behandelt. Fuehrende Nullen bleiben erhalten.
## RollCalc-Ausgabemodell
Das geplante Ausgabemodell ist RollCalc-kompatibel und bleibt flach:
```json
{
"nr": "214700",
"name": "Stex H 751 (Tfix 751), 6,00 x 50 m",
"width": 6.0,
"thickness": 6.722,
"area_weight": 0.0,
"core_diameter": 100,
"core_type": "cardboard"
}
```
## Null-Werte und unbekannte Werte
Fehlende Werte sollen intern bevorzugt als `null` beziehungsweise `None` modelliert werden. Ob RollCalc `null`, fehlende Felder oder `0` erwartet, ist offen und darf nicht ohne dokumentierte Entscheidung geaendert werden.
Der Wert `0` darf nicht automatisch als unbekannt interpretiert werden. Im vorhandenen RollCalc-JSON treten `area_weight: 0.0` und `core_type: 0.0` auf. Die fachliche Bedeutung ist offen.
## Beobachtete RollCalc-JSON-Struktur
Lokale Datei: `incoming/article-data_.json`, nicht versioniert.
- 274 Artikel.
- Felder: `nr`, `name`, `thickness`, `area_weight`, `core_type`.
- `nr`: 274 Strings.
- `name`: 274 Strings.
- `thickness`: 273 Floats, 1 Integer.
- `area_weight`: 273 Floats, 1 Integer.
- `core_type`: 274 Floats.
- Mindestens eine Artikelnummer beginnt mit `0`.
## Herkunftsmetadaten
Spaeter koennen Herkunftsmetadaten pro Feld sinnvoll werden:
- Quelle
- Importzeitpunkt
- Originalfeld
- Originalwert
- Normalisierungsregel
- manueller Freigabestatus
Diese Metadaten gehoeren nicht zwingend in die RollCalc-Ausgabedatei.
## Offene fachliche Fragen
- Welche ERP-Spalte enthaelt eindeutig die fuer RollCalc relevante Produktbreite?
- Ist die ERP-Breite Nennbreite, Produktionsbreite oder Verkaufsbreite?
- Welche Einheit verwendet die ERP-Spalte?
- Koennen Artikelnummern im ERP mehrfach vorkommen?
- Falls ja: aus welchem fachlichen Grund?
- Welcher ERP-Datensatz ist bei Mehrfachtreffern massgeblich?
- Welche Bedeutung hat das bisherige Feld `core_type` mit numerischen Werten?
- Soll `core_type` kuenftig Textwerte oder Codes verwenden?
- Darf `0.0` bei `area_weight`, `thickness` oder `core_type` als unbekannt interpretiert werden?
- Welche Felder benoetigt RollCalc zwingend?
- Unterstuetzt RollCalc `null`?
- Soll der Artikelname aus dem ERP uebernommen oder nur zum Abgleich verwendet werden?
- Welche manuellen Felder duerfen ERP-Werte ueberschreiben?
- Wie wird die Qualitaet oder Freigabe manueller Daten gekennzeichnet?
+29
View File
@@ -0,0 +1,29 @@
# Entwicklung
## Voraussetzungen
- Python 3.12 oder neuer
- lokale virtuelle Umgebung
## Setup
```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
```
## Checks
```bash
ruff check .
pytest
```
## Phase-0-Grenzen
Phase 0 enthaelt keine vollstaendige Merge-Implementierung und keine Weboberflaeche. Erlaubt sind kleine Interfaces, Tests und Hilfsfunktionen, die die geplante Architektur absichern.
## Abhaengigkeiten
Die Standardbibliothek wird bevorzugt. Neue Runtime-Abhaengigkeiten brauchen einen konkreten Nutzen und muessen in der Dokumentation nachvollziehbar sein.
+64
View File
@@ -0,0 +1,64 @@
# Merge-Regeln
## Matching
Primaerer Matching-Schluessel ist die Artikelnummer:
- RollCalc: `nr`
- ERP: beobachtet `SL_ITEM_NO`
- manuelle Daten: `nr`
Artikelnummern werden als Strings behandelt. Fuehrende Nullen bleiben erhalten. Normalisierung in Phase 0: nur fuehrende und nachgestellte Leerzeichen entfernen.
## Merge-Reihenfolge
1. ERP-Stammdaten
2. bestehende RollCalc-Berechnungsdaten
3. manuelle Ergaenzungen und explizite Korrekturen
Diese Reihenfolge ist eine Prioritaetsregel, aber keine Erlaubnis fuer stille Konfliktbereinigung.
## Mehrfache ERP-Treffer
Wenn dieselbe Artikelnummer mehrfach im ERP-Export vorkommt, darf Phase 1 keinen Datensatz stillschweigend auswaehlen. Der Artikel muss im Report erscheinen. Falls mehrere ERP-Zeilen fachlich gueltig sind, braucht es eine bestaetigte Auswahlregel.
## Fehlende Treffer
- Nur in RollCalc: Artikel bleibt als moeglicher Bestandsartikel sichtbar und wird reportet.
- Nur im ERP: Artikel wird als neuer oder nicht in RollCalc gepflegter Artikel reportet.
- Nur manuell: Artikel wird reportet, weil kein gesicherter Stammdatensatz vorhanden ist.
## Konflikte
Widerspruechliche Werte zwischen Quellen werden nicht stillschweigend ueberschrieben. Konflikte muessen mindestens Quelle, Artikelnummer, Feld, Quellwert und Zielwert enthalten.
## Feldprioritaeten
- ERP: Stammdaten, soweit Feld und Bedeutung bestaetigt sind.
- RollCalc: bestehende Berechnungsparameter wie `thickness`, `area_weight` und bisherige Kerninformationen.
- Manuell: Zusatzdaten und explizit freigegebene Overrides.
## Erlaubte manuelle Overrides
In Phase 0 vorgesehen:
- `core_diameter`
- `core_type`
- `area_weight`
Weitere Overrides muessen im Datenwoerterbuch dokumentiert werden.
## Reporting
Geplante Reports:
- `data/reports/merge-report.csv`
- `data/reports/unmatched-rollcalc-articles.csv`
- `data/reports/unmatched-erp-articles.csv`
- `data/reports/validation-report.json`
Reports koennen sensible Inhalte enthalten und werden nicht versioniert.
## Deterministische Ausgabe
Die exportierte JSON-Datei soll stabil sortiert werden, voraussichtlich nach normalisierter Artikelnummer. Das genaue Sortierverhalten wird in Phase 1 getestet.
+58
View File
@@ -0,0 +1,58 @@
# Roadmap
## Phase 0 - Repository und Datenverstaendnis
- Projektstruktur
- Dokumentation
- Beispieldaten
- Analyse der vorhandenen JSON- und CSV-Struktur
- Datenwoerterbuch
- Architekturentscheidungen
## Phase 1 - Reproduzierbarer CLI-Merge
- JSON-Importer
- ERP-CSV-Importer
- Import manueller CSV-Daten
- Normalisierung
- Validierung
- Merge
- Export der RollCalc-kompatiblen `article-data.json`
- Konflikt- und Trefferreports
- Unit- und Integrationstests
## Phase 2 - Qualitaetskontrolle
- Schema-Validierung
- Dublettenpruefung
- Einheitenpruefung
- Vergleich mit vorheriger Ausgabe
- Aenderungsreport
- Schutz vor unbeabsichtigtem Datenverlust
- Exit-Codes fuer Automatisierung
## Phase 3 - Einfache Pflegeoberflaeche
- lokale Weboberflaeche
- Suche nach Artikelnummer oder Name
- Anzeige der ERP-Werte
- Bearbeitung manueller Zusatzfelder
- Validierung bei Eingabe
- Aenderungsprotokoll
## Phase 4 - Erweiterte Datenhaltung
- SQLite als lokale Datenbasis
- Importhistorie
- Datenherkunft
- Aenderungsverfolgung
- Freigabestatus
- Benutzer- und Rollenmodell nur bei tatsaechlichem Bedarf
## Phase 5 - Integration
- definierter Export fuer RollCalc
- optionales Release-Artefakt
- optionaler automatisierter Import im RollCalc-Deployment
- Schnittstelle zu weiteren internen Werkzeugen
- moegliche Nutzung als Datenbasis fuer ein spaeteres Produktionsexpertensystem
+23
View File
@@ -0,0 +1,23 @@
# Sicherheit und Datenschutz
ERP-Daten koennen interne oder vertrauliche Inhalte enthalten. Produktive Exporte sollen nicht standardmaessig versioniert werden.
## Regeln
- `incoming/` ist nicht versioniert.
- `data/source/`, `data/manual/`, `data/generated/` und `data/reports/` sind fuer echte Arbeitsdaten ausgeschlossen.
- Beispieldaten muessen anonymisiert oder reduziert sein.
- Logs und Reports koennen Artikeltexte, Kosten, Produktionsdaten oder Konflikte enthalten und sind ebenfalls sensibel.
- Es ist keine Cloud-Abhaengigkeit erforderlich.
- Das Projekt soll lokal ausfuehrbar bleiben.
## Praktische Hinweise
Vor jedem Commit:
```bash
git status
git diff --cached
```
Keine produktiven ERP- oder RollCalc-Exporte nach `examples/` oder `tests/fixtures/` kopieren.
+5
View File
@@ -0,0 +1,5 @@
nr;core_diameter;core_type;area_weight;comment
214700;100;cardboard;;Standard-Pappkern
000123;76;cardboard;355.5;Manueller Override des Flaechengewichts
229800;168;steel;;Grosser Stahlkern
777777;100;cardboard;;Konfliktfall fuer Tests
1 nr core_diameter core_type area_weight comment
2 214700 100 cardboard Standard-Pappkern
3 000123 76 cardboard 355.5 Manueller Override des Flaechengewichts
4 229800 168 steel Grosser Stahlkern
5 777777 100 cardboard Konfliktfall fuer Tests
+6
View File
@@ -0,0 +1,6 @@
SL_COMPANY_NO,PHG,PG,SL_ITEM_NO,SL_ITEM_TEXT,SL_QUANTITY_UNIT2,SL_PART_CATEGORY_NO2,SL_PLANNING_FLAG2,qm,ROP_PRODUCT_WIDTH,ROP_RATE_OF_PRODUCTION,WPL_WORKPLACE_TEXT,SL_NUMBER_OF_PERSON_MASCHINE,SL_MINIMUM_PRODUCTION_QUANTITY,SL_PRODUCTION_SPEED,kg_qm,SL_PRODUCTION_REJECT_QUOTE,SL_SETUP_TIME_MASCHINE,SL_CALCULATED_LOT_SIZE,PRCO_CALCULATION_DATE,PRCO_CALCULATION_TYPE,PRCO_CALCULATION_NO,PRCO_PRODUCTION_COSTS,PRCO_COST_OF_MATERIAL_VARIABLE,PRCO_MATERIAL_OVERHEAD_COSTS,PRCO_EXTERNAL_COSTS,PRCO_MANUFACTURING_COSTS_FIXED_VARIABLE,PRCO_MANUFACTURING_OVERHEAD_COSTS,PRCO_PRODUCTION_COSTS_ADJUSTMENTS
001,Example PHG,Example PG,214700,"Example Mat A, 6,00 x 50 m",QM,B,J,"300,00","6,00","4,30",K 7,"3,0",50.000,1.548,"0,774","9,0%","0,60",13.600,17.07.2026,K,26,"1,69","1,36","0,03","0,00","0,29","0,02","0,00"
001,Example PHG,Example PG,000123,"Example Leading Zero, 1.20 x 10 m",QM,B,J,"12.50","1.20","2.50",K 1,"1,0",1.000,100,"0.350","5,0%","0.50",1.000,17.07.2026,K,27,"1.00","0.80","0.02","0.00","0.15","0.03","0.00"
001,Example PHG,Example PG,800001,"Example ERP Only",QM,B,J,"","2,00","",K 2,"",0,invalid,"","5,0%","",0,17.07.2026,K,28,"","","","","","",""
001,Example PHG,Example PG,777777,"Example Duplicate A",QM,B,J,"100,00","1,00","3,00",K 3,"2,0",500,300,"0,500","6,0%","1,00",2.000,17.07.2026,K,29,"1,10","0,90","0,02","0,00","0,16","0,02","0,00"
001,Example PHG,Example PG,777777,"Example Duplicate B Conflict",QM,B,J,"100,00","1,10","3,00",K 4,"2,0",500,300,"0,500","6,0%","1,00",2.000,17.07.2026,K,30,"1,10","0,90","0,02","0,00","0,16","0,02","0,00"
1 SL_COMPANY_NO PHG PG SL_ITEM_NO SL_ITEM_TEXT SL_QUANTITY_UNIT2 SL_PART_CATEGORY_NO2 SL_PLANNING_FLAG2 qm ROP_PRODUCT_WIDTH ROP_RATE_OF_PRODUCTION WPL_WORKPLACE_TEXT SL_NUMBER_OF_PERSON_MASCHINE SL_MINIMUM_PRODUCTION_QUANTITY SL_PRODUCTION_SPEED kg_qm SL_PRODUCTION_REJECT_QUOTE SL_SETUP_TIME_MASCHINE SL_CALCULATED_LOT_SIZE PRCO_CALCULATION_DATE PRCO_CALCULATION_TYPE PRCO_CALCULATION_NO PRCO_PRODUCTION_COSTS PRCO_COST_OF_MATERIAL_VARIABLE PRCO_MATERIAL_OVERHEAD_COSTS PRCO_EXTERNAL_COSTS PRCO_MANUFACTURING_COSTS_FIXED_VARIABLE PRCO_MANUFACTURING_OVERHEAD_COSTS PRCO_PRODUCTION_COSTS_ADJUSTMENTS
2 001 Example PHG Example PG 214700 Example Mat A, 6,00 x 50 m QM B J 300,00 6,00 4,30 K 7 3,0 50.000 1.548 0,774 9,0% 0,60 13.600 17.07.2026 K 26 1,69 1,36 0,03 0,00 0,29 0,02 0,00
3 001 Example PHG Example PG 000123 Example Leading Zero, 1.20 x 10 m QM B J 12.50 1.20 2.50 K 1 1,0 1.000 100 0.350 5,0% 0.50 1.000 17.07.2026 K 27 1.00 0.80 0.02 0.00 0.15 0.03 0.00
4 001 Example PHG Example PG 800001 Example ERP Only QM B J 2,00 K 2 0 invalid 5,0% 0 17.07.2026 K 28
5 001 Example PHG Example PG 777777 Example Duplicate A QM B J 100,00 1,00 3,00 K 3 2,0 500 300 0,500 6,0% 1,00 2.000 17.07.2026 K 29 1,10 0,90 0,02 0,00 0,16 0,02 0,00
6 001 Example PHG Example PG 777777 Example Duplicate B Conflict QM B J 100,00 1,10 3,00 K 4 2,0 500 300 0,500 6,0% 1,00 2.000 17.07.2026 K 30 1,10 0,90 0,02 0,00 0,16 0,02 0,00
@@ -0,0 +1,23 @@
[
{
"nr": "214700",
"name": "Example Mat A, 6,00 x 50 m",
"thickness": 6.722,
"area_weight": 0.0,
"core_type": 0.0
},
{
"nr": "000123",
"name": "Example Leading Zero, 1,20 x 10 m",
"thickness": 1.5,
"area_weight": 350.0,
"core_type": 0.0
},
{
"nr": "900001",
"name": "Example RollCalc Only",
"thickness": 2.0,
"area_weight": 0.0,
"core_type": 0.0
}
]
+37
View File
@@ -0,0 +1,37 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "article-data-manager"
version = "0.1.0"
description = "Tools for preparing Roll Calculator article data from ERP, existing RollCalc, and manual sources."
readme = "README.md"
requires-python = ">=3.12"
license = { text = "MIT" }
authors = [{ name = "Article Data Manager contributors" }]
dependencies = []
[project.optional-dependencies]
dev = [
"pytest>=8",
"ruff>=0.5",
]
[project.scripts]
article-data-manager = "article_data_manager.cli.main:main"
[tool.hatch.build.targets.wheel]
packages = ["src/article_data_manager"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = []
+1
View File
@@ -0,0 +1 @@
+5
View File
@@ -0,0 +1,5 @@
"""Article Data Manager package."""
__all__ = ["__version__"]
__version__ = "0.1.0"
+1
View File
@@ -0,0 +1 @@
"""Command line interface package."""
+33
View File
@@ -0,0 +1,33 @@
"""Minimal Phase 0 CLI entry point."""
from __future__ import annotations
import argparse
from pathlib import Path
from article_data_manager.importers.csv_inspection import inspect_csv
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="article-data-manager")
subparsers = parser.add_subparsers(dest="command")
inspect_parser = subparsers.add_parser("inspect-csv", help="Inspect CSV structure.")
inspect_parser.add_argument("path", type=Path)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.command == "inspect-csv":
result = inspect_csv(args.path)
print(f"delimiter={result.delimiter!r}")
print(f"rows={result.row_count}")
print("columns=" + ",".join(result.columns))
return 0
parser.print_help()
return 0
@@ -0,0 +1 @@
"""Exporter placeholders."""
@@ -0,0 +1 @@
"""Importer helpers."""
@@ -0,0 +1,29 @@
"""Small CSV inspection helpers used in Phase 0 tests and documentation."""
from __future__ import annotations
import csv
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True, slots=True)
class CsvInspection:
delimiter: str
columns: tuple[str, ...]
row_count: int
def inspect_csv(path: Path, *, delimiters: str = ",;\t") -> CsvInspection:
"""Inspect delimiter, columns, and row count without interpreting business meaning."""
with path.open("r", encoding="utf-8-sig", newline="") as csv_file:
sample = csv_file.read(4096)
csv_file.seek(0)
dialect = csv.Sniffer().sniff(sample, delimiters=delimiters)
reader = csv.DictReader(csv_file, dialect=dialect)
return CsvInspection(
delimiter=dialect.delimiter,
columns=tuple(reader.fieldnames or ()),
row_count=sum(1 for _ in reader),
)
@@ -0,0 +1 @@
"""Merge planning package."""
+7
View File
@@ -0,0 +1,7 @@
"""Declared Phase 0 merge constants.
The complete merge implementation belongs to Phase 1.
"""
MERGE_PRIORITY: tuple[str, ...] = ("erp", "rollcalc", "manual")
MANUAL_OVERRIDE_FIELDS: frozenset[str] = frozenset({"area_weight", "core_diameter", "core_type"})
@@ -0,0 +1,5 @@
"""Domain models."""
from article_data_manager.models.article import Article
__all__ = ["Article"]
@@ -0,0 +1,26 @@
"""Canonical article model for Phase 0 assumptions."""
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Article:
"""Flat RollCalc-compatible article shape.
Missing optional values are represented as None internally. Export compatibility
with RollCalc must be confirmed before changing existing production behavior.
"""
nr: str
name: str | None = None
width: float | None = None
thickness: float | None = None
area_weight: float | None = None
core_diameter: int | None = None
core_type: str | None = None
def __post_init__(self) -> None:
if not isinstance(self.nr, str):
raise TypeError("Article number 'nr' must be a string.")
if self.nr == "":
raise ValueError("Article number 'nr' must not be empty.")
@@ -0,0 +1,5 @@
"""Validation helpers."""
from article_data_manager.validation.article_number import normalize_article_number
__all__ = ["normalize_article_number"]
@@ -0,0 +1,12 @@
"""Article number normalization."""
def normalize_article_number(value: str) -> str:
"""Trim surrounding whitespace while preserving leading zeros and text content."""
if not isinstance(value, str):
raise TypeError("Article number must be provided as a string.")
normalized = value.strip()
if normalized == "":
raise ValueError("Article number must not be empty.")
return normalized
@@ -0,0 +1,16 @@
"""Number parsing helpers for CSV values."""
def parse_decimal(value: str) -> float | None:
"""Parse simple German or English decimal strings.
Empty strings map to None. Thousands separators are intentionally not inferred
in Phase 0 because ERP semantics must be confirmed first.
"""
cleaned = value.strip()
if cleaned == "":
return None
if "," in cleaned and "." in cleaned:
raise ValueError(f"Ambiguous decimal value: {value!r}")
return float(cleaned.replace(",", "."))
+5
View File
@@ -0,0 +1,5 @@
nr;core_diameter;core_type;area_weight;comment
214700;100;cardboard;;Standard-Pappkern
000123;76;cardboard;355.5;Manueller Override des Flaechengewichts
229800;168;steel;;Grosser Stahlkern
777777;100;cardboard;;Konfliktfall fuer Tests
1 nr core_diameter core_type area_weight comment
2 214700 100 cardboard Standard-Pappkern
3 000123 76 cardboard 355.5 Manueller Override des Flaechengewichts
4 229800 168 steel Grosser Stahlkern
5 777777 100 cardboard Konfliktfall fuer Tests
+6
View File
@@ -0,0 +1,6 @@
SL_COMPANY_NO,PHG,PG,SL_ITEM_NO,SL_ITEM_TEXT,SL_QUANTITY_UNIT2,SL_PART_CATEGORY_NO2,SL_PLANNING_FLAG2,qm,ROP_PRODUCT_WIDTH,ROP_RATE_OF_PRODUCTION,WPL_WORKPLACE_TEXT,SL_NUMBER_OF_PERSON_MASCHINE,SL_MINIMUM_PRODUCTION_QUANTITY,SL_PRODUCTION_SPEED,kg_qm,SL_PRODUCTION_REJECT_QUOTE,SL_SETUP_TIME_MASCHINE,SL_CALCULATED_LOT_SIZE,PRCO_CALCULATION_DATE,PRCO_CALCULATION_TYPE,PRCO_CALCULATION_NO,PRCO_PRODUCTION_COSTS,PRCO_COST_OF_MATERIAL_VARIABLE,PRCO_MATERIAL_OVERHEAD_COSTS,PRCO_EXTERNAL_COSTS,PRCO_MANUFACTURING_COSTS_FIXED_VARIABLE,PRCO_MANUFACTURING_OVERHEAD_COSTS,PRCO_PRODUCTION_COSTS_ADJUSTMENTS
001,Example PHG,Example PG,214700,"Example Mat A, 6,00 x 50 m",QM,B,J,"300,00","6,00","4,30",K 7,"3,0",50.000,1.548,"0,774","9,0%","0,60",13.600,17.07.2026,K,26,"1,69","1,36","0,03","0,00","0,29","0,02","0,00"
001,Example PHG,Example PG,000123,"Example Leading Zero, 1.20 x 10 m",QM,B,J,"12.50","1.20","2.50",K 1,"1,0",1.000,100,"0.350","5,0%","0.50",1.000,17.07.2026,K,27,"1.00","0.80","0.02","0.00","0.15","0.03","0.00"
001,Example PHG,Example PG,800001,"Example ERP Only",QM,B,J,"","2,00","",K 2,"",0,invalid,"","5,0%","",0,17.07.2026,K,28,"","","","","","",""
001,Example PHG,Example PG,777777,"Example Duplicate A",QM,B,J,"100,00","1,00","3,00",K 3,"2,0",500,300,"0,500","6,0%","1,00",2.000,17.07.2026,K,29,"1,10","0,90","0,02","0,00","0,16","0,02","0,00"
001,Example PHG,Example PG,777777,"Example Duplicate B Conflict",QM,B,J,"100,00","1,10","3,00",K 4,"2,0",500,300,"0,500","6,0%","1,00",2.000,17.07.2026,K,30,"1,10","0,90","0,02","0,00","0,16","0,02","0,00"
1 SL_COMPANY_NO PHG PG SL_ITEM_NO SL_ITEM_TEXT SL_QUANTITY_UNIT2 SL_PART_CATEGORY_NO2 SL_PLANNING_FLAG2 qm ROP_PRODUCT_WIDTH ROP_RATE_OF_PRODUCTION WPL_WORKPLACE_TEXT SL_NUMBER_OF_PERSON_MASCHINE SL_MINIMUM_PRODUCTION_QUANTITY SL_PRODUCTION_SPEED kg_qm SL_PRODUCTION_REJECT_QUOTE SL_SETUP_TIME_MASCHINE SL_CALCULATED_LOT_SIZE PRCO_CALCULATION_DATE PRCO_CALCULATION_TYPE PRCO_CALCULATION_NO PRCO_PRODUCTION_COSTS PRCO_COST_OF_MATERIAL_VARIABLE PRCO_MATERIAL_OVERHEAD_COSTS PRCO_EXTERNAL_COSTS PRCO_MANUFACTURING_COSTS_FIXED_VARIABLE PRCO_MANUFACTURING_OVERHEAD_COSTS PRCO_PRODUCTION_COSTS_ADJUSTMENTS
2 001 Example PHG Example PG 214700 Example Mat A, 6,00 x 50 m QM B J 300,00 6,00 4,30 K 7 3,0 50.000 1.548 0,774 9,0% 0,60 13.600 17.07.2026 K 26 1,69 1,36 0,03 0,00 0,29 0,02 0,00
3 001 Example PHG Example PG 000123 Example Leading Zero, 1.20 x 10 m QM B J 12.50 1.20 2.50 K 1 1,0 1.000 100 0.350 5,0% 0.50 1.000 17.07.2026 K 27 1.00 0.80 0.02 0.00 0.15 0.03 0.00
4 001 Example PHG Example PG 800001 Example ERP Only QM B J 2,00 K 2 0 invalid 5,0% 0 17.07.2026 K 28
5 001 Example PHG Example PG 777777 Example Duplicate A QM B J 100,00 1,00 3,00 K 3 2,0 500 300 0,500 6,0% 1,00 2.000 17.07.2026 K 29 1,10 0,90 0,02 0,00 0,16 0,02 0,00
6 001 Example PHG Example PG 777777 Example Duplicate B Conflict QM B J 100,00 1,10 3,00 K 4 2,0 500 300 0,500 6,0% 1,00 2.000 17.07.2026 K 30 1,10 0,90 0,02 0,00 0,16 0,02 0,00
+23
View File
@@ -0,0 +1,23 @@
[
{
"nr": "214700",
"name": "Example Mat A, 6,00 x 50 m",
"thickness": 6.722,
"area_weight": 0.0,
"core_type": 0.0
},
{
"nr": "000123",
"name": "Example Leading Zero, 1,20 x 10 m",
"thickness": 1.5,
"area_weight": 350.0,
"core_type": 0.0
},
{
"nr": "900001",
"name": "Example RollCalc Only",
"thickness": 2.0,
"area_weight": 0.0,
"core_type": 0.0
}
]
@@ -0,0 +1,57 @@
import csv
import json
from collections import Counter
from pathlib import Path
from article_data_manager.merge.rules import MANUAL_OVERRIDE_FIELDS, MERGE_PRIORITY
def test_expected_documentation_files_exist() -> None:
expected = [
"README.md",
"AGENTS.md",
"PROJECT_KNOWLEDGE.md",
"docs/architecture.md",
"docs/data-model.md",
"docs/data-dictionary.md",
"docs/merge-rules.md",
"docs/security-and-data-protection.md",
"docs/roadmap.md",
"docs/adr/0001-separate-repository.md",
"docs/adr/0002-source-data-separation.md",
"docs/adr/0003-article-number-as-string.md",
"docs/adr/0004-generated-output-not-primary-data.md",
]
for file_name in expected:
assert Path(file_name).is_file()
def test_fixtures_cover_phase_zero_cases() -> None:
rollcalc = json.loads(Path("tests/fixtures/rollcalc-article-data.json").read_text())
with Path("tests/fixtures/production-key-data.csv").open(newline="") as csv_file:
erp_rows = list(csv.DictReader(csv_file))
with Path("tests/fixtures/article-manual-data.csv").open(newline="") as csv_file:
manual_rows = list(csv.DictReader(csv_file, delimiter=";"))
rollcalc_numbers = {row["nr"] for row in rollcalc}
erp_numbers = [row["SL_ITEM_NO"] for row in erp_rows]
manual_numbers = {row["nr"] for row in manual_rows}
assert "214700" in rollcalc_numbers and "214700" in erp_numbers
assert "900001" in rollcalc_numbers and "900001" not in erp_numbers
assert "800001" in erp_numbers and "800001" not in rollcalc_numbers
assert "000123" in rollcalc_numbers and "000123" in erp_numbers
assert Counter(erp_numbers)["777777"] == 2
assert "214700" in manual_numbers
assert "000123" in manual_numbers
assert any(row["SL_PRODUCTION_SPEED"] == "invalid" for row in erp_rows)
assert any(row["ROP_PRODUCT_WIDTH"] == "6,00" for row in erp_rows)
assert any(row["ROP_PRODUCT_WIDTH"] == "1.20" for row in erp_rows)
assert any(row["qm"] == "" for row in erp_rows)
assert len({row["ROP_PRODUCT_WIDTH"] for row in erp_rows if row["SL_ITEM_NO"] == "777777"}) == 2
def test_declared_merge_policy_is_phase_zero_contract() -> None:
assert MERGE_PRIORITY == ("erp", "rollcalc", "manual")
assert frozenset({"area_weight", "core_diameter", "core_type"}) == MANUAL_OVERRIDE_FIELDS
+14
View File
@@ -0,0 +1,14 @@
import pytest
from article_data_manager.models import Article
def test_article_number_is_string_and_preserves_leading_zero() -> None:
article = Article(nr="000123", name="Example")
assert article.nr == "000123"
def test_article_number_rejects_numeric_value() -> None:
with pytest.raises(TypeError):
Article(nr=123) # type: ignore[arg-type]
+12
View File
@@ -0,0 +1,12 @@
import pytest
from article_data_manager.validation import normalize_article_number
def test_normalize_article_number_trims_but_preserves_leading_zero() -> None:
assert normalize_article_number(" 000123 ") == "000123"
def test_normalize_article_number_rejects_non_string() -> None:
with pytest.raises(TypeError):
normalize_article_number(123) # type: ignore[arg-type]
+12
View File
@@ -0,0 +1,12 @@
from pathlib import Path
from article_data_manager.importers.csv_inspection import inspect_csv
def test_inspect_csv_detects_fixture_shape() -> None:
result = inspect_csv(Path("tests/fixtures/production-key-data.csv"))
assert result.delimiter == ","
assert result.row_count == 5
assert result.columns[:4] == ("SL_COMPANY_NO", "PHG", "PG", "SL_ITEM_NO")
assert "ROP_PRODUCT_WIDTH" in result.columns
+20
View File
@@ -0,0 +1,20 @@
import pytest
from article_data_manager.validation.numbers import parse_decimal
def test_parse_decimal_accepts_comma_decimal() -> None:
assert parse_decimal("6,00") == 6.0
def test_parse_decimal_accepts_point_decimal() -> None:
assert parse_decimal("1.20") == 1.2
def test_parse_decimal_maps_empty_value_to_none() -> None:
assert parse_decimal("") is None
def test_parse_decimal_rejects_ambiguous_number() -> None:
with pytest.raises(ValueError):
parse_decimal("1.234,56")