Initialize Article Data Manager project
This commit is contained in:
+5
@@ -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
|
||||
|
+6
@@ -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"
|
||||
|
+23
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user