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
+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")