From 616e86c2105617c1d75d651352e6f3f68994c9ab Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 19:55:57 +0000 Subject: [PATCH 1/9] Add tag_swedish_amounts function for tagging monetary amounts and percentages Implements a function to identify and wrap Swedish currency amounts (kronor, kr, SEK) and percentages (%, procent) with semantic tags containing: - id: context-aware slug (e.g., "avgift-1500-kr", "ranta-8-5-procent") - type: "amount" or "percentage" - value: normalized numeric value The function: - Handles Swedish number formats (space separators, decimal comma) - Supports multipliers (miljoner, miljarder, tusen) - Extracts context words for descriptive slugs - Skips markdown headers and XML/HTML tags - Includes 48 unit tests --- formatters/tag_swedish_amounts.py | 384 +++++++++++++++++++++++++++ test/test_tag_swedish_amounts.py | 418 ++++++++++++++++++++++++++++++ 2 files changed, 802 insertions(+) create mode 100644 formatters/tag_swedish_amounts.py create mode 100644 test/test_tag_swedish_amounts.py diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py new file mode 100644 index 0000000..bfc6b48 --- /dev/null +++ b/formatters/tag_swedish_amounts.py @@ -0,0 +1,384 @@ +""" +Functions for tagging Swedish monetary amounts and percentages with elements. + +This module contains functions to identify and tag: +1. Swedish currency amounts (kronor, kr, SEK) +2. Percentages (%, procent) + +Each match is wrapped in a element with: +- type: "amount" or "percentage" +- value: normalized numeric value +- id: a descriptive slug based on context +""" + +import re +from typing import Optional, Tuple +import unicodedata + + +# ============================================================================ +# Regex patterns for Swedish amounts +# ============================================================================ + +# Number patterns - Swedish uses space as thousands separator and comma for decimals +# Matches: 1 000, 1000, 1 000 000, 1,5, 1.5 +_NUMBER_PATTERN = r'(\d[\d\s]*(?:[,\.]\d+)?)' + +# Currency units +_KRONOR_PATTERN = r'(?:kronor|kr\.?|SEK)' +_MILJON_PATTERN = r'(?:miljon(?:er)?)' +_MILJARD_PATTERN = r'(?:miljard(?:er)?)' +_TUSENTAL_PATTERN = r'(?:tusen)' + +# Full amount patterns with lookahead/lookbehind to avoid matching inside tags/links +# Pattern 1: X kronor/kr/SEK +AMOUNT_SIMPLE_PATTERN = re.compile( + rf'(?\w])({_NUMBER_PATTERN})\s*({_KRONOR_PATTERN})(?![<\w])', + re.IGNORECASE +) + +# Pattern 2: X miljoner/miljarder/tusen kronor +AMOUNT_WITH_MULTIPLIER_PATTERN = re.compile( + rf'(?\w])({_NUMBER_PATTERN})\s*({_TUSENTAL_PATTERN}|{_MILJON_PATTERN}|{_MILJARD_PATTERN})\s*({_KRONOR_PATTERN})(?![<\w])', + re.IGNORECASE +) + +# ============================================================================ +# Regex patterns for percentages +# ============================================================================ + +# Pattern: X %, X%, X procent +PERCENTAGE_PATTERN = re.compile( + rf'(?\w])({_NUMBER_PATTERN})\s*(%|procent)(?![<\w])', + re.IGNORECASE +) + + +def normalize_number(num_str: str) -> str: + """ + Normalize a Swedish number string to a standard format. + + Removes spaces (thousands separator) and converts comma to dot for decimals. + + Args: + num_str: Number string like "1 000 000" or "1,5" + + Returns: + Normalized number string like "1000000" or "1.5" + """ + # Remove all whitespace + normalized = re.sub(r'\s+', '', num_str) + # Convert Swedish decimal comma to dot + normalized = normalized.replace(',', '.') + return normalized + + +def generate_amount_slug(value: str, multiplier: Optional[str], currency: str, context: str) -> str: + """ + Generate a descriptive slug for an amount based on context. + + Args: + value: The numeric value (normalized) + multiplier: Optional multiplier like "miljoner", "miljarder", "tusen" + currency: The currency unit used + context: Surrounding text for context extraction + + Returns: + A slug like "belopp-1000000-kr" or "avgift-500-kr" + """ + # Try to extract a descriptive word from context + prefix = _extract_context_word(context) + + # Format the value with multiplier + if multiplier: + multiplier_lower = multiplier.lower() + if 'miljard' in multiplier_lower: + suffix = 'mdkr' + elif 'miljon' in multiplier_lower: + suffix = 'mkr' + elif 'tusen' in multiplier_lower: + suffix = 'tkr' + else: + suffix = 'kr' + else: + suffix = 'kr' + + # Create slug + slug_parts = [prefix, value, suffix] + slug = '-'.join(filter(None, slug_parts)) + + return _slugify(slug) + + +def generate_percentage_slug(value: str, context: str) -> str: + """ + Generate a descriptive slug for a percentage based on context. + + Args: + value: The numeric value (normalized) + context: Surrounding text for context extraction + + Returns: + A slug like "ranta-5-procent" or "andel-25-procent" + """ + prefix = _extract_context_word(context) + + slug_parts = [prefix, value, 'procent'] + slug = '-'.join(filter(None, slug_parts)) + + return _slugify(slug) + + +def _extract_context_word(context: str) -> str: + """ + Extract a descriptive word from the context preceding the amount/percentage. + + Looks for Swedish financial/legal terms that describe what the amount represents. + + Args: + context: Text preceding the amount + + Returns: + A descriptive word or "belopp"/"andel" as default + """ + # Common Swedish terms that describe amounts + amount_descriptors = [ + # Fees and charges + r'(avgift(?:en)?)', + r'(kostnad(?:en)?)', + r'(pris(?:et)?)', + r'(taxa(?:n)?)', + r'(ers[äa]ttning(?:en)?)', + r'(bidrag(?:et)?)', + r'(understöd(?:et)?)', + r'(arvode(?:t)?)', + r'(lön(?:en)?)', + # Limits and thresholds + r'(gräns(?:en)?)', + r'(tak(?:et)?)', + r'(golv(?:et)?)', + r'(minst)', + r'(högst)', + r'(max(?:imum)?)', + r'(min(?:imum)?)', + # Financial terms + r'(kapital(?:et)?)', + r'(belopp(?:et)?)', + r'(summa(?:n)?)', + r'(värde(?:t)?)', + r'(inkomst(?:en)?)', + r'(utgift(?:en)?)', + r'(skatt(?:en)?)', + r'(moms(?:en)?)', + r'(böter(?:na)?)', + r'(vite(?:t)?)', + r'(skuld(?:en)?)', + r'(fordran)', + r'(tillgång(?:ar)?)', + r'(omsättning(?:en)?)', + # Insurance/pension + r'(pension(?:en)?)', + r'(försäkring(?:en)?)', + r'(premie(?:n)?)', + # Interest + r'(ränt(?:a|an)?)', + r'(avkastning(?:en)?)', + ] + + # Percentage-specific descriptors + percentage_descriptors = [ + r'(ränt(?:a|an|esats)?)', + r'(andel(?:en)?)', + r'(procentsats(?:en)?)', + r'(moms(?:en)?)', + r'(skatt(?:esats)?(?:en)?)', + r'(avgift(?:ssats)?(?:en)?)', + r'(avdrag(?:et)?)', + r'(påslag(?:et)?)', + r'(rabatt(?:en)?)', + r'(höjning(?:en)?)', + r'(sänkning(?:en)?)', + r'(ökning(?:en)?)', + r'(minskning(?:en)?)', + ] + + all_descriptors = amount_descriptors + percentage_descriptors + + # Search backwards in context for descriptive words + context_lower = context.lower() + + for pattern in all_descriptors: + match = re.search(pattern, context_lower) + if match: + word = match.group(1) + # Remove definite article suffixes for cleaner slugs + # Only remove common Swedish article endings, being careful not to + # remove parts of the base word (e.g., 't' in 'avgift') + if word.endswith('erna'): + word = word[:-4] + elif word.endswith('arna'): + word = word[:-4] + elif word.endswith('en') and len(word) > 3: + word = word[:-2] + elif word.endswith('et') and len(word) > 3: + word = word[:-2] + elif word.endswith('na') and len(word) > 3: + word = word[:-2] + if word: + return word + + return 'belopp' + + +def _slugify(text: str) -> str: + """ + Convert text to a URL-safe slug. + + Args: + text: Text to slugify + + Returns: + Lowercase ASCII slug with hyphens + """ + # Normalize unicode characters + text = unicodedata.normalize('NFKD', text) + # Convert Swedish characters + text = text.replace('å', 'a').replace('ä', 'a').replace('ö', 'o') + text = text.replace('Å', 'a').replace('Ä', 'a').replace('Ö', 'o') + # Remove non-ASCII characters + text = text.encode('ASCII', 'ignore').decode('ASCII') + # Convert to lowercase + text = text.lower() + # Replace spaces and special chars with hyphens + text = re.sub(r'[^a-z0-9]+', '-', text) + # Remove leading/trailing hyphens + text = text.strip('-') + # Collapse multiple hyphens + text = re.sub(r'-+', '-', text) + + return text + + +def tag_swedish_amounts(text: str) -> str: + """ + Tag Swedish monetary amounts and percentages in text with elements. + + Processes text line by line, skipping markdown headers. + Each amount/percentage is wrapped with a tag containing: + - id: descriptive slug + - type: "amount" or "percentage" + - value: normalized numeric value + + Args: + text: The text to process + + Returns: + Text with amounts and percentages wrapped in tags + + Example: + Input: "Avgiften är 1 000 kronor per år." + Output: 'Avgiften är 1 000 kronor per år.' + """ + lines = text.split('\n') + processed_lines = [] + + for line in lines: + # Skip headers (lines starting with #) + if line.strip().startswith('#'): + processed_lines.append(line) + continue + + # Skip lines that are inside XML/HTML tags (section tags, etc.) + if re.match(r'^\s*]*>\s*$', line): + processed_lines.append(line) + continue + + # Process amounts and percentages + processed_line = _tag_amounts_in_line(line) + processed_line = _tag_percentages_in_line(processed_line) + + processed_lines.append(processed_line) + + return '\n'.join(processed_lines) + + +def _tag_amounts_in_line(line: str) -> str: + """ + Tag monetary amounts in a single line. + + Args: + line: A single line of text + + Returns: + Line with amounts tagged + """ + # First, try to match amounts with multipliers (miljoner, miljarder, tusen) + def replace_amount_with_multiplier(match): + full_match = match.group(0) + number = match.group(1) + multiplier = match.group(3) + currency = match.group(4) + + # Get context (text before match) + start_pos = match.start() + context = line[:start_pos] + + normalized_value = normalize_number(number) + slug = generate_amount_slug(normalized_value, multiplier, currency, context) + + return f'{full_match}' + + # Then, match simple amounts (without multipliers) + def replace_simple_amount(match): + full_match = match.group(0) + + # Skip if already inside a tag + start_pos = match.start() + if '{full_match}' + + # Apply patterns + result = AMOUNT_WITH_MULTIPLIER_PATTERN.sub(replace_amount_with_multiplier, line) + result = AMOUNT_SIMPLE_PATTERN.sub(replace_simple_amount, result) + + return result + + +def _tag_percentages_in_line(line: str) -> str: + """ + Tag percentages in a single line. + + Args: + line: A single line of text + + Returns: + Line with percentages tagged + """ + def replace_percentage(match): + full_match = match.group(0) + + # Skip if already inside a tag + start_pos = match.start() + if '{full_match}' + + return PERCENTAGE_PATTERN.sub(replace_percentage, line) diff --git a/test/test_tag_swedish_amounts.py b/test/test_tag_swedish_amounts.py new file mode 100644 index 0000000..4748244 --- /dev/null +++ b/test/test_tag_swedish_amounts.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python3 +""" +Tests for Swedish amount and percentage tagging utilities. +""" + +import pytest +from formatters.tag_swedish_amounts import ( + tag_swedish_amounts, + normalize_number, + generate_amount_slug, + generate_percentage_slug, + _slugify, + _extract_context_word, +) + + +# =========================================================================== +# normalize_number Tests +# =========================================================================== + +@pytest.mark.unit +class TestNormalizeNumber: + """Test the normalize_number function.""" + + def test_simple_number(self): + """Test normalizing a simple number.""" + assert normalize_number("1000") == "1000" + + def test_number_with_space_separator(self): + """Test normalizing number with Swedish space as thousands separator.""" + assert normalize_number("1 000") == "1000" + assert normalize_number("1 000 000") == "1000000" + assert normalize_number("10 000 000") == "10000000" + + def test_number_with_decimal_comma(self): + """Test normalizing number with Swedish decimal comma.""" + assert normalize_number("1,5") == "1.5" + assert normalize_number("12,75") == "12.75" + + def test_number_with_decimal_dot(self): + """Test normalizing number with decimal dot.""" + assert normalize_number("1.5") == "1.5" + + def test_combined_format(self): + """Test normalizing number with both space separator and decimal.""" + assert normalize_number("1 000,5") == "1000.5" + assert normalize_number("1 234 567,89") == "1234567.89" + + +# =========================================================================== +# _slugify Tests +# =========================================================================== + +@pytest.mark.unit +class TestSlugify: + """Test the _slugify function.""" + + def test_simple_text(self): + """Test slugifying simple text.""" + assert _slugify("belopp") == "belopp" + + def test_swedish_characters(self): + """Test slugifying Swedish characters.""" + assert _slugify("räntesats") == "rantesats" + assert _slugify("avgäld") == "avgald" + assert _slugify("höjning") == "hojning" + assert _slugify("Årsavgift") == "arsavgift" + + def test_with_numbers(self): + """Test slugifying text with numbers.""" + assert _slugify("belopp-1000-kr") == "belopp-1000-kr" + + def test_special_characters(self): + """Test slugifying text with special characters.""" + assert _slugify("avgift (test)") == "avgift-test" + + def test_multiple_spaces(self): + """Test slugifying text with multiple spaces.""" + assert _slugify("en två tre") == "en-tva-tre" + + +# =========================================================================== +# _extract_context_word Tests +# =========================================================================== + +@pytest.mark.unit +class TestExtractContextWord: + """Test the _extract_context_word function.""" + + def test_avgift(self): + """Test extracting 'avgift' from context.""" + assert _extract_context_word("En avgift på ") == "avgift" + + def test_avgiften(self): + """Test extracting base word from definite form.""" + result = _extract_context_word("Avgiften är ") + assert result in ["avgift", "avgift"] + + def test_ranta(self): + """Test extracting 'ränt' from context.""" + result = _extract_context_word("med en ränta på ") + assert "rant" in _slugify(result) or result == "belopp" + + def test_no_descriptor(self): + """Test default when no descriptor found.""" + assert _extract_context_word("xyz ") == "belopp" + + def test_skatt(self): + """Test extracting 'skatt' from context.""" + result = _extract_context_word("Den kommunala skatten är ") + assert result in ["skatt", "skatt", "belopp"] + + +# =========================================================================== +# generate_amount_slug Tests +# =========================================================================== + +@pytest.mark.unit +class TestGenerateAmountSlug: + """Test the generate_amount_slug function.""" + + def test_simple_amount(self): + """Test generating slug for simple amount.""" + slug = generate_amount_slug("1000", None, "kronor", "En avgift på ") + assert "1000" in slug + assert "kr" in slug + + def test_amount_with_miljoner(self): + """Test generating slug for amount with 'miljoner'.""" + slug = generate_amount_slug("5", "miljoner", "kronor", "Kapitalet är ") + assert "5" in slug + assert "mkr" in slug + + def test_amount_with_miljarder(self): + """Test generating slug for amount with 'miljarder'.""" + slug = generate_amount_slug("2", "miljarder", "kronor", "Omsättningen är ") + assert "2" in slug + assert "mdkr" in slug + + def test_amount_with_tusen(self): + """Test generating slug for amount with 'tusen'.""" + slug = generate_amount_slug("50", "tusen", "kronor", "Priset är ") + assert "50" in slug + assert "tkr" in slug + + +# =========================================================================== +# generate_percentage_slug Tests +# =========================================================================== + +@pytest.mark.unit +class TestGeneratePercentageSlug: + """Test the generate_percentage_slug function.""" + + def test_simple_percentage(self): + """Test generating slug for simple percentage.""" + slug = generate_percentage_slug("5", "Räntan är ") + assert "5" in slug + assert "procent" in slug + + def test_percentage_with_context(self): + """Test generating slug with context extraction.""" + slug = generate_percentage_slug("25", "Momsen är ") + assert "25" in slug + assert "procent" in slug + + +# =========================================================================== +# tag_swedish_amounts Tests - Simple amounts +# =========================================================================== + +@pytest.mark.unit +class TestTagSwedishAmountsSimple: + """Test tagging simple Swedish amounts.""" + + def test_kronor_amount(self): + """Test tagging amount with 'kronor'.""" + result = tag_swedish_amounts("Avgiften är 1000 kronor.") + assert '1000 kronor' in result + + def test_kr_amount(self): + """Test tagging amount with 'kr'.""" + result = tag_swedish_amounts("Priset är 500 kr.") + assert '' in result + + def test_miljon_kr(self): + """Test tagging amount with 'miljon kr'.""" + result = tag_swedish_amounts("Det kostar 1 miljon kr.") + assert '') + assert '') + assert '') == 2 + + def test_amount_and_percentage(self): + """Test tagging both amount and percentage.""" + result = tag_swedish_amounts("Räntan på 5% ger 1000 kronor i avkastning.") + assert 'type="percentage"' in result + assert 'type="amount"' in result + + +# =========================================================================== +# tag_swedish_amounts Tests - Context-based slugs +# =========================================================================== + +@pytest.mark.unit +class TestTagSwedishAmountsContextSlugs: + """Test that slugs are generated based on context.""" + + def test_avgift_context(self): + """Test slug generation with 'avgift' context.""" + result = tag_swedish_amounts("Avgiften är 500 kronor.") + assert 'id="avgift-500-kr"' in result + + def test_ranta_context_percentage(self): + """Test slug generation with 'ränta' context for percentage.""" + result = tag_swedish_amounts("Räntan är 5 procent.") + # Ränta should be extracted and slugified + assert 'id="' in result + assert 'procent"' in result + + +# =========================================================================== +# tag_swedish_amounts Tests - Edge cases +# =========================================================================== + +@pytest.mark.unit +class TestTagSwedishAmountsEdgeCases: + """Test edge cases.""" + + def test_empty_string(self): + """Test with empty string.""" + result = tag_swedish_amounts("") + assert result == "" + + def test_no_amounts(self): + """Test text without amounts.""" + text = "Detta är en vanlig text utan belopp." + result = tag_swedish_amounts(text) + assert result == text + assert '' in result + assert '2000 SEK' in result + + # Check header is NOT tagged + assert '## Rubrik med 1000 kronor' in result + assert ' Date: Thu, 8 Jan 2026 20:01:38 +0000 Subject: [PATCH 2/9] Simplify data tag id to only contain descriptive identifier Remove numeric value and unit from id attribute, keeping only the context-derived identifier (e.g., "avgift", "ranta", "moms"). This allows tracking the same data point across law amendments, since the id stays constant while only the value changes. Before: id="avgift-1500-kr" After: id="avgift" --- formatters/tag_swedish_amounts.py | 54 +++++++++------------------- test/test_tag_swedish_amounts.py | 58 ++++++++++++++++--------------- 2 files changed, 46 insertions(+), 66 deletions(-) diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py index bfc6b48..f28237e 100644 --- a/formatters/tag_swedish_amounts.py +++ b/formatters/tag_swedish_amounts.py @@ -73,60 +73,41 @@ def normalize_number(num_str: str) -> str: return normalized -def generate_amount_slug(value: str, multiplier: Optional[str], currency: str, context: str) -> str: +def generate_amount_slug(context: str) -> str: """ Generate a descriptive slug for an amount based on context. + The slug identifies what the amount represents, not its value. + This allows tracking changes across law amendments. + Args: - value: The numeric value (normalized) - multiplier: Optional multiplier like "miljoner", "miljarder", "tusen" - currency: The currency unit used context: Surrounding text for context extraction Returns: - A slug like "belopp-1000000-kr" or "avgift-500-kr" + A slug like "avgift" or "bidrag" that identifies the amount """ - # Try to extract a descriptive word from context + # Extract a descriptive word from context prefix = _extract_context_word(context) - # Format the value with multiplier - if multiplier: - multiplier_lower = multiplier.lower() - if 'miljard' in multiplier_lower: - suffix = 'mdkr' - elif 'miljon' in multiplier_lower: - suffix = 'mkr' - elif 'tusen' in multiplier_lower: - suffix = 'tkr' - else: - suffix = 'kr' - else: - suffix = 'kr' - - # Create slug - slug_parts = [prefix, value, suffix] - slug = '-'.join(filter(None, slug_parts)) - - return _slugify(slug) + return _slugify(prefix) -def generate_percentage_slug(value: str, context: str) -> str: +def generate_percentage_slug(context: str) -> str: """ Generate a descriptive slug for a percentage based on context. + The slug identifies what the percentage represents, not its value. + This allows tracking changes across law amendments. + Args: - value: The numeric value (normalized) context: Surrounding text for context extraction Returns: - A slug like "ranta-5-procent" or "andel-25-procent" + A slug like "ranta" or "moms" that identifies the percentage """ prefix = _extract_context_word(context) - slug_parts = [prefix, value, 'procent'] - slug = '-'.join(filter(None, slug_parts)) - - return _slugify(slug) + return _slugify(prefix) def _extract_context_word(context: str) -> str: @@ -316,15 +297,13 @@ def _tag_amounts_in_line(line: str) -> str: def replace_amount_with_multiplier(match): full_match = match.group(0) number = match.group(1) - multiplier = match.group(3) - currency = match.group(4) # Get context (text before match) start_pos = match.start() context = line[:start_pos] normalized_value = normalize_number(number) - slug = generate_amount_slug(normalized_value, multiplier, currency, context) + slug = generate_amount_slug(context) return f'{full_match}' @@ -338,12 +317,11 @@ def replace_simple_amount(match): return full_match number = match.group(1) - currency = match.group(3) context = line[:start_pos] normalized_value = normalize_number(number) - slug = generate_amount_slug(normalized_value, None, currency, context) + slug = generate_amount_slug(context) return f'{full_match}' @@ -377,7 +355,7 @@ def replace_percentage(match): context = line[:start_pos] normalized_value = normalize_number(number) - slug = generate_percentage_slug(normalized_value, context) + slug = generate_percentage_slug(context) return f'{full_match}' diff --git a/test/test_tag_swedish_amounts.py b/test/test_tag_swedish_amounts.py index 4748244..0adfd9c 100644 --- a/test/test_tag_swedish_amounts.py +++ b/test/test_tag_swedish_amounts.py @@ -121,27 +121,23 @@ class TestGenerateAmountSlug: def test_simple_amount(self): """Test generating slug for simple amount.""" - slug = generate_amount_slug("1000", None, "kronor", "En avgift på ") - assert "1000" in slug - assert "kr" in slug + slug = generate_amount_slug("En avgift på ") + assert slug == "avgift" - def test_amount_with_miljoner(self): - """Test generating slug for amount with 'miljoner'.""" - slug = generate_amount_slug("5", "miljoner", "kronor", "Kapitalet är ") - assert "5" in slug - assert "mkr" in slug + def test_amount_with_context(self): + """Test generating slug extracts context word.""" + slug = generate_amount_slug("Kapitalet är ") + assert slug == "kapital" - def test_amount_with_miljarder(self): - """Test generating slug for amount with 'miljarder'.""" - slug = generate_amount_slug("2", "miljarder", "kronor", "Omsättningen är ") - assert "2" in slug - assert "mdkr" in slug + def test_amount_with_omsattning(self): + """Test generating slug for 'omsättning'.""" + slug = generate_amount_slug("Omsättningen är ") + assert slug == "omsattning" - def test_amount_with_tusen(self): - """Test generating slug for amount with 'tusen'.""" - slug = generate_amount_slug("50", "tusen", "kronor", "Priset är ") - assert "50" in slug - assert "tkr" in slug + def test_amount_with_pris(self): + """Test generating slug for 'pris'.""" + slug = generate_amount_slug("Priset är ") + assert slug == "pris" # =========================================================================== @@ -154,15 +150,13 @@ class TestGeneratePercentageSlug: def test_simple_percentage(self): """Test generating slug for simple percentage.""" - slug = generate_percentage_slug("5", "Räntan är ") - assert "5" in slug - assert "procent" in slug + slug = generate_percentage_slug("Räntan är ") + assert slug == "ranta" def test_percentage_with_context(self): """Test generating slug with context extraction.""" - slug = generate_percentage_slug("25", "Momsen är ") - assert "25" in slug - assert "procent" in slug + slug = generate_percentage_slug("Momsen är ") + assert slug == "moms" # =========================================================================== @@ -361,14 +355,22 @@ class TestTagSwedishAmountsContextSlugs: def test_avgift_context(self): """Test slug generation with 'avgift' context.""" result = tag_swedish_amounts("Avgiften är 500 kronor.") - assert 'id="avgift-500-kr"' in result + assert 'id="avgift"' in result def test_ranta_context_percentage(self): """Test slug generation with 'ränta' context for percentage.""" result = tag_swedish_amounts("Räntan är 5 procent.") - # Ränta should be extracted and slugified - assert 'id="' in result - assert 'procent"' in result + assert 'id="ranta"' in result + + def test_same_id_different_values(self): + """Test that same context gives same id regardless of value.""" + result1 = tag_swedish_amounts("Avgiften är 500 kronor.") + result2 = tag_swedish_amounts("Avgiften är 1000 kronor.") + # Both should have id="avgift" but different values + assert 'id="avgift"' in result1 + assert 'id="avgift"' in result2 + assert 'value="500"' in result1 + assert 'value="1000"' in result2 # =========================================================================== From 136017a88787e58955c63039b4355f820ed7e273 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 20:06:21 +0000 Subject: [PATCH 3/9] Use positional ids with reference table for descriptive slugs Replace context-based slug generation with positional ids that can be mapped to descriptive slugs via a reference table. Changes: - Generate positional ids like "kap5.2-belopp-1" based on section + type + position - Add reference table support (data/amount-references.json) for custom slugs - Section tags in the text automatically set the current section id - Counters reset when entering a new section This approach allows: - Consistent ids across law amendments (same position = same id) - Human/LLM curation of descriptive slugs like "riksbankens-referensranta" - Tracking value changes over time using the stable id Example with reference table: {"kap5.2-belopp-1": "tillstandsavgift"} Output: 1 500 kronor --- data/amount-references.json | 7 + formatters/tag_swedish_amounts.py | 262 ++++++++++++++---------------- test/test_tag_swedish_amounts.py | 159 +++++++++--------- 3 files changed, 205 insertions(+), 223 deletions(-) create mode 100644 data/amount-references.json diff --git a/data/amount-references.json b/data/amount-references.json new file mode 100644 index 0000000..fd38ff5 --- /dev/null +++ b/data/amount-references.json @@ -0,0 +1,7 @@ +{ + "_comment": "Reference table mapping positional ids to descriptive slugs. Keys starting with _ are ignored.", + "_format": "{ 'section-id-type-position': 'descriptive-slug' }", + + "kap5.2-belopp-1": "tillstandsavgift", + "kap5.2-procent-1": "riksbankens-referensranta" +} diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py index f28237e..562c6d2 100644 --- a/formatters/tag_swedish_amounts.py +++ b/formatters/tag_swedish_amounts.py @@ -8,14 +8,23 @@ Each match is wrapped in a element with: - type: "amount" or "percentage" - value: normalized numeric value -- id: a descriptive slug based on context +- id: a reference id based on section + position, or a custom slug from reference table + +The reference table (data/amount-references.json) maps positional ids to +descriptive slugs like "riksbankens-referensranta". """ import re -from typing import Optional, Tuple +import json +from pathlib import Path +from typing import Optional, Dict import unicodedata +# Cache for reference table +_reference_table: Optional[Dict[str, str]] = None + + # ============================================================================ # Regex patterns for Swedish amounts # ============================================================================ @@ -73,142 +82,71 @@ def normalize_number(num_str: str) -> str: return normalized -def generate_amount_slug(context: str) -> str: +def load_reference_table() -> Dict[str, str]: """ - Generate a descriptive slug for an amount based on context. + Load the amount reference table from data/amount-references.json. - The slug identifies what the amount represents, not its value. - This allows tracking changes across law amendments. - - Args: - context: Surrounding text for context extraction + The reference table maps positional ids (e.g., "kap5.2-belopp-1") to + descriptive slugs (e.g., "riksbankens-referensranta"). Returns: - A slug like "avgift" or "bidrag" that identifies the amount + Dictionary mapping positional ids to descriptive slugs """ - # Extract a descriptive word from context - prefix = _extract_context_word(context) + global _reference_table - return _slugify(prefix) + if _reference_table is not None: + return _reference_table + try: + current_file = Path(__file__) + project_root = current_file.parent.parent + ref_file = project_root / "data" / "amount-references.json" -def generate_percentage_slug(context: str) -> str: - """ - Generate a descriptive slug for a percentage based on context. + if ref_file.exists(): + with open(ref_file, 'r', encoding='utf-8') as f: + _reference_table = json.load(f) + else: + _reference_table = {} + + except Exception as e: + print(f"Warning: Could not load amount references: {e}") + _reference_table = {} + + return _reference_table - The slug identifies what the percentage represents, not its value. - This allows tracking changes across law amendments. + +def generate_positional_id(section_id: Optional[str], data_type: str, position: int) -> str: + """ + Generate a positional id for a data element. Args: - context: Surrounding text for context extraction + section_id: The section id (e.g., "kap5.2") or None + data_type: "belopp" for amounts, "procent" for percentages + position: 1-based position within the section for this type Returns: - A slug like "ranta" or "moms" that identifies the percentage + A positional id like "kap5.2-belopp-1" or "procent-1" if no section """ - prefix = _extract_context_word(context) + if section_id: + return f"{section_id}-{data_type}-{position}" + else: + return f"{data_type}-{position}" - return _slugify(prefix) - -def _extract_context_word(context: str) -> str: +def resolve_id(positional_id: str) -> str: """ - Extract a descriptive word from the context preceding the amount/percentage. + Resolve a positional id to a descriptive slug using the reference table. - Looks for Swedish financial/legal terms that describe what the amount represents. + If no mapping exists, returns the positional id as-is. Args: - context: Text preceding the amount + positional_id: The positional id (e.g., "kap5.2-belopp-1") Returns: - A descriptive word or "belopp"/"andel" as default + The descriptive slug if found, otherwise the positional id """ - # Common Swedish terms that describe amounts - amount_descriptors = [ - # Fees and charges - r'(avgift(?:en)?)', - r'(kostnad(?:en)?)', - r'(pris(?:et)?)', - r'(taxa(?:n)?)', - r'(ers[äa]ttning(?:en)?)', - r'(bidrag(?:et)?)', - r'(understöd(?:et)?)', - r'(arvode(?:t)?)', - r'(lön(?:en)?)', - # Limits and thresholds - r'(gräns(?:en)?)', - r'(tak(?:et)?)', - r'(golv(?:et)?)', - r'(minst)', - r'(högst)', - r'(max(?:imum)?)', - r'(min(?:imum)?)', - # Financial terms - r'(kapital(?:et)?)', - r'(belopp(?:et)?)', - r'(summa(?:n)?)', - r'(värde(?:t)?)', - r'(inkomst(?:en)?)', - r'(utgift(?:en)?)', - r'(skatt(?:en)?)', - r'(moms(?:en)?)', - r'(böter(?:na)?)', - r'(vite(?:t)?)', - r'(skuld(?:en)?)', - r'(fordran)', - r'(tillgång(?:ar)?)', - r'(omsättning(?:en)?)', - # Insurance/pension - r'(pension(?:en)?)', - r'(försäkring(?:en)?)', - r'(premie(?:n)?)', - # Interest - r'(ränt(?:a|an)?)', - r'(avkastning(?:en)?)', - ] - - # Percentage-specific descriptors - percentage_descriptors = [ - r'(ränt(?:a|an|esats)?)', - r'(andel(?:en)?)', - r'(procentsats(?:en)?)', - r'(moms(?:en)?)', - r'(skatt(?:esats)?(?:en)?)', - r'(avgift(?:ssats)?(?:en)?)', - r'(avdrag(?:et)?)', - r'(påslag(?:et)?)', - r'(rabatt(?:en)?)', - r'(höjning(?:en)?)', - r'(sänkning(?:en)?)', - r'(ökning(?:en)?)', - r'(minskning(?:en)?)', - ] - - all_descriptors = amount_descriptors + percentage_descriptors - - # Search backwards in context for descriptive words - context_lower = context.lower() - - for pattern in all_descriptors: - match = re.search(pattern, context_lower) - if match: - word = match.group(1) - # Remove definite article suffixes for cleaner slugs - # Only remove common Swedish article endings, being careful not to - # remove parts of the base word (e.g., 't' in 'avgift') - if word.endswith('erna'): - word = word[:-4] - elif word.endswith('arna'): - word = word[:-4] - elif word.endswith('en') and len(word) > 3: - word = word[:-2] - elif word.endswith('et') and len(word) > 3: - word = word[:-2] - elif word.endswith('na') and len(word) > 3: - word = word[:-2] - if word: - return word - - return 'belopp' + ref_table = load_reference_table() + return ref_table.get(positional_id, positional_id) def _slugify(text: str) -> str: @@ -240,75 +178,109 @@ def _slugify(text: str) -> str: return text -def tag_swedish_amounts(text: str) -> str: +def tag_swedish_amounts(text: str, section_id: Optional[str] = None) -> str: """ Tag Swedish monetary amounts and percentages in text with elements. Processes text line by line, skipping markdown headers. Each amount/percentage is wrapped with a tag containing: - - id: descriptive slug + - id: positional id (e.g., "kap5.2-belopp-1") or resolved slug from reference table - type: "amount" or "percentage" - value: normalized numeric value Args: text: The text to process + section_id: Optional section id for generating positional ids (e.g., "kap5.2") Returns: Text with amounts and percentages wrapped in tags Example: - Input: "Avgiften är 1 000 kronor per år." - Output: 'Avgiften är 1 000 kronor per år.' + Input: "Avgiften är 1 000 kronor per år." with section_id="kap5.2" + Output: '1 000 kronor' + + With reference table {"kap5.2-belopp-1": "tillstandsavgift"}: + Output: '1 000 kronor' """ lines = text.split('\n') processed_lines = [] + # Track current section and counters + current_section = section_id + amount_counter = 0 + percentage_counter = 0 + for line in lines: # Skip headers (lines starting with #) if line.strip().startswith('#'): processed_lines.append(line) continue + # Check for section tags to extract section id + section_match = re.match(r'^\s*]*\bid=["\']([^"\']+)["\']', line) + if section_match: + current_section = section_match.group(1) + amount_counter = 0 # Reset counters for new section + percentage_counter = 0 + processed_lines.append(line) + continue + # Skip lines that are inside XML/HTML tags (section tags, etc.) if re.match(r'^\s*]*>\s*$', line): processed_lines.append(line) continue - # Process amounts and percentages - processed_line = _tag_amounts_in_line(line) - processed_line = _tag_percentages_in_line(processed_line) + # Process amounts and percentages with counters + processed_line, new_amount_count = _tag_amounts_in_line( + line, current_section, amount_counter + ) + amount_counter = new_amount_count + + processed_line, new_percentage_count = _tag_percentages_in_line( + processed_line, current_section, percentage_counter + ) + percentage_counter = new_percentage_count processed_lines.append(processed_line) return '\n'.join(processed_lines) -def _tag_amounts_in_line(line: str) -> str: +def _tag_amounts_in_line( + line: str, + section_id: Optional[str], + counter: int +) -> tuple[str, int]: """ Tag monetary amounts in a single line. Args: line: A single line of text + section_id: Current section id for positional ids + counter: Current count of amounts in this section Returns: - Line with amounts tagged + Tuple of (processed line, updated counter) """ + current_counter = counter + # First, try to match amounts with multipliers (miljoner, miljarder, tusen) def replace_amount_with_multiplier(match): + nonlocal current_counter full_match = match.group(0) number = match.group(1) - # Get context (text before match) - start_pos = match.start() - context = line[:start_pos] + current_counter += 1 + positional_id = generate_positional_id(section_id, "belopp", current_counter) + resolved_id = resolve_id(positional_id) normalized_value = normalize_number(number) - slug = generate_amount_slug(context) - return f'{full_match}' + return f'{full_match}' # Then, match simple amounts (without multipliers) def replace_simple_amount(match): + nonlocal current_counter full_match = match.group(0) # Skip if already inside a tag @@ -318,31 +290,41 @@ def replace_simple_amount(match): number = match.group(1) - context = line[:start_pos] + current_counter += 1 + positional_id = generate_positional_id(section_id, "belopp", current_counter) + resolved_id = resolve_id(positional_id) normalized_value = normalize_number(number) - slug = generate_amount_slug(context) - return f'{full_match}' + return f'{full_match}' # Apply patterns result = AMOUNT_WITH_MULTIPLIER_PATTERN.sub(replace_amount_with_multiplier, line) result = AMOUNT_SIMPLE_PATTERN.sub(replace_simple_amount, result) - return result + return result, current_counter -def _tag_percentages_in_line(line: str) -> str: +def _tag_percentages_in_line( + line: str, + section_id: Optional[str], + counter: int +) -> tuple[str, int]: """ Tag percentages in a single line. Args: line: A single line of text + section_id: Current section id for positional ids + counter: Current count of percentages in this section Returns: - Line with percentages tagged + Tuple of (processed line, updated counter) """ + current_counter = counter + def replace_percentage(match): + nonlocal current_counter full_match = match.group(0) # Skip if already inside a tag @@ -352,11 +334,13 @@ def replace_percentage(match): number = match.group(1) - context = line[:start_pos] + current_counter += 1 + positional_id = generate_positional_id(section_id, "procent", current_counter) + resolved_id = resolve_id(positional_id) normalized_value = normalize_number(number) - slug = generate_percentage_slug(context) - return f'{full_match}' + return f'{full_match}' - return PERCENTAGE_PATTERN.sub(replace_percentage, line) + result = PERCENTAGE_PATTERN.sub(replace_percentage, line) + return result, current_counter diff --git a/test/test_tag_swedish_amounts.py b/test/test_tag_swedish_amounts.py index 0adfd9c..b3d09d0 100644 --- a/test/test_tag_swedish_amounts.py +++ b/test/test_tag_swedish_amounts.py @@ -7,10 +7,10 @@ from formatters.tag_swedish_amounts import ( tag_swedish_amounts, normalize_number, - generate_amount_slug, - generate_percentage_slug, + generate_positional_id, + resolve_id, + load_reference_table, _slugify, - _extract_context_word, ) @@ -80,83 +80,51 @@ def test_multiple_spaces(self): # =========================================================================== -# _extract_context_word Tests +# generate_positional_id Tests # =========================================================================== @pytest.mark.unit -class TestExtractContextWord: - """Test the _extract_context_word function.""" +class TestGeneratePositionalId: + """Test the generate_positional_id function.""" - def test_avgift(self): - """Test extracting 'avgift' from context.""" - assert _extract_context_word("En avgift på ") == "avgift" + def test_with_section_id(self): + """Test generating positional id with section.""" + result = generate_positional_id("kap5.2", "belopp", 1) + assert result == "kap5.2-belopp-1" - def test_avgiften(self): - """Test extracting base word from definite form.""" - result = _extract_context_word("Avgiften är ") - assert result in ["avgift", "avgift"] + def test_with_section_id_multiple(self): + """Test generating positional id with higher position.""" + result = generate_positional_id("kap5.2", "belopp", 3) + assert result == "kap5.2-belopp-3" - def test_ranta(self): - """Test extracting 'ränt' from context.""" - result = _extract_context_word("med en ränta på ") - assert "rant" in _slugify(result) or result == "belopp" + def test_without_section_id(self): + """Test generating positional id without section.""" + result = generate_positional_id(None, "belopp", 1) + assert result == "belopp-1" - def test_no_descriptor(self): - """Test default when no descriptor found.""" - assert _extract_context_word("xyz ") == "belopp" - - def test_skatt(self): - """Test extracting 'skatt' from context.""" - result = _extract_context_word("Den kommunala skatten är ") - assert result in ["skatt", "skatt", "belopp"] - - -# =========================================================================== -# generate_amount_slug Tests -# =========================================================================== - -@pytest.mark.unit -class TestGenerateAmountSlug: - """Test the generate_amount_slug function.""" - - def test_simple_amount(self): - """Test generating slug for simple amount.""" - slug = generate_amount_slug("En avgift på ") - assert slug == "avgift" - - def test_amount_with_context(self): - """Test generating slug extracts context word.""" - slug = generate_amount_slug("Kapitalet är ") - assert slug == "kapital" - - def test_amount_with_omsattning(self): - """Test generating slug for 'omsättning'.""" - slug = generate_amount_slug("Omsättningen är ") - assert slug == "omsattning" - - def test_amount_with_pris(self): - """Test generating slug for 'pris'.""" - slug = generate_amount_slug("Priset är ") - assert slug == "pris" + def test_percentage_type(self): + """Test generating positional id for percentage.""" + result = generate_positional_id("kap1.5", "procent", 2) + assert result == "kap1.5-procent-2" # =========================================================================== -# generate_percentage_slug Tests +# resolve_id Tests # =========================================================================== @pytest.mark.unit -class TestGeneratePercentageSlug: - """Test the generate_percentage_slug function.""" +class TestResolveId: + """Test the resolve_id function.""" - def test_simple_percentage(self): - """Test generating slug for simple percentage.""" - slug = generate_percentage_slug("Räntan är ") - assert slug == "ranta" + def test_no_mapping_returns_original(self): + """Test that unmapped ids are returned as-is.""" + result = resolve_id("kap99.99-belopp-99") + assert result == "kap99.99-belopp-99" - def test_percentage_with_context(self): - """Test generating slug with context extraction.""" - slug = generate_percentage_slug("Momsen är ") - assert slug == "moms" + def test_returns_positional_when_no_table(self): + """Test fallback when no reference table exists.""" + result = resolve_id("nonexistent-id") + assert result == "nonexistent-id" # =========================================================================== @@ -345,30 +313,53 @@ def test_amount_and_percentage(self): # =========================================================================== -# tag_swedish_amounts Tests - Context-based slugs +# tag_swedish_amounts Tests - Positional ids # =========================================================================== @pytest.mark.unit -class TestTagSwedishAmountsContextSlugs: - """Test that slugs are generated based on context.""" +class TestTagSwedishAmountsPositionalIds: + """Test that positional ids are generated correctly.""" - def test_avgift_context(self): - """Test slug generation with 'avgift' context.""" + def test_simple_positional_id(self): + """Test positional id without section.""" result = tag_swedish_amounts("Avgiften är 500 kronor.") - assert 'id="avgift"' in result - - def test_ranta_context_percentage(self): - """Test slug generation with 'ränta' context for percentage.""" - result = tag_swedish_amounts("Räntan är 5 procent.") - assert 'id="ranta"' in result - - def test_same_id_different_values(self): - """Test that same context gives same id regardless of value.""" - result1 = tag_swedish_amounts("Avgiften är 500 kronor.") - result2 = tag_swedish_amounts("Avgiften är 1000 kronor.") - # Both should have id="avgift" but different values - assert 'id="avgift"' in result1 - assert 'id="avgift"' in result2 + assert 'id="belopp-1"' in result + + def test_with_section_id(self): + """Test positional id with section_id parameter.""" + result = tag_swedish_amounts("Avgiften är 500 kronor.", section_id="kap5.2") + assert 'id="kap5.2-belopp-1"' in result + + def test_multiple_amounts_incrementing(self): + """Test that multiple amounts get incrementing positions.""" + result = tag_swedish_amounts("Första 500 kr och andra 1000 kr.", section_id="kap1.1") + assert 'id="kap1.1-belopp-1"' in result + assert 'id="kap1.1-belopp-2"' in result + + def test_section_tag_resets_counter(self): + """Test that section tags reset the counter.""" + text = '''
+Belopp 100 kronor. +
+
+Belopp 200 kronor. +
''' + result = tag_swedish_amounts(text) + assert 'id="kap1.1-belopp-1"' in result + assert 'id="kap1.2-belopp-1"' in result + + def test_percentage_positional_id(self): + """Test positional id for percentages.""" + result = tag_swedish_amounts("Räntan är 5 procent.", section_id="kap2.3") + assert 'id="kap2.3-procent-1"' in result + + def test_same_id_across_amendments(self): + """Test that same position gives same id with different values.""" + result1 = tag_swedish_amounts("Avgiften är 500 kronor.", section_id="kap5.2") + result2 = tag_swedish_amounts("Avgiften är 1000 kronor.", section_id="kap5.2") + # Both should have same positional id but different values + assert 'id="kap5.2-belopp-1"' in result1 + assert 'id="kap5.2-belopp-1"' in result2 assert 'value="500"' in result1 assert 'value="1000"' in result2 From a0d6ee54ec597cf848efbd35dd77087e936c88cd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 20:15:46 +0000 Subject: [PATCH 4/9] Add SFS designation to positional ids for tracking across amendments Include SFS designation (e.g., "2024:123") in positional ids to enable: - Unique identification across different laws - Tracking value changes when same slug maps to multiple SFS versions New id format: sfs-2024-123-kap5.2-belopp-1 Reference table now supports tracking changes over time: { "sfs-2020-100-kap5.2-belopp-1": "tillstandsavgift", "sfs-2024-123-kap5.2-belopp-1": "tillstandsavgift" } Both resolve to id="tillstandsavgift" but with different values, allowing comparison of the same data point across amendments. Also extracts SFS id from
tags. --- data/amount-references.json | 12 +++-- formatters/tag_swedish_amounts.py | 65 ++++++++++++++++------ test/test_tag_swedish_amounts.py | 90 +++++++++++++++++++------------ 3 files changed, 114 insertions(+), 53 deletions(-) diff --git a/data/amount-references.json b/data/amount-references.json index fd38ff5..fd34d7b 100644 --- a/data/amount-references.json +++ b/data/amount-references.json @@ -1,7 +1,13 @@ { "_comment": "Reference table mapping positional ids to descriptive slugs. Keys starting with _ are ignored.", - "_format": "{ 'section-id-type-position': 'descriptive-slug' }", + "_format": "{ 'sfs-YYYY-NNN-section-type-position': 'descriptive-slug' }", - "kap5.2-belopp-1": "tillstandsavgift", - "kap5.2-procent-1": "riksbankens-referensranta" + "_example_tracking_changes": "Multiple SFS entries can map to same slug to track value changes over time", + + "sfs-2020-100-kap5.2-belopp-1": "tillstandsavgift", + "sfs-2022-456-kap5.2-belopp-1": "tillstandsavgift", + "sfs-2024-123-kap5.2-belopp-1": "tillstandsavgift", + + "sfs-2020-100-kap6.1-procent-1": "riksbankens-referensranta", + "sfs-2024-123-kap6.1-procent-1": "riksbankens-referensranta" } diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py index 562c6d2..7e04507 100644 --- a/formatters/tag_swedish_amounts.py +++ b/formatters/tag_swedish_amounts.py @@ -115,22 +115,32 @@ def load_reference_table() -> Dict[str, str]: return _reference_table -def generate_positional_id(section_id: Optional[str], data_type: str, position: int) -> str: +def generate_positional_id(sfs_id: Optional[str], section_id: Optional[str], data_type: str, position: int) -> str: """ Generate a positional id for a data element. Args: + sfs_id: The SFS designation (e.g., "2024:123") or None section_id: The section id (e.g., "kap5.2") or None data_type: "belopp" for amounts, "procent" for percentages position: 1-based position within the section for this type Returns: - A positional id like "kap5.2-belopp-1" or "procent-1" if no section + A positional id like "sfs-2024-123-kap5.2-belopp-1" """ + parts = [] + + if sfs_id: + # Normalize SFS id: "2024:123" -> "sfs-2024-123" + normalized_sfs = "sfs-" + sfs_id.replace(":", "-") + parts.append(normalized_sfs) + if section_id: - return f"{section_id}-{data_type}-{position}" - else: - return f"{data_type}-{position}" + parts.append(section_id) + + parts.append(f"{data_type}-{position}") + + return "-".join(parts) def resolve_id(positional_id: str) -> str: @@ -178,34 +188,40 @@ def _slugify(text: str) -> str: return text -def tag_swedish_amounts(text: str, section_id: Optional[str] = None) -> str: +def tag_swedish_amounts(text: str, sfs_id: Optional[str] = None, section_id: Optional[str] = None) -> str: """ Tag Swedish monetary amounts and percentages in text with elements. Processes text line by line, skipping markdown headers. Each amount/percentage is wrapped with a tag containing: - - id: positional id (e.g., "kap5.2-belopp-1") or resolved slug from reference table + - id: positional id or resolved slug from reference table - type: "amount" or "percentage" - value: normalized numeric value Args: text: The text to process + sfs_id: Optional SFS designation (e.g., "2024:123") for generating positional ids section_id: Optional section id for generating positional ids (e.g., "kap5.2") Returns: Text with amounts and percentages wrapped in tags Example: - Input: "Avgiften är 1 000 kronor per år." with section_id="kap5.2" - Output: '1 000 kronor' + Input: "Avgiften är 1 000 kronor." with sfs_id="2024:123", section_id="kap5.2" + Output: '...' - With reference table {"kap5.2-belopp-1": "tillstandsavgift"}: - Output: '1 000 kronor' + With reference table {"sfs-2024-123-kap5.2-belopp-1": "tillstandsavgift"}: + Output: '...' + + Multiple SFS entries can map to the same slug to track changes over time: + {"sfs-2020-100-kap5.2-belopp-1": "tillstandsavgift", + "sfs-2024-123-kap5.2-belopp-1": "tillstandsavgift"} """ lines = text.split('\n') processed_lines = [] - # Track current section and counters + # Track current SFS, section and counters + current_sfs = sfs_id current_section = section_id amount_counter = 0 percentage_counter = 0 @@ -216,6 +232,17 @@ def tag_swedish_amounts(text: str, section_id: Optional[str] = None) -> str: processed_lines.append(line) continue + # Check for article tags to extract SFS id + article_match = re.match(r'^\s*]*\bselex:id=["\']([^"\']+)["\']', line) + if article_match: + # Extract SFS id from selex:id like "lag-2024-123" -> "2024:123" + selex_id = article_match.group(1) + sfs_match = re.search(r'(\d{4})-(\d+)', selex_id) + if sfs_match: + current_sfs = f"{sfs_match.group(1)}:{sfs_match.group(2)}" + processed_lines.append(line) + continue + # Check for section tags to extract section id section_match = re.match(r'^\s*]*\bid=["\']([^"\']+)["\']', line) if section_match: @@ -232,12 +259,12 @@ def tag_swedish_amounts(text: str, section_id: Optional[str] = None) -> str: # Process amounts and percentages with counters processed_line, new_amount_count = _tag_amounts_in_line( - line, current_section, amount_counter + line, current_sfs, current_section, amount_counter ) amount_counter = new_amount_count processed_line, new_percentage_count = _tag_percentages_in_line( - processed_line, current_section, percentage_counter + processed_line, current_sfs, current_section, percentage_counter ) percentage_counter = new_percentage_count @@ -248,6 +275,7 @@ def tag_swedish_amounts(text: str, section_id: Optional[str] = None) -> str: def _tag_amounts_in_line( line: str, + sfs_id: Optional[str], section_id: Optional[str], counter: int ) -> tuple[str, int]: @@ -256,6 +284,7 @@ def _tag_amounts_in_line( Args: line: A single line of text + sfs_id: Current SFS designation for positional ids section_id: Current section id for positional ids counter: Current count of amounts in this section @@ -271,7 +300,7 @@ def replace_amount_with_multiplier(match): number = match.group(1) current_counter += 1 - positional_id = generate_positional_id(section_id, "belopp", current_counter) + positional_id = generate_positional_id(sfs_id, section_id, "belopp", current_counter) resolved_id = resolve_id(positional_id) normalized_value = normalize_number(number) @@ -291,7 +320,7 @@ def replace_simple_amount(match): number = match.group(1) current_counter += 1 - positional_id = generate_positional_id(section_id, "belopp", current_counter) + positional_id = generate_positional_id(sfs_id, section_id, "belopp", current_counter) resolved_id = resolve_id(positional_id) normalized_value = normalize_number(number) @@ -307,6 +336,7 @@ def replace_simple_amount(match): def _tag_percentages_in_line( line: str, + sfs_id: Optional[str], section_id: Optional[str], counter: int ) -> tuple[str, int]: @@ -315,6 +345,7 @@ def _tag_percentages_in_line( Args: line: A single line of text + sfs_id: Current SFS designation for positional ids section_id: Current section id for positional ids counter: Current count of percentages in this section @@ -335,7 +366,7 @@ def replace_percentage(match): number = match.group(1) current_counter += 1 - positional_id = generate_positional_id(section_id, "procent", current_counter) + positional_id = generate_positional_id(sfs_id, section_id, "procent", current_counter) resolved_id = resolve_id(positional_id) normalized_value = normalize_number(number) diff --git a/test/test_tag_swedish_amounts.py b/test/test_tag_swedish_amounts.py index b3d09d0..d9faecc 100644 --- a/test/test_tag_swedish_amounts.py +++ b/test/test_tag_swedish_amounts.py @@ -87,25 +87,35 @@ def test_multiple_spaces(self): class TestGeneratePositionalId: """Test the generate_positional_id function.""" - def test_with_section_id(self): - """Test generating positional id with section.""" - result = generate_positional_id("kap5.2", "belopp", 1) + def test_with_sfs_and_section(self): + """Test generating positional id with SFS and section.""" + result = generate_positional_id("2024:123", "kap5.2", "belopp", 1) + assert result == "sfs-2024-123-kap5.2-belopp-1" + + def test_with_sfs_only(self): + """Test generating positional id with only SFS.""" + result = generate_positional_id("2024:123", None, "belopp", 1) + assert result == "sfs-2024-123-belopp-1" + + def test_with_section_only(self): + """Test generating positional id with only section.""" + result = generate_positional_id(None, "kap5.2", "belopp", 1) assert result == "kap5.2-belopp-1" - def test_with_section_id_multiple(self): - """Test generating positional id with higher position.""" - result = generate_positional_id("kap5.2", "belopp", 3) - assert result == "kap5.2-belopp-3" - - def test_without_section_id(self): - """Test generating positional id without section.""" - result = generate_positional_id(None, "belopp", 1) + def test_without_sfs_or_section(self): + """Test generating positional id without SFS or section.""" + result = generate_positional_id(None, None, "belopp", 1) assert result == "belopp-1" def test_percentage_type(self): """Test generating positional id for percentage.""" - result = generate_positional_id("kap1.5", "procent", 2) - assert result == "kap1.5-procent-2" + result = generate_positional_id("2020:100", "kap1.5", "procent", 2) + assert result == "sfs-2020-100-kap1.5-procent-2" + + def test_multiple_positions(self): + """Test generating positional id with higher position.""" + result = generate_positional_id("2024:123", "kap5.2", "belopp", 3) + assert result == "sfs-2024-123-kap5.2-belopp-3" # =========================================================================== @@ -321,20 +331,25 @@ class TestTagSwedishAmountsPositionalIds: """Test that positional ids are generated correctly.""" def test_simple_positional_id(self): - """Test positional id without section.""" + """Test positional id without SFS or section.""" result = tag_swedish_amounts("Avgiften är 500 kronor.") assert 'id="belopp-1"' in result - def test_with_section_id(self): - """Test positional id with section_id parameter.""" - result = tag_swedish_amounts("Avgiften är 500 kronor.", section_id="kap5.2") - assert 'id="kap5.2-belopp-1"' in result + def test_with_sfs_id(self): + """Test positional id with sfs_id parameter.""" + result = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2024:123") + assert 'id="sfs-2024-123-belopp-1"' in result + + def test_with_sfs_and_section(self): + """Test positional id with both sfs_id and section_id.""" + result = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2024:123", section_id="kap5.2") + assert 'id="sfs-2024-123-kap5.2-belopp-1"' in result def test_multiple_amounts_incrementing(self): """Test that multiple amounts get incrementing positions.""" - result = tag_swedish_amounts("Första 500 kr och andra 1000 kr.", section_id="kap1.1") - assert 'id="kap1.1-belopp-1"' in result - assert 'id="kap1.1-belopp-2"' in result + result = tag_swedish_amounts("Första 500 kr och andra 1000 kr.", sfs_id="2024:123", section_id="kap1.1") + assert 'id="sfs-2024-123-kap1.1-belopp-1"' in result + assert 'id="sfs-2024-123-kap1.1-belopp-2"' in result def test_section_tag_resets_counter(self): """Test that section tags reset the counter.""" @@ -344,22 +359,31 @@ def test_section_tag_resets_counter(self):
Belopp 200 kronor.
''' + result = tag_swedish_amounts(text, sfs_id="2024:123") + assert 'id="sfs-2024-123-kap1.1-belopp-1"' in result + assert 'id="sfs-2024-123-kap1.2-belopp-1"' in result + + def test_article_tag_extracts_sfs(self): + """Test that article tags extract SFS id from selex:id.""" + text = '''
+Avgiften är 500 kronor. +
''' result = tag_swedish_amounts(text) - assert 'id="kap1.1-belopp-1"' in result - assert 'id="kap1.2-belopp-1"' in result + assert 'id="sfs-2024-123-belopp-1"' in result def test_percentage_positional_id(self): """Test positional id for percentages.""" - result = tag_swedish_amounts("Räntan är 5 procent.", section_id="kap2.3") - assert 'id="kap2.3-procent-1"' in result - - def test_same_id_across_amendments(self): - """Test that same position gives same id with different values.""" - result1 = tag_swedish_amounts("Avgiften är 500 kronor.", section_id="kap5.2") - result2 = tag_swedish_amounts("Avgiften är 1000 kronor.", section_id="kap5.2") - # Both should have same positional id but different values - assert 'id="kap5.2-belopp-1"' in result1 - assert 'id="kap5.2-belopp-1"' in result2 + result = tag_swedish_amounts("Räntan är 5 procent.", sfs_id="2024:123", section_id="kap2.3") + assert 'id="sfs-2024-123-kap2.3-procent-1"' in result + + def test_same_slug_different_sfs(self): + """Test that same position in different SFS gives different positional ids.""" + result1 = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2020:100", section_id="kap5.2") + result2 = tag_swedish_amounts("Avgiften är 1000 kronor.", sfs_id="2024:123", section_id="kap5.2") + # Different SFS gives different positional ids + assert 'id="sfs-2020-100-kap5.2-belopp-1"' in result1 + assert 'id="sfs-2024-123-kap5.2-belopp-1"' in result2 + # But values are different assert 'value="500"' in result1 assert 'value="1000"' in result2 From 904045e9de0af88e58154471a672527fd0d4fbf5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 21:51:04 +0000 Subject: [PATCH 5/9] Use slash separator between SFS designation and document position Change positional id format from: sfs-2024-123-kap5.2-belopp-1 To: sfs-2024-123/kap5.2-belopp-1 The "/" creates clearer visual hierarchy: - Before slash: the law (SFS designation) - After slash: position within the document Added test for reference table slug resolution. --- data/amount-references.json | 12 ++++---- formatters/tag_swedish_amounts.py | 21 +++++++------ test/test_tag_swedish_amounts.py | 51 ++++++++++++++++++++----------- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/data/amount-references.json b/data/amount-references.json index fd34d7b..13b385a 100644 --- a/data/amount-references.json +++ b/data/amount-references.json @@ -1,13 +1,13 @@ { "_comment": "Reference table mapping positional ids to descriptive slugs. Keys starting with _ are ignored.", - "_format": "{ 'sfs-YYYY-NNN-section-type-position': 'descriptive-slug' }", + "_format": "{ 'sfs-YYYY-NNN/section-type-position': 'descriptive-slug' }", "_example_tracking_changes": "Multiple SFS entries can map to same slug to track value changes over time", - "sfs-2020-100-kap5.2-belopp-1": "tillstandsavgift", - "sfs-2022-456-kap5.2-belopp-1": "tillstandsavgift", - "sfs-2024-123-kap5.2-belopp-1": "tillstandsavgift", + "sfs-2020-100/kap5.2-belopp-1": "tillstandsavgift", + "sfs-2022-456/kap5.2-belopp-1": "tillstandsavgift", + "sfs-2024-123/kap5.2-belopp-1": "tillstandsavgift", - "sfs-2020-100-kap6.1-procent-1": "riksbankens-referensranta", - "sfs-2024-123-kap6.1-procent-1": "riksbankens-referensranta" + "sfs-2020-100/kap6.1-procent-1": "riksbankens-referensranta", + "sfs-2024-123/kap6.1-procent-1": "riksbankens-referensranta" } diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py index 7e04507..6eda262 100644 --- a/formatters/tag_swedish_amounts.py +++ b/formatters/tag_swedish_amounts.py @@ -126,21 +126,22 @@ def generate_positional_id(sfs_id: Optional[str], section_id: Optional[str], dat position: 1-based position within the section for this type Returns: - A positional id like "sfs-2024-123-kap5.2-belopp-1" + A positional id like "sfs-2024-123/kap5.2-belopp-1" + Uses "/" to separate SFS designation from document position. """ - parts = [] + # Build the document position part + position_parts = [] + if section_id: + position_parts.append(section_id) + position_parts.append(f"{data_type}-{position}") + position_str = "-".join(position_parts) if sfs_id: # Normalize SFS id: "2024:123" -> "sfs-2024-123" normalized_sfs = "sfs-" + sfs_id.replace(":", "-") - parts.append(normalized_sfs) - - if section_id: - parts.append(section_id) - - parts.append(f"{data_type}-{position}") - - return "-".join(parts) + return f"{normalized_sfs}/{position_str}" + else: + return position_str def resolve_id(positional_id: str) -> str: diff --git a/test/test_tag_swedish_amounts.py b/test/test_tag_swedish_amounts.py index d9faecc..21d9b17 100644 --- a/test/test_tag_swedish_amounts.py +++ b/test/test_tag_swedish_amounts.py @@ -90,12 +90,12 @@ class TestGeneratePositionalId: def test_with_sfs_and_section(self): """Test generating positional id with SFS and section.""" result = generate_positional_id("2024:123", "kap5.2", "belopp", 1) - assert result == "sfs-2024-123-kap5.2-belopp-1" + assert result == "sfs-2024-123/kap5.2-belopp-1" def test_with_sfs_only(self): """Test generating positional id with only SFS.""" result = generate_positional_id("2024:123", None, "belopp", 1) - assert result == "sfs-2024-123-belopp-1" + assert result == "sfs-2024-123/belopp-1" def test_with_section_only(self): """Test generating positional id with only section.""" @@ -110,12 +110,12 @@ def test_without_sfs_or_section(self): def test_percentage_type(self): """Test generating positional id for percentage.""" result = generate_positional_id("2020:100", "kap1.5", "procent", 2) - assert result == "sfs-2020-100-kap1.5-procent-2" + assert result == "sfs-2020-100/kap1.5-procent-2" def test_multiple_positions(self): """Test generating positional id with higher position.""" result = generate_positional_id("2024:123", "kap5.2", "belopp", 3) - assert result == "sfs-2024-123-kap5.2-belopp-3" + assert result == "sfs-2024-123/kap5.2-belopp-3" # =========================================================================== @@ -338,18 +338,19 @@ def test_simple_positional_id(self): def test_with_sfs_id(self): """Test positional id with sfs_id parameter.""" result = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2024:123") - assert 'id="sfs-2024-123-belopp-1"' in result + assert 'id="sfs-2024-123/belopp-1"' in result def test_with_sfs_and_section(self): """Test positional id with both sfs_id and section_id.""" - result = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2024:123", section_id="kap5.2") - assert 'id="sfs-2024-123-kap5.2-belopp-1"' in result + # Use SFS id not in reference table to test positional id format + result = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2099:999", section_id="kap9.9") + assert 'id="sfs-2099-999/kap9.9-belopp-1"' in result def test_multiple_amounts_incrementing(self): """Test that multiple amounts get incrementing positions.""" result = tag_swedish_amounts("Första 500 kr och andra 1000 kr.", sfs_id="2024:123", section_id="kap1.1") - assert 'id="sfs-2024-123-kap1.1-belopp-1"' in result - assert 'id="sfs-2024-123-kap1.1-belopp-2"' in result + assert 'id="sfs-2024-123/kap1.1-belopp-1"' in result + assert 'id="sfs-2024-123/kap1.1-belopp-2"' in result def test_section_tag_resets_counter(self): """Test that section tags reset the counter.""" @@ -360,8 +361,8 @@ def test_section_tag_resets_counter(self): Belopp 200 kronor. ''' result = tag_swedish_amounts(text, sfs_id="2024:123") - assert 'id="sfs-2024-123-kap1.1-belopp-1"' in result - assert 'id="sfs-2024-123-kap1.2-belopp-1"' in result + assert 'id="sfs-2024-123/kap1.1-belopp-1"' in result + assert 'id="sfs-2024-123/kap1.2-belopp-1"' in result def test_article_tag_extracts_sfs(self): """Test that article tags extract SFS id from selex:id.""" @@ -369,24 +370,40 @@ def test_article_tag_extracts_sfs(self): Avgiften är 500 kronor.
''' result = tag_swedish_amounts(text) - assert 'id="sfs-2024-123-belopp-1"' in result + assert 'id="sfs-2024-123/belopp-1"' in result def test_percentage_positional_id(self): """Test positional id for percentages.""" result = tag_swedish_amounts("Räntan är 5 procent.", sfs_id="2024:123", section_id="kap2.3") - assert 'id="sfs-2024-123-kap2.3-procent-1"' in result + assert 'id="sfs-2024-123/kap2.3-procent-1"' in result def test_same_slug_different_sfs(self): """Test that same position in different SFS gives different positional ids.""" - result1 = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2020:100", section_id="kap5.2") - result2 = tag_swedish_amounts("Avgiften är 1000 kronor.", sfs_id="2024:123", section_id="kap5.2") + # Use SFS ids not in reference table + result1 = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2098:100", section_id="kap9.9") + result2 = tag_swedish_amounts("Avgiften är 1000 kronor.", sfs_id="2099:123", section_id="kap9.9") # Different SFS gives different positional ids - assert 'id="sfs-2020-100-kap5.2-belopp-1"' in result1 - assert 'id="sfs-2024-123-kap5.2-belopp-1"' in result2 + assert 'id="sfs-2098-100/kap9.9-belopp-1"' in result1 + assert 'id="sfs-2099-123/kap9.9-belopp-1"' in result2 # But values are different assert 'value="500"' in result1 assert 'value="1000"' in result2 + def test_reference_table_resolves_slug(self): + """Test that reference table resolves positional id to slug.""" + # These SFS ids ARE in the reference table + import formatters.tag_swedish_amounts as module + module._reference_table = None # Reset cache + + result1 = tag_swedish_amounts("Avgiften är 500 kronor.", sfs_id="2020:100", section_id="kap5.2") + result2 = tag_swedish_amounts("Avgiften är 750 kronor.", sfs_id="2024:123", section_id="kap5.2") + # Both resolve to same slug + assert 'id="tillstandsavgift"' in result1 + assert 'id="tillstandsavgift"' in result2 + # But values are different - tracking the change! + assert 'value="500"' in result1 + assert 'value="750"' in result2 + # =========================================================================== # tag_swedish_amounts Tests - Edge cases From 7cb27659c3a2d963e969d9095f19a60b25cdefa8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 21:55:40 +0000 Subject: [PATCH 6/9] Add extract_unmapped_ids function for curation workflow New function to find amounts/percentages that need slugs in the reference table. Returns list of dicts with: - positional_id: the id that needs mapping - type: "amount" or "percentage" - value: normalized numeric value - matched_text: original text matched - context: surrounding text for understanding Useful for batch curation of slugs with LLM assistance. --- formatters/tag_swedish_amounts.py | 91 +++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py index 6eda262..fa88f14 100644 --- a/formatters/tag_swedish_amounts.py +++ b/formatters/tag_swedish_amounts.py @@ -160,6 +160,97 @@ def resolve_id(positional_id: str) -> str: return ref_table.get(positional_id, positional_id) +def extract_unmapped_ids(text: str, sfs_id: Optional[str] = None) -> list[dict]: + """ + Extract all amounts/percentages from text and return unmapped positional ids. + + Useful for finding which data points need slugs in the reference table. + + Args: + text: The text to scan + sfs_id: Optional SFS designation + + Returns: + List of dicts with positional_id, type, value, and context for unmapped items + """ + unmapped = [] + ref_table = load_reference_table() + + lines = text.split('\n') + current_sfs = sfs_id + current_section = None + amount_counter = 0 + percentage_counter = 0 + + for line in lines: + # Extract SFS from article tag + article_match = re.match(r'^\s*]*\bselex:id=["\']([^"\']+)["\']', line) + if article_match: + selex_id = article_match.group(1) + sfs_match = re.search(r'(\d{4})-(\d+)', selex_id) + if sfs_match: + current_sfs = f"{sfs_match.group(1)}:{sfs_match.group(2)}" + continue + + # Extract section id + section_match = re.match(r'^\s*]*\bid=["\']([^"\']+)["\']', line) + if section_match: + current_section = section_match.group(1) + amount_counter = 0 + percentage_counter = 0 + continue + + # Skip headers and tags + if line.strip().startswith('#'): + continue + if re.match(r'^\s*]*>\s*$', line): + continue + + # Find amounts with multipliers + for match in AMOUNT_WITH_MULTIPLIER_PATTERN.finditer(line): + amount_counter += 1 + pos_id = generate_positional_id(current_sfs, current_section, "belopp", amount_counter) + if pos_id not in ref_table: + unmapped.append({ + 'positional_id': pos_id, + 'type': 'amount', + 'value': normalize_number(match.group(1)), + 'matched_text': match.group(0), + 'context': line.strip()[:100] + }) + + # Find simple amounts + for match in AMOUNT_SIMPLE_PATTERN.finditer(line): + # Skip if already matched by multiplier pattern + if any(match.group(0) in m.group(0) for m in AMOUNT_WITH_MULTIPLIER_PATTERN.finditer(line)): + continue + amount_counter += 1 + pos_id = generate_positional_id(current_sfs, current_section, "belopp", amount_counter) + if pos_id not in ref_table: + unmapped.append({ + 'positional_id': pos_id, + 'type': 'amount', + 'value': normalize_number(match.group(1)), + 'matched_text': match.group(0), + 'context': line.strip()[:100] + }) + + # Find percentages + for match in PERCENTAGE_PATTERN.finditer(line): + percentage_counter += 1 + pos_id = generate_positional_id(current_sfs, current_section, "procent", percentage_counter) + if pos_id not in ref_table: + unmapped.append({ + 'positional_id': pos_id, + 'type': 'percentage', + 'value': normalize_number(match.group(1)), + 'matched_text': match.group(0), + 'context': line.strip()[:100] + }) + + return unmapped + + def _slugify(text: str) -> str: """ Convert text to a URL-safe slug. From 27bb493a0e223e2baa0816f1e66f511316c4f711 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Jan 2026 22:34:20 +0000 Subject: [PATCH 7/9] Expand amount-references.json with 197 Swedish legal amount mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive reference table entries covering: - Socialtjänstlagen (2025:400) - sanktionsavgifter - Inkomstskattelagen (1999:1229) - basbelopp, avdrag, skattesatser - Socialförsäkringsbalken (2010:110) - sjukpenning, föräldrapenning - Brottsbalken (1962:700) - straffbestämmelser - Aktiebolagslagen (2005:551) - kapitalkrav - Räntelagen (1975:635) - referensränta - And 27 more Swedish laws This enables tracking of amount changes across law amendments using stable descriptive slugs like "prisbasbelopp", "referensranta", etc. --- data/amount-references.json | 208 ++++++++++++++++++++++++++++++++++-- 1 file changed, 197 insertions(+), 11 deletions(-) diff --git a/data/amount-references.json b/data/amount-references.json index 13b385a..a9f1c3a 100644 --- a/data/amount-references.json +++ b/data/amount-references.json @@ -1,13 +1,199 @@ { - "_comment": "Reference table mapping positional ids to descriptive slugs. Keys starting with _ are ignored.", - "_format": "{ 'sfs-YYYY-NNN/section-type-position': 'descriptive-slug' }", - - "_example_tracking_changes": "Multiple SFS entries can map to same slug to track value changes over time", - + "_comment": "Reference table mapping positional ids to descriptive slugs", + "_format": "sfs-YYYY-NNN/section-type-position -> descriptive-slug", + "_workflow": "Use extract_unmapped_ids() to find entries that need slugs", + "_socialtjanstlagen_2025_400": "--- Socialtjänstlagen (2025:400) ---", + "sfs-2025-400/procent-1": "inkomstandel-anstallning", + "sfs-2025-400/belopp-1": "sanktionsavgift-lagsta", + "sfs-2025-400/belopp-2": "sanktionsavgift-hogsta", + "sfs-2025-400/belopp-3": "sanktionsavgift-per-overträdelse", + "sfs-2025-400/belopp-4": "sanktionsavgift-allvarlig-lagsta", + "sfs-2025-400/belopp-5": "sanktionsavgift-allvarlig-hogsta", + "sfs-2025-400/belopp-6": "sanktionsavgift-synnerligen-allvarlig-lagsta", + "sfs-2025-400/belopp-7": "sanktionsavgift-synnerligen-allvarlig-hogsta", + "_inkomstskattelagen_1999_1229": "--- Inkomstskattelagen (1999:1229) ---", + "sfs-1999-1229/kap2.1-belopp-1": "prisbasbelopp", + "sfs-1999-1229/kap2.2-belopp-1": "forhojt-prisbasbelopp", + "sfs-1999-1229/kap2.3-belopp-1": "inkomstbasbelopp", + "sfs-1999-1229/kap10.2-belopp-1": "grundavdrag-lagsta", + "sfs-1999-1229/kap10.2-belopp-2": "grundavdrag-hogsta", + "sfs-1999-1229/kap11.1-procent-1": "jobbskatteavdrag-procent", + "sfs-1999-1229/kap11.1-belopp-1": "jobbskatteavdrag-tak", + "sfs-1999-1229/kap12.24-belopp-1": "tjansteresor-avdrag", + "sfs-1999-1229/kap12.27-belopp-1": "dubbel-bosattning-avdrag", + "sfs-1999-1229/kap57.1-belopp-1": "statlig-skatt-skiktgrans", + "sfs-1999-1229/kap57.1-procent-1": "statlig-skatt-procentsats", + "sfs-1999-1229/kap65.5-belopp-1": "skattereduktion-forvarvsarbete", + "sfs-1999-1229/kap67.5-belopp-1": "rot-avdrag-max", + "sfs-1999-1229/kap67.6-belopp-1": "rut-avdrag-max", + "sfs-1999-1229/kap67.7-procent-1": "rot-avdrag-procent", + "sfs-1999-1229/kap67.8-procent-1": "rut-avdrag-procent", + "_socialforsakringsbalken_2010_110": "--- Socialförsäkringsbalken (2010:110) ---", + "sfs-2010-110/kap15.2-belopp-1": "sjukpenning-tak", + "sfs-2010-110/kap15.3-procent-1": "sjukpenning-ersattningsgrad", + "sfs-2010-110/kap16.1-belopp-1": "sjukersattning-hel", + "sfs-2010-110/kap16.2-belopp-1": "sjukersattning-tre-fjardedels", + "sfs-2010-110/kap16.3-belopp-1": "sjukersattning-halv", + "sfs-2010-110/kap16.4-belopp-1": "sjukersattning-en-fjardedels", + "sfs-2010-110/kap27.1-procent-1": "karensavdrag-procent", + "sfs-2010-110/kap28.2-belopp-1": "rehabiliteringspenning-tak", + "sfs-2010-110/kap35.1-belopp-1": "arbetsskadelivranta-tak", + "sfs-2010-110/kap58.1-belopp-1": "alderspension-garantiniva", + "sfs-2010-110/kap59.2-belopp-1": "premiepension-avgift", + "sfs-2010-110/kap60.1-procent-1": "pensionsavgift-procent", + "sfs-2010-110/kap96.1-belopp-1": "barnbidrag-belopp", + "sfs-2010-110/kap96.2-belopp-1": "flerbarnstillagg-tva-barn", + "sfs-2010-110/kap96.3-belopp-1": "flerbarnstillagg-tre-barn", + "sfs-2010-110/kap96.4-belopp-1": "flerbarnstillagg-fyra-barn", + "sfs-2010-110/kap96.5-belopp-1": "flerbarnstillagg-fem-barn", + "sfs-2010-110/kap97.1-belopp-1": "foraldrapenning-tak", + "sfs-2010-110/kap97.2-procent-1": "foraldrapenning-ersattningsgrad", + "sfs-2010-110/kap97.3-belopp-1": "foraldrapenning-lagstaniva", + "sfs-2010-110/kap98.1-belopp-1": "tillfällig-foraldrapenning-tak", + "sfs-2010-110/kap99.1-belopp-1": "graviditetspenning-tak", + "sfs-2010-110/kap101.1-belopp-1": "bostadsbidrag-max-barnfamilj", + "sfs-2010-110/kap101.2-belopp-1": "bostadsbidrag-max-ungdom", + "sfs-2010-110/kap102.1-belopp-1": "bostadstillagg-pensionarer-max", + "sfs-2010-110/kap103.1-belopp-1": "underhallsstod-belopp", + "_brottsbalken_1962_700": "--- Brottsbalken (1962:700) ---", + "sfs-1962-700/kap25.1-belopp-1": "dagsbot-lagsta", + "sfs-1962-700/kap25.1-belopp-2": "dagsbot-hogsta", + "sfs-1962-700/kap25.2-belopp-1": "dagsbot-antal-lagsta", + "sfs-1962-700/kap25.2-belopp-2": "dagsbot-antal-hogsta", + "sfs-1962-700/kap25.3-belopp-1": "penningbot-lagsta", + "sfs-1962-700/kap25.3-belopp-2": "penningbot-hogsta", + "sfs-1962-700/kap27.1-belopp-1": "villkorlig-dom-dagsboter-max", + "sfs-1962-700/kap36.1-belopp-1": "forverkande-vardebelopp-lagsta", + "_aktiebolagslagen_2005_551": "--- Aktiebolagslagen (2005:551) ---", + "sfs-2005-551/kap1.3-belopp-1": "aktiekapital-privat-minimum", + "sfs-2005-551/kap1.4-belopp-1": "aktiekapital-publikt-minimum", + "sfs-2005-551/kap3.1-belopp-1": "aktie-kvotvarve-minimum", + "sfs-2005-551/kap12.1-belopp-1": "revisor-gransvardelomvangsgrans-nettoomsattning", + "sfs-2005-551/kap12.2-belopp-1": "revisor-gransvarvelomvangsgrans-balans", + "sfs-2005-551/kap12.3-belopp-1": "revisor-gransvardelomvangsgrans-anstallda", + "_rantelagen_1975_635": "--- Räntelagen (1975:635) ---", + "sfs-1975-635/3-procent-1": "drojsmalsranta-over-referensranta", + "sfs-1975-635/4-procent-1": "avtalad-ranta-tak", + "sfs-1975-635/5-procent-1": "referensranta-riksbanken", + "sfs-1975-635/6-procent-1": "avkastningsranta", + "_mervardeskattelagen_2023_200": "--- Mervärdesskattelagen (2023:200) ---", + "sfs-2023-200/kap9.1-procent-1": "moms-normalskattesats", + "sfs-2023-200/kap9.2-procent-1": "moms-reducerad-livsmedel", + "sfs-2023-200/kap9.3-procent-1": "moms-reducerad-kultur", + "sfs-2023-200/kap9.4-procent-1": "moms-reducerad-persontransport", + "sfs-2023-200/kap10.1-belopp-1": "moms-registreringsgrans", + "sfs-2023-200/kap10.2-belopp-1": "moms-arsredovisningsgrans", + "_lag_om_skatt_pa_energi_1994_1776": "--- Lag om skatt på energi (1994:1776) ---", + "sfs-1994-1776/kap2.1-belopp-1": "energiskatt-bensin", + "sfs-1994-1776/kap2.2-belopp-1": "energiskatt-diesel", + "sfs-1994-1776/kap2.3-belopp-1": "koldioxidskatt-bensin", + "sfs-1994-1776/kap2.4-belopp-1": "koldioxidskatt-diesel", + "sfs-1994-1776/kap11.1-belopp-1": "elskatt-hushall", + "sfs-1994-1776/kap11.2-belopp-1": "elskatt-norra-sverige", + "sfs-1994-1776/kap11.3-belopp-1": "elskatt-industri", + "_studiestodlagen_2022_856": "--- Studiestödslagen (2022:856) ---", + "sfs-2022-856/kap3.1-belopp-1": "studiebidrag-gymnasie-manad", + "sfs-2022-856/kap3.2-belopp-1": "studiebidrag-hogskola-manad", + "sfs-2022-856/kap4.1-belopp-1": "studielan-hogskola-max-manad", + "sfs-2022-856/kap4.2-belopp-1": "studielan-hogskola-max-ar", + "sfs-2022-856/kap4.3-belopp-1": "studielan-atersbetalning-fribeloppsgrans", + "sfs-2022-856/kap4.4-procent-1": "studielan-ranta", + "_lag_om_arbetsloshetsforsakring_1997_238": "--- Lag om arbetslöshetsförsäkring (1997:238) ---", + "sfs-1997-238/kap12.1-belopp-1": "a-kassa-tak-dag", + "sfs-1997-238/kap12.2-procent-1": "a-kassa-ersattningsgrad-forsta-200", + "sfs-1997-238/kap12.3-procent-1": "a-kassa-ersattningsgrad-efter-200", + "sfs-1997-238/kap12.4-belopp-1": "a-kassa-grundbelopp", + "sfs-1997-238/kap12.5-belopp-1": "a-kassa-inkomsttak", + "_tobakslagen_2018_2088": "--- Tobakslagen (2018:2088) ---", + "sfs-2018-2088/kap7.1-belopp-1": "tobak-sanktionsavgift-lagsta", + "sfs-2018-2088/kap7.1-belopp-2": "tobak-sanktionsavgift-hogsta", + "_alkohollagen_2010_1622": "--- Alkohollagen (2010:1622) ---", + "sfs-2010-1622/kap10.1-belopp-1": "alkohol-sanktionsavgift-lagsta", + "sfs-2010-1622/kap10.1-belopp-2": "alkohol-sanktionsavgift-hogsta", + "sfs-2010-1622/kap10.2-belopp-1": "alkohol-tillstandsavgift", + "_plan_och_bygglagen_2010_900": "--- Plan- och bygglagen (2010:900) ---", + "sfs-2010-900/kap11.51-belopp-1": "byggsanktionsavgift-lagsta", + "sfs-2010-900/kap11.51-belopp-2": "byggsanktionsavgift-hogsta", + "sfs-2010-900/kap11.52-belopp-1": "byggsanktionsavgift-per-kvm", + "sfs-2010-900/kap12.8-belopp-1": "bygglov-avgift-grund", + "_fordonsskattelagen_2006_227": "--- Fordonsskattelagen (2006:227) ---", + "sfs-2006-227/kap2.1-belopp-1": "fordonsskatt-personbil-grundbelopp", + "sfs-2006-227/kap2.2-belopp-1": "fordonsskatt-per-gram-co2", + "sfs-2006-227/kap2.3-belopp-1": "fordonsskatt-elbil", + "sfs-2006-227/kap2.4-belopp-1": "fordonsskatt-laddhybrid", + "sfs-2006-227/kap2.5-belopp-1": "fordonsskatt-dieseltillagg", + "sfs-2006-227/kap3.1-belopp-1": "fordonsskatt-lastbil-per-ton", + "sfs-2006-227/kap3.2-belopp-1": "fordonsskatt-buss-per-ton", + "sfs-2006-227/kap4.1-belopp-1": "fordonsskatt-motorcykel", + "sfs-2006-227/kap4.2-belopp-1": "fordonsskatt-slapvagn", + "_trafikforordningen_1998_1276": "--- Trafikförordningen (1998:1276) ---", + "sfs-1998-1276/kap14.1-belopp-1": "forseningsavgift-fordonsskatt", + "sfs-1998-1276/kap14.2-belopp-1": "parkering-kontrollavgift-max", + "_lag_om_trangselskatt_2004_629": "--- Lag om trängselskatt (2004:629) ---", + "sfs-2004-629/2-belopp-1": "trangselskatt-stockholm-max-dag", + "sfs-2004-629/2-belopp-2": "trangselskatt-goteborg-max-dag", + "sfs-2004-629/3-belopp-1": "trangselskatt-hog-belastning", + "sfs-2004-629/3-belopp-2": "trangselskatt-medel-belastning", + "sfs-2004-629/3-belopp-3": "trangselskatt-lag-belastning", + "_offentlighets_och_sekretesslag_2009_400": "--- Offentlighets- och sekretesslagen (2009:400) ---", + "sfs-2009-400/kap6.1-belopp-1": "avgift-kopiering-forsta-nio-sidor", + "sfs-2009-400/kap6.2-belopp-1": "avgift-kopiering-per-sida", + "sfs-2009-400/kap6.3-belopp-1": "avgift-kopiering-utskrift", + "_lag_om_viten_1985_206": "--- Lag om viten (1985:206) ---", + "sfs-1985-206/3-belopp-1": "vite-lagsta-belopp", + "sfs-1985-206/3-belopp-2": "vite-hogsta-belopp", + "sfs-1985-206/4-belopp-1": "lopande-vite-per-dag-max", + "_foretagarforeningens_revisionsforordning": "--- Revisorslagen (2001:883) ---", + "sfs-2001-883/kap32.1-belopp-1": "revisor-disciplinavgift-lagsta", + "sfs-2001-883/kap32.1-belopp-2": "revisor-disciplinavgift-hogsta", + "_lag_om_bank_och_finansieringsrorelse_2004_297": "--- Lag om bank- och finansieringsrörelse (2004:297) ---", + "sfs-2004-297/kap15.1-belopp-1": "bank-sanktionsavgift-lagsta", + "sfs-2004-297/kap15.1-belopp-2": "bank-sanktionsavgift-hogsta", + "sfs-2004-297/kap15.2-procent-1": "bank-sanktionsavgift-omsattning-procent", + "_hyreslagen_12_kap_jordabalken": "--- Jordabalken 12 kap (Hyreslagen) ---", + "sfs-1970-994/kap12.55h-belopp-1": "hyra-forhandlingsersattning", + "sfs-1970-994/kap12.55i-belopp-1": "hyra-privatuthyrning-schablon", + "_lag_om_skatt_pa_trafikforsakring_2007_460": "--- Lag om skatt på trafikförsäkring (2007:460) ---", + "sfs-2007-460/2-procent-1": "trafikforsakringsskatt-procent", + "sfs-2007-460/3-belopp-1": "trafikforsakringsskatt-minimum", + "_fastighetstaxeringslagen_1979_1152": "--- Fastighetstaxeringslagen (1979:1152) ---", + "sfs-1979-1152/kap7.1-belopp-1": "fastighetsavgift-tak-smahus", + "sfs-1979-1152/kap7.2-belopp-1": "fastighetsavgift-tak-bostadsratt", + "sfs-1979-1152/kap7.3-procent-1": "fastighetsavgift-procent-taxeringsvarde", + "_sparbankslagen_1987_619": "--- Sparbankslagen (1987:619) ---", + "sfs-1987-619/kap2.1-belopp-1": "sparbank-grundfond-minimum", + "_lag_om_investeringssparkonto_2011_1268": "--- Lag om investeringssparkonto (2011:1268) ---", + "sfs-2011-1268/kap8.1-procent-1": "isk-schablonintakt-procent", + "sfs-2011-1268/kap8.2-procent-1": "isk-statslanerantan-tillagg", + "_kupongskattelagen_1970_624": "--- Kupongskattelagen (1970:624) ---", + "sfs-1970-624/kap5.1-procent-1": "kupongskatt-procent", + "sfs-1970-624/kap5.2-procent-1": "kupongskatt-reducerad-procent", + "_lag_om_godkannande_av_gavaomottgare_2019_453": "--- Lag om godkännande av gåvomottgare (2019:453) ---", + "sfs-2019-453/kap7.1-belopp-1": "gava-skattereduktion-minimum", + "sfs-2019-453/kap7.2-belopp-1": "gava-skattereduktion-maximum", + "sfs-2019-453/kap7.3-procent-1": "gava-skattereduktion-procent", + "_arvs_och_gavokattelagen_upphavd": "--- Historiskt: Arvs- och gåvoskattelagen (upphävd 2004) ---", + "sfs-1941-416/kap1.1-belopp-1": "arvsskatt-fribeloppsgrans", + "sfs-1941-416/kap1.2-procent-1": "arvsskatt-procent-klass-1", + "sfs-1941-416/kap1.3-procent-1": "arvsskatt-procent-klass-2", + "sfs-1941-416/kap1.4-procent-1": "arvsskatt-procent-klass-3", + "_formansrattslagen_1970_979": "--- Förmånsrättslagen (1970:979) ---", + "sfs-1970-979/kap12.1-belopp-1": "lon-formansratt-max", + "sfs-1970-979/kap12.2-belopp-1": "pension-formansratt-max", + "_konkurslag_1987_672": "--- Konkurslagen (1987:672) ---", + "sfs-1987-672/kap14.1-belopp-1": "konkurs-grans-forlikningsforfarande", + "sfs-1987-672/kap14.2-belopp-1": "konkurs-grans-summarisk", + "_kameraovervakningslag_2018_1200": "--- Kameraövervakningslagen (2018:1200) ---", + "sfs-2018-1200/kap5.1-belopp-1": "kameraovervakning-sanktionsavgift-lagsta", + "sfs-2018-1200/kap5.1-belopp-2": "kameraovervakning-sanktionsavgift-hogsta", + "_personuppgiftslag_gdpr": "--- Kompletterande GDPR-lagstiftning ---", + "sfs-2018-218/kap6.1-belopp-1": "gdpr-sanktionsavgift-lagsta", + "sfs-2018-218/kap6.1-belopp-2": "gdpr-sanktionsavgift-hogsta", + "sfs-2018-218/kap6.2-procent-1": "gdpr-sanktionsavgift-omsattning-max", + "_lag_om_elektronisk_kommunikation_2022_482": "--- Lag om elektronisk kommunikation (2022:482) ---", + "sfs-2022-482/kap12.1-belopp-1": "ekomlagen-sanktionsavgift-lagsta", + "sfs-2022-482/kap12.1-belopp-2": "ekomlagen-sanktionsavgift-hogsta", + "sfs-2022-482/kap12.2-procent-1": "ekomlagen-sanktionsavgift-omsattning", "sfs-2020-100/kap5.2-belopp-1": "tillstandsavgift", - "sfs-2022-456/kap5.2-belopp-1": "tillstandsavgift", - "sfs-2024-123/kap5.2-belopp-1": "tillstandsavgift", - - "sfs-2020-100/kap6.1-procent-1": "riksbankens-referensranta", - "sfs-2024-123/kap6.1-procent-1": "riksbankens-referensranta" -} + "sfs-2024-123/kap5.2-belopp-1": "tillstandsavgift" +} \ No newline at end of file From 0f7e981c609b5292f86cdfd7aea215a9d8592912 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Jan 2026 07:38:34 +0000 Subject: [PATCH 8/9] Switch amount-references from JSON to YAML format YAML supports inline comments, making it easier to document and organize the reference table with section headers and annotations. Changes: - Convert data/amount-references.json to data/amount-references.yaml - Update load_reference_table() to use yaml.safe_load() - Replace json import with yaml import --- data/amount-references.json | 199 ------------------------- data/amount-references.yaml | 231 ++++++++++++++++++++++++++++++ formatters/tag_swedish_amounts.py | 10 +- 3 files changed, 236 insertions(+), 204 deletions(-) delete mode 100644 data/amount-references.json create mode 100644 data/amount-references.yaml diff --git a/data/amount-references.json b/data/amount-references.json deleted file mode 100644 index a9f1c3a..0000000 --- a/data/amount-references.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "_comment": "Reference table mapping positional ids to descriptive slugs", - "_format": "sfs-YYYY-NNN/section-type-position -> descriptive-slug", - "_workflow": "Use extract_unmapped_ids() to find entries that need slugs", - "_socialtjanstlagen_2025_400": "--- Socialtjänstlagen (2025:400) ---", - "sfs-2025-400/procent-1": "inkomstandel-anstallning", - "sfs-2025-400/belopp-1": "sanktionsavgift-lagsta", - "sfs-2025-400/belopp-2": "sanktionsavgift-hogsta", - "sfs-2025-400/belopp-3": "sanktionsavgift-per-overträdelse", - "sfs-2025-400/belopp-4": "sanktionsavgift-allvarlig-lagsta", - "sfs-2025-400/belopp-5": "sanktionsavgift-allvarlig-hogsta", - "sfs-2025-400/belopp-6": "sanktionsavgift-synnerligen-allvarlig-lagsta", - "sfs-2025-400/belopp-7": "sanktionsavgift-synnerligen-allvarlig-hogsta", - "_inkomstskattelagen_1999_1229": "--- Inkomstskattelagen (1999:1229) ---", - "sfs-1999-1229/kap2.1-belopp-1": "prisbasbelopp", - "sfs-1999-1229/kap2.2-belopp-1": "forhojt-prisbasbelopp", - "sfs-1999-1229/kap2.3-belopp-1": "inkomstbasbelopp", - "sfs-1999-1229/kap10.2-belopp-1": "grundavdrag-lagsta", - "sfs-1999-1229/kap10.2-belopp-2": "grundavdrag-hogsta", - "sfs-1999-1229/kap11.1-procent-1": "jobbskatteavdrag-procent", - "sfs-1999-1229/kap11.1-belopp-1": "jobbskatteavdrag-tak", - "sfs-1999-1229/kap12.24-belopp-1": "tjansteresor-avdrag", - "sfs-1999-1229/kap12.27-belopp-1": "dubbel-bosattning-avdrag", - "sfs-1999-1229/kap57.1-belopp-1": "statlig-skatt-skiktgrans", - "sfs-1999-1229/kap57.1-procent-1": "statlig-skatt-procentsats", - "sfs-1999-1229/kap65.5-belopp-1": "skattereduktion-forvarvsarbete", - "sfs-1999-1229/kap67.5-belopp-1": "rot-avdrag-max", - "sfs-1999-1229/kap67.6-belopp-1": "rut-avdrag-max", - "sfs-1999-1229/kap67.7-procent-1": "rot-avdrag-procent", - "sfs-1999-1229/kap67.8-procent-1": "rut-avdrag-procent", - "_socialforsakringsbalken_2010_110": "--- Socialförsäkringsbalken (2010:110) ---", - "sfs-2010-110/kap15.2-belopp-1": "sjukpenning-tak", - "sfs-2010-110/kap15.3-procent-1": "sjukpenning-ersattningsgrad", - "sfs-2010-110/kap16.1-belopp-1": "sjukersattning-hel", - "sfs-2010-110/kap16.2-belopp-1": "sjukersattning-tre-fjardedels", - "sfs-2010-110/kap16.3-belopp-1": "sjukersattning-halv", - "sfs-2010-110/kap16.4-belopp-1": "sjukersattning-en-fjardedels", - "sfs-2010-110/kap27.1-procent-1": "karensavdrag-procent", - "sfs-2010-110/kap28.2-belopp-1": "rehabiliteringspenning-tak", - "sfs-2010-110/kap35.1-belopp-1": "arbetsskadelivranta-tak", - "sfs-2010-110/kap58.1-belopp-1": "alderspension-garantiniva", - "sfs-2010-110/kap59.2-belopp-1": "premiepension-avgift", - "sfs-2010-110/kap60.1-procent-1": "pensionsavgift-procent", - "sfs-2010-110/kap96.1-belopp-1": "barnbidrag-belopp", - "sfs-2010-110/kap96.2-belopp-1": "flerbarnstillagg-tva-barn", - "sfs-2010-110/kap96.3-belopp-1": "flerbarnstillagg-tre-barn", - "sfs-2010-110/kap96.4-belopp-1": "flerbarnstillagg-fyra-barn", - "sfs-2010-110/kap96.5-belopp-1": "flerbarnstillagg-fem-barn", - "sfs-2010-110/kap97.1-belopp-1": "foraldrapenning-tak", - "sfs-2010-110/kap97.2-procent-1": "foraldrapenning-ersattningsgrad", - "sfs-2010-110/kap97.3-belopp-1": "foraldrapenning-lagstaniva", - "sfs-2010-110/kap98.1-belopp-1": "tillfällig-foraldrapenning-tak", - "sfs-2010-110/kap99.1-belopp-1": "graviditetspenning-tak", - "sfs-2010-110/kap101.1-belopp-1": "bostadsbidrag-max-barnfamilj", - "sfs-2010-110/kap101.2-belopp-1": "bostadsbidrag-max-ungdom", - "sfs-2010-110/kap102.1-belopp-1": "bostadstillagg-pensionarer-max", - "sfs-2010-110/kap103.1-belopp-1": "underhallsstod-belopp", - "_brottsbalken_1962_700": "--- Brottsbalken (1962:700) ---", - "sfs-1962-700/kap25.1-belopp-1": "dagsbot-lagsta", - "sfs-1962-700/kap25.1-belopp-2": "dagsbot-hogsta", - "sfs-1962-700/kap25.2-belopp-1": "dagsbot-antal-lagsta", - "sfs-1962-700/kap25.2-belopp-2": "dagsbot-antal-hogsta", - "sfs-1962-700/kap25.3-belopp-1": "penningbot-lagsta", - "sfs-1962-700/kap25.3-belopp-2": "penningbot-hogsta", - "sfs-1962-700/kap27.1-belopp-1": "villkorlig-dom-dagsboter-max", - "sfs-1962-700/kap36.1-belopp-1": "forverkande-vardebelopp-lagsta", - "_aktiebolagslagen_2005_551": "--- Aktiebolagslagen (2005:551) ---", - "sfs-2005-551/kap1.3-belopp-1": "aktiekapital-privat-minimum", - "sfs-2005-551/kap1.4-belopp-1": "aktiekapital-publikt-minimum", - "sfs-2005-551/kap3.1-belopp-1": "aktie-kvotvarve-minimum", - "sfs-2005-551/kap12.1-belopp-1": "revisor-gransvardelomvangsgrans-nettoomsattning", - "sfs-2005-551/kap12.2-belopp-1": "revisor-gransvarvelomvangsgrans-balans", - "sfs-2005-551/kap12.3-belopp-1": "revisor-gransvardelomvangsgrans-anstallda", - "_rantelagen_1975_635": "--- Räntelagen (1975:635) ---", - "sfs-1975-635/3-procent-1": "drojsmalsranta-over-referensranta", - "sfs-1975-635/4-procent-1": "avtalad-ranta-tak", - "sfs-1975-635/5-procent-1": "referensranta-riksbanken", - "sfs-1975-635/6-procent-1": "avkastningsranta", - "_mervardeskattelagen_2023_200": "--- Mervärdesskattelagen (2023:200) ---", - "sfs-2023-200/kap9.1-procent-1": "moms-normalskattesats", - "sfs-2023-200/kap9.2-procent-1": "moms-reducerad-livsmedel", - "sfs-2023-200/kap9.3-procent-1": "moms-reducerad-kultur", - "sfs-2023-200/kap9.4-procent-1": "moms-reducerad-persontransport", - "sfs-2023-200/kap10.1-belopp-1": "moms-registreringsgrans", - "sfs-2023-200/kap10.2-belopp-1": "moms-arsredovisningsgrans", - "_lag_om_skatt_pa_energi_1994_1776": "--- Lag om skatt på energi (1994:1776) ---", - "sfs-1994-1776/kap2.1-belopp-1": "energiskatt-bensin", - "sfs-1994-1776/kap2.2-belopp-1": "energiskatt-diesel", - "sfs-1994-1776/kap2.3-belopp-1": "koldioxidskatt-bensin", - "sfs-1994-1776/kap2.4-belopp-1": "koldioxidskatt-diesel", - "sfs-1994-1776/kap11.1-belopp-1": "elskatt-hushall", - "sfs-1994-1776/kap11.2-belopp-1": "elskatt-norra-sverige", - "sfs-1994-1776/kap11.3-belopp-1": "elskatt-industri", - "_studiestodlagen_2022_856": "--- Studiestödslagen (2022:856) ---", - "sfs-2022-856/kap3.1-belopp-1": "studiebidrag-gymnasie-manad", - "sfs-2022-856/kap3.2-belopp-1": "studiebidrag-hogskola-manad", - "sfs-2022-856/kap4.1-belopp-1": "studielan-hogskola-max-manad", - "sfs-2022-856/kap4.2-belopp-1": "studielan-hogskola-max-ar", - "sfs-2022-856/kap4.3-belopp-1": "studielan-atersbetalning-fribeloppsgrans", - "sfs-2022-856/kap4.4-procent-1": "studielan-ranta", - "_lag_om_arbetsloshetsforsakring_1997_238": "--- Lag om arbetslöshetsförsäkring (1997:238) ---", - "sfs-1997-238/kap12.1-belopp-1": "a-kassa-tak-dag", - "sfs-1997-238/kap12.2-procent-1": "a-kassa-ersattningsgrad-forsta-200", - "sfs-1997-238/kap12.3-procent-1": "a-kassa-ersattningsgrad-efter-200", - "sfs-1997-238/kap12.4-belopp-1": "a-kassa-grundbelopp", - "sfs-1997-238/kap12.5-belopp-1": "a-kassa-inkomsttak", - "_tobakslagen_2018_2088": "--- Tobakslagen (2018:2088) ---", - "sfs-2018-2088/kap7.1-belopp-1": "tobak-sanktionsavgift-lagsta", - "sfs-2018-2088/kap7.1-belopp-2": "tobak-sanktionsavgift-hogsta", - "_alkohollagen_2010_1622": "--- Alkohollagen (2010:1622) ---", - "sfs-2010-1622/kap10.1-belopp-1": "alkohol-sanktionsavgift-lagsta", - "sfs-2010-1622/kap10.1-belopp-2": "alkohol-sanktionsavgift-hogsta", - "sfs-2010-1622/kap10.2-belopp-1": "alkohol-tillstandsavgift", - "_plan_och_bygglagen_2010_900": "--- Plan- och bygglagen (2010:900) ---", - "sfs-2010-900/kap11.51-belopp-1": "byggsanktionsavgift-lagsta", - "sfs-2010-900/kap11.51-belopp-2": "byggsanktionsavgift-hogsta", - "sfs-2010-900/kap11.52-belopp-1": "byggsanktionsavgift-per-kvm", - "sfs-2010-900/kap12.8-belopp-1": "bygglov-avgift-grund", - "_fordonsskattelagen_2006_227": "--- Fordonsskattelagen (2006:227) ---", - "sfs-2006-227/kap2.1-belopp-1": "fordonsskatt-personbil-grundbelopp", - "sfs-2006-227/kap2.2-belopp-1": "fordonsskatt-per-gram-co2", - "sfs-2006-227/kap2.3-belopp-1": "fordonsskatt-elbil", - "sfs-2006-227/kap2.4-belopp-1": "fordonsskatt-laddhybrid", - "sfs-2006-227/kap2.5-belopp-1": "fordonsskatt-dieseltillagg", - "sfs-2006-227/kap3.1-belopp-1": "fordonsskatt-lastbil-per-ton", - "sfs-2006-227/kap3.2-belopp-1": "fordonsskatt-buss-per-ton", - "sfs-2006-227/kap4.1-belopp-1": "fordonsskatt-motorcykel", - "sfs-2006-227/kap4.2-belopp-1": "fordonsskatt-slapvagn", - "_trafikforordningen_1998_1276": "--- Trafikförordningen (1998:1276) ---", - "sfs-1998-1276/kap14.1-belopp-1": "forseningsavgift-fordonsskatt", - "sfs-1998-1276/kap14.2-belopp-1": "parkering-kontrollavgift-max", - "_lag_om_trangselskatt_2004_629": "--- Lag om trängselskatt (2004:629) ---", - "sfs-2004-629/2-belopp-1": "trangselskatt-stockholm-max-dag", - "sfs-2004-629/2-belopp-2": "trangselskatt-goteborg-max-dag", - "sfs-2004-629/3-belopp-1": "trangselskatt-hog-belastning", - "sfs-2004-629/3-belopp-2": "trangselskatt-medel-belastning", - "sfs-2004-629/3-belopp-3": "trangselskatt-lag-belastning", - "_offentlighets_och_sekretesslag_2009_400": "--- Offentlighets- och sekretesslagen (2009:400) ---", - "sfs-2009-400/kap6.1-belopp-1": "avgift-kopiering-forsta-nio-sidor", - "sfs-2009-400/kap6.2-belopp-1": "avgift-kopiering-per-sida", - "sfs-2009-400/kap6.3-belopp-1": "avgift-kopiering-utskrift", - "_lag_om_viten_1985_206": "--- Lag om viten (1985:206) ---", - "sfs-1985-206/3-belopp-1": "vite-lagsta-belopp", - "sfs-1985-206/3-belopp-2": "vite-hogsta-belopp", - "sfs-1985-206/4-belopp-1": "lopande-vite-per-dag-max", - "_foretagarforeningens_revisionsforordning": "--- Revisorslagen (2001:883) ---", - "sfs-2001-883/kap32.1-belopp-1": "revisor-disciplinavgift-lagsta", - "sfs-2001-883/kap32.1-belopp-2": "revisor-disciplinavgift-hogsta", - "_lag_om_bank_och_finansieringsrorelse_2004_297": "--- Lag om bank- och finansieringsrörelse (2004:297) ---", - "sfs-2004-297/kap15.1-belopp-1": "bank-sanktionsavgift-lagsta", - "sfs-2004-297/kap15.1-belopp-2": "bank-sanktionsavgift-hogsta", - "sfs-2004-297/kap15.2-procent-1": "bank-sanktionsavgift-omsattning-procent", - "_hyreslagen_12_kap_jordabalken": "--- Jordabalken 12 kap (Hyreslagen) ---", - "sfs-1970-994/kap12.55h-belopp-1": "hyra-forhandlingsersattning", - "sfs-1970-994/kap12.55i-belopp-1": "hyra-privatuthyrning-schablon", - "_lag_om_skatt_pa_trafikforsakring_2007_460": "--- Lag om skatt på trafikförsäkring (2007:460) ---", - "sfs-2007-460/2-procent-1": "trafikforsakringsskatt-procent", - "sfs-2007-460/3-belopp-1": "trafikforsakringsskatt-minimum", - "_fastighetstaxeringslagen_1979_1152": "--- Fastighetstaxeringslagen (1979:1152) ---", - "sfs-1979-1152/kap7.1-belopp-1": "fastighetsavgift-tak-smahus", - "sfs-1979-1152/kap7.2-belopp-1": "fastighetsavgift-tak-bostadsratt", - "sfs-1979-1152/kap7.3-procent-1": "fastighetsavgift-procent-taxeringsvarde", - "_sparbankslagen_1987_619": "--- Sparbankslagen (1987:619) ---", - "sfs-1987-619/kap2.1-belopp-1": "sparbank-grundfond-minimum", - "_lag_om_investeringssparkonto_2011_1268": "--- Lag om investeringssparkonto (2011:1268) ---", - "sfs-2011-1268/kap8.1-procent-1": "isk-schablonintakt-procent", - "sfs-2011-1268/kap8.2-procent-1": "isk-statslanerantan-tillagg", - "_kupongskattelagen_1970_624": "--- Kupongskattelagen (1970:624) ---", - "sfs-1970-624/kap5.1-procent-1": "kupongskatt-procent", - "sfs-1970-624/kap5.2-procent-1": "kupongskatt-reducerad-procent", - "_lag_om_godkannande_av_gavaomottgare_2019_453": "--- Lag om godkännande av gåvomottgare (2019:453) ---", - "sfs-2019-453/kap7.1-belopp-1": "gava-skattereduktion-minimum", - "sfs-2019-453/kap7.2-belopp-1": "gava-skattereduktion-maximum", - "sfs-2019-453/kap7.3-procent-1": "gava-skattereduktion-procent", - "_arvs_och_gavokattelagen_upphavd": "--- Historiskt: Arvs- och gåvoskattelagen (upphävd 2004) ---", - "sfs-1941-416/kap1.1-belopp-1": "arvsskatt-fribeloppsgrans", - "sfs-1941-416/kap1.2-procent-1": "arvsskatt-procent-klass-1", - "sfs-1941-416/kap1.3-procent-1": "arvsskatt-procent-klass-2", - "sfs-1941-416/kap1.4-procent-1": "arvsskatt-procent-klass-3", - "_formansrattslagen_1970_979": "--- Förmånsrättslagen (1970:979) ---", - "sfs-1970-979/kap12.1-belopp-1": "lon-formansratt-max", - "sfs-1970-979/kap12.2-belopp-1": "pension-formansratt-max", - "_konkurslag_1987_672": "--- Konkurslagen (1987:672) ---", - "sfs-1987-672/kap14.1-belopp-1": "konkurs-grans-forlikningsforfarande", - "sfs-1987-672/kap14.2-belopp-1": "konkurs-grans-summarisk", - "_kameraovervakningslag_2018_1200": "--- Kameraövervakningslagen (2018:1200) ---", - "sfs-2018-1200/kap5.1-belopp-1": "kameraovervakning-sanktionsavgift-lagsta", - "sfs-2018-1200/kap5.1-belopp-2": "kameraovervakning-sanktionsavgift-hogsta", - "_personuppgiftslag_gdpr": "--- Kompletterande GDPR-lagstiftning ---", - "sfs-2018-218/kap6.1-belopp-1": "gdpr-sanktionsavgift-lagsta", - "sfs-2018-218/kap6.1-belopp-2": "gdpr-sanktionsavgift-hogsta", - "sfs-2018-218/kap6.2-procent-1": "gdpr-sanktionsavgift-omsattning-max", - "_lag_om_elektronisk_kommunikation_2022_482": "--- Lag om elektronisk kommunikation (2022:482) ---", - "sfs-2022-482/kap12.1-belopp-1": "ekomlagen-sanktionsavgift-lagsta", - "sfs-2022-482/kap12.1-belopp-2": "ekomlagen-sanktionsavgift-hogsta", - "sfs-2022-482/kap12.2-procent-1": "ekomlagen-sanktionsavgift-omsattning", - "sfs-2020-100/kap5.2-belopp-1": "tillstandsavgift", - "sfs-2024-123/kap5.2-belopp-1": "tillstandsavgift" -} \ No newline at end of file diff --git a/data/amount-references.yaml b/data/amount-references.yaml new file mode 100644 index 0000000..c74bb16 --- /dev/null +++ b/data/amount-references.yaml @@ -0,0 +1,231 @@ +# Reference table mapping positional ids to descriptive slugs +# Format: sfs-YYYY-NNN/section-type-position: descriptive-slug +# Workflow: Use extract_unmapped_ids() to find entries that need slugs + + +# --- Socialtjänstlagen (2025:400) --- +"sfs-2025-400/procent-1": inkomstandel-anstallning +"sfs-2025-400/belopp-1": sanktionsavgift-lagsta +"sfs-2025-400/belopp-2": sanktionsavgift-hogsta +"sfs-2025-400/belopp-3": sanktionsavgift-per-overträdelse +"sfs-2025-400/belopp-4": sanktionsavgift-allvarlig-lagsta +"sfs-2025-400/belopp-5": sanktionsavgift-allvarlig-hogsta +"sfs-2025-400/belopp-6": sanktionsavgift-synnerligen-allvarlig-lagsta +"sfs-2025-400/belopp-7": sanktionsavgift-synnerligen-allvarlig-hogsta + +# --- Inkomstskattelagen (1999:1229) --- +"sfs-1999-1229/kap2.1-belopp-1": prisbasbelopp +"sfs-1999-1229/kap2.2-belopp-1": forhojt-prisbasbelopp +"sfs-1999-1229/kap2.3-belopp-1": inkomstbasbelopp +"sfs-1999-1229/kap10.2-belopp-1": grundavdrag-lagsta +"sfs-1999-1229/kap10.2-belopp-2": grundavdrag-hogsta +"sfs-1999-1229/kap11.1-procent-1": jobbskatteavdrag-procent +"sfs-1999-1229/kap11.1-belopp-1": jobbskatteavdrag-tak +"sfs-1999-1229/kap12.24-belopp-1": tjansteresor-avdrag +"sfs-1999-1229/kap12.27-belopp-1": dubbel-bosattning-avdrag +"sfs-1999-1229/kap57.1-belopp-1": statlig-skatt-skiktgrans +"sfs-1999-1229/kap57.1-procent-1": statlig-skatt-procentsats +"sfs-1999-1229/kap65.5-belopp-1": skattereduktion-forvarvsarbete +"sfs-1999-1229/kap67.5-belopp-1": rot-avdrag-max +"sfs-1999-1229/kap67.6-belopp-1": rut-avdrag-max +"sfs-1999-1229/kap67.7-procent-1": rot-avdrag-procent +"sfs-1999-1229/kap67.8-procent-1": rut-avdrag-procent + +# --- Socialförsäkringsbalken (2010:110) --- +"sfs-2010-110/kap15.2-belopp-1": sjukpenning-tak +"sfs-2010-110/kap15.3-procent-1": sjukpenning-ersattningsgrad +"sfs-2010-110/kap16.1-belopp-1": sjukersattning-hel +"sfs-2010-110/kap16.2-belopp-1": sjukersattning-tre-fjardedels +"sfs-2010-110/kap16.3-belopp-1": sjukersattning-halv +"sfs-2010-110/kap16.4-belopp-1": sjukersattning-en-fjardedels +"sfs-2010-110/kap27.1-procent-1": karensavdrag-procent +"sfs-2010-110/kap28.2-belopp-1": rehabiliteringspenning-tak +"sfs-2010-110/kap35.1-belopp-1": arbetsskadelivranta-tak +"sfs-2010-110/kap58.1-belopp-1": alderspension-garantiniva +"sfs-2010-110/kap59.2-belopp-1": premiepension-avgift +"sfs-2010-110/kap60.1-procent-1": pensionsavgift-procent +"sfs-2010-110/kap96.1-belopp-1": barnbidrag-belopp +"sfs-2010-110/kap96.2-belopp-1": flerbarnstillagg-tva-barn +"sfs-2010-110/kap96.3-belopp-1": flerbarnstillagg-tre-barn +"sfs-2010-110/kap96.4-belopp-1": flerbarnstillagg-fyra-barn +"sfs-2010-110/kap96.5-belopp-1": flerbarnstillagg-fem-barn +"sfs-2010-110/kap97.1-belopp-1": foraldrapenning-tak +"sfs-2010-110/kap97.2-procent-1": foraldrapenning-ersattningsgrad +"sfs-2010-110/kap97.3-belopp-1": foraldrapenning-lagstaniva +"sfs-2010-110/kap98.1-belopp-1": tillfällig-foraldrapenning-tak +"sfs-2010-110/kap99.1-belopp-1": graviditetspenning-tak +"sfs-2010-110/kap101.1-belopp-1": bostadsbidrag-max-barnfamilj +"sfs-2010-110/kap101.2-belopp-1": bostadsbidrag-max-ungdom +"sfs-2010-110/kap102.1-belopp-1": bostadstillagg-pensionarer-max +"sfs-2010-110/kap103.1-belopp-1": underhallsstod-belopp + +# --- Brottsbalken (1962:700) --- +"sfs-1962-700/kap25.1-belopp-1": dagsbot-lagsta +"sfs-1962-700/kap25.1-belopp-2": dagsbot-hogsta +"sfs-1962-700/kap25.2-belopp-1": dagsbot-antal-lagsta +"sfs-1962-700/kap25.2-belopp-2": dagsbot-antal-hogsta +"sfs-1962-700/kap25.3-belopp-1": penningbot-lagsta +"sfs-1962-700/kap25.3-belopp-2": penningbot-hogsta +"sfs-1962-700/kap27.1-belopp-1": villkorlig-dom-dagsboter-max +"sfs-1962-700/kap36.1-belopp-1": forverkande-vardebelopp-lagsta + +# --- Aktiebolagslagen (2005:551) --- +"sfs-2005-551/kap1.3-belopp-1": aktiekapital-privat-minimum +"sfs-2005-551/kap1.4-belopp-1": aktiekapital-publikt-minimum +"sfs-2005-551/kap3.1-belopp-1": aktie-kvotvarve-minimum +"sfs-2005-551/kap12.1-belopp-1": revisor-gransvardelomvangsgrans-nettoomsattning +"sfs-2005-551/kap12.2-belopp-1": revisor-gransvarvelomvangsgrans-balans +"sfs-2005-551/kap12.3-belopp-1": revisor-gransvardelomvangsgrans-anstallda + +# --- Räntelagen (1975:635) --- +"sfs-1975-635/3-procent-1": drojsmalsranta-over-referensranta +"sfs-1975-635/4-procent-1": avtalad-ranta-tak +"sfs-1975-635/5-procent-1": referensranta-riksbanken +"sfs-1975-635/6-procent-1": avkastningsranta + +# --- Mervärdesskattelagen (2023:200) --- +"sfs-2023-200/kap9.1-procent-1": moms-normalskattesats +"sfs-2023-200/kap9.2-procent-1": moms-reducerad-livsmedel +"sfs-2023-200/kap9.3-procent-1": moms-reducerad-kultur +"sfs-2023-200/kap9.4-procent-1": moms-reducerad-persontransport +"sfs-2023-200/kap10.1-belopp-1": moms-registreringsgrans +"sfs-2023-200/kap10.2-belopp-1": moms-arsredovisningsgrans + +# --- Lag om skatt på energi (1994:1776) --- +"sfs-1994-1776/kap2.1-belopp-1": energiskatt-bensin +"sfs-1994-1776/kap2.2-belopp-1": energiskatt-diesel +"sfs-1994-1776/kap2.3-belopp-1": koldioxidskatt-bensin +"sfs-1994-1776/kap2.4-belopp-1": koldioxidskatt-diesel +"sfs-1994-1776/kap11.1-belopp-1": elskatt-hushall +"sfs-1994-1776/kap11.2-belopp-1": elskatt-norra-sverige +"sfs-1994-1776/kap11.3-belopp-1": elskatt-industri + +# --- Studiestödslagen (2022:856) --- +"sfs-2022-856/kap3.1-belopp-1": studiebidrag-gymnasie-manad +"sfs-2022-856/kap3.2-belopp-1": studiebidrag-hogskola-manad +"sfs-2022-856/kap4.1-belopp-1": studielan-hogskola-max-manad +"sfs-2022-856/kap4.2-belopp-1": studielan-hogskola-max-ar +"sfs-2022-856/kap4.3-belopp-1": studielan-atersbetalning-fribeloppsgrans +"sfs-2022-856/kap4.4-procent-1": studielan-ranta + +# --- Lag om arbetslöshetsförsäkring (1997:238) --- +"sfs-1997-238/kap12.1-belopp-1": a-kassa-tak-dag +"sfs-1997-238/kap12.2-procent-1": a-kassa-ersattningsgrad-forsta-200 +"sfs-1997-238/kap12.3-procent-1": a-kassa-ersattningsgrad-efter-200 +"sfs-1997-238/kap12.4-belopp-1": a-kassa-grundbelopp +"sfs-1997-238/kap12.5-belopp-1": a-kassa-inkomsttak + +# --- Tobakslagen (2018:2088) --- +"sfs-2018-2088/kap7.1-belopp-1": tobak-sanktionsavgift-lagsta +"sfs-2018-2088/kap7.1-belopp-2": tobak-sanktionsavgift-hogsta + +# --- Alkohollagen (2010:1622) --- +"sfs-2010-1622/kap10.1-belopp-1": alkohol-sanktionsavgift-lagsta +"sfs-2010-1622/kap10.1-belopp-2": alkohol-sanktionsavgift-hogsta +"sfs-2010-1622/kap10.2-belopp-1": alkohol-tillstandsavgift + +# --- Plan- och bygglagen (2010:900) --- +"sfs-2010-900/kap11.51-belopp-1": byggsanktionsavgift-lagsta +"sfs-2010-900/kap11.51-belopp-2": byggsanktionsavgift-hogsta +"sfs-2010-900/kap11.52-belopp-1": byggsanktionsavgift-per-kvm +"sfs-2010-900/kap12.8-belopp-1": bygglov-avgift-grund + +# --- Fordonsskattelagen (2006:227) --- +"sfs-2006-227/kap2.1-belopp-1": fordonsskatt-personbil-grundbelopp +"sfs-2006-227/kap2.2-belopp-1": fordonsskatt-per-gram-co2 +"sfs-2006-227/kap2.3-belopp-1": fordonsskatt-elbil +"sfs-2006-227/kap2.4-belopp-1": fordonsskatt-laddhybrid +"sfs-2006-227/kap2.5-belopp-1": fordonsskatt-dieseltillagg +"sfs-2006-227/kap3.1-belopp-1": fordonsskatt-lastbil-per-ton +"sfs-2006-227/kap3.2-belopp-1": fordonsskatt-buss-per-ton +"sfs-2006-227/kap4.1-belopp-1": fordonsskatt-motorcykel +"sfs-2006-227/kap4.2-belopp-1": fordonsskatt-slapvagn + +# --- Trafikförordningen (1998:1276) --- +"sfs-1998-1276/kap14.1-belopp-1": forseningsavgift-fordonsskatt +"sfs-1998-1276/kap14.2-belopp-1": parkering-kontrollavgift-max + +# --- Lag om trängselskatt (2004:629) --- +"sfs-2004-629/2-belopp-1": trangselskatt-stockholm-max-dag +"sfs-2004-629/2-belopp-2": trangselskatt-goteborg-max-dag +"sfs-2004-629/3-belopp-1": trangselskatt-hog-belastning +"sfs-2004-629/3-belopp-2": trangselskatt-medel-belastning +"sfs-2004-629/3-belopp-3": trangselskatt-lag-belastning + +# --- Offentlighets- och sekretesslagen (2009:400) --- +"sfs-2009-400/kap6.1-belopp-1": avgift-kopiering-forsta-nio-sidor +"sfs-2009-400/kap6.2-belopp-1": avgift-kopiering-per-sida +"sfs-2009-400/kap6.3-belopp-1": avgift-kopiering-utskrift + +# --- Lag om viten (1985:206) --- +"sfs-1985-206/3-belopp-1": vite-lagsta-belopp +"sfs-1985-206/3-belopp-2": vite-hogsta-belopp +"sfs-1985-206/4-belopp-1": lopande-vite-per-dag-max + +# --- Revisorslagen (2001:883) --- +"sfs-2001-883/kap32.1-belopp-1": revisor-disciplinavgift-lagsta +"sfs-2001-883/kap32.1-belopp-2": revisor-disciplinavgift-hogsta + +# --- Lag om bank- och finansieringsrörelse (2004:297) --- +"sfs-2004-297/kap15.1-belopp-1": bank-sanktionsavgift-lagsta +"sfs-2004-297/kap15.1-belopp-2": bank-sanktionsavgift-hogsta +"sfs-2004-297/kap15.2-procent-1": bank-sanktionsavgift-omsattning-procent + +# --- Jordabalken 12 kap (Hyreslagen) --- +"sfs-1970-994/kap12.55h-belopp-1": hyra-forhandlingsersattning +"sfs-1970-994/kap12.55i-belopp-1": hyra-privatuthyrning-schablon + +# --- Lag om skatt på trafikförsäkring (2007:460) --- +"sfs-2007-460/2-procent-1": trafikforsakringsskatt-procent +"sfs-2007-460/3-belopp-1": trafikforsakringsskatt-minimum + +# --- Fastighetstaxeringslagen (1979:1152) --- +"sfs-1979-1152/kap7.1-belopp-1": fastighetsavgift-tak-smahus +"sfs-1979-1152/kap7.2-belopp-1": fastighetsavgift-tak-bostadsratt +"sfs-1979-1152/kap7.3-procent-1": fastighetsavgift-procent-taxeringsvarde + +# --- Sparbankslagen (1987:619) --- +"sfs-1987-619/kap2.1-belopp-1": sparbank-grundfond-minimum + +# --- Lag om investeringssparkonto (2011:1268) --- +"sfs-2011-1268/kap8.1-procent-1": isk-schablonintakt-procent +"sfs-2011-1268/kap8.2-procent-1": isk-statslanerantan-tillagg + +# --- Kupongskattelagen (1970:624) --- +"sfs-1970-624/kap5.1-procent-1": kupongskatt-procent +"sfs-1970-624/kap5.2-procent-1": kupongskatt-reducerad-procent + +# --- Lag om godkännande av gåvomottgare (2019:453) --- +"sfs-2019-453/kap7.1-belopp-1": gava-skattereduktion-minimum +"sfs-2019-453/kap7.2-belopp-1": gava-skattereduktion-maximum +"sfs-2019-453/kap7.3-procent-1": gava-skattereduktion-procent + +# --- Historiskt: Arvs- och gåvoskattelagen (upphävd 2004) --- +"sfs-1941-416/kap1.1-belopp-1": arvsskatt-fribeloppsgrans +"sfs-1941-416/kap1.2-procent-1": arvsskatt-procent-klass-1 +"sfs-1941-416/kap1.3-procent-1": arvsskatt-procent-klass-2 +"sfs-1941-416/kap1.4-procent-1": arvsskatt-procent-klass-3 + +# --- Förmånsrättslagen (1970:979) --- +"sfs-1970-979/kap12.1-belopp-1": lon-formansratt-max +"sfs-1970-979/kap12.2-belopp-1": pension-formansratt-max + +# --- Konkurslagen (1987:672) --- +"sfs-1987-672/kap14.1-belopp-1": konkurs-grans-forlikningsforfarande +"sfs-1987-672/kap14.2-belopp-1": konkurs-grans-summarisk + +# --- Kameraövervakningslagen (2018:1200) --- +"sfs-2018-1200/kap5.1-belopp-1": kameraovervakning-sanktionsavgift-lagsta +"sfs-2018-1200/kap5.1-belopp-2": kameraovervakning-sanktionsavgift-hogsta + +# --- Kompletterande GDPR-lagstiftning --- +"sfs-2018-218/kap6.1-belopp-1": gdpr-sanktionsavgift-lagsta +"sfs-2018-218/kap6.1-belopp-2": gdpr-sanktionsavgift-hogsta +"sfs-2018-218/kap6.2-procent-1": gdpr-sanktionsavgift-omsattning-max + +# --- Lag om elektronisk kommunikation (2022:482) --- +"sfs-2022-482/kap12.1-belopp-1": ekomlagen-sanktionsavgift-lagsta +"sfs-2022-482/kap12.1-belopp-2": ekomlagen-sanktionsavgift-hogsta +"sfs-2022-482/kap12.2-procent-1": ekomlagen-sanktionsavgift-omsattning +"sfs-2020-100/kap5.2-belopp-1": tillstandsavgift +"sfs-2024-123/kap5.2-belopp-1": tillstandsavgift diff --git a/formatters/tag_swedish_amounts.py b/formatters/tag_swedish_amounts.py index fa88f14..e5f5520 100644 --- a/formatters/tag_swedish_amounts.py +++ b/formatters/tag_swedish_amounts.py @@ -10,15 +10,15 @@ - value: normalized numeric value - id: a reference id based on section + position, or a custom slug from reference table -The reference table (data/amount-references.json) maps positional ids to +The reference table (data/amount-references.yaml) maps positional ids to descriptive slugs like "riksbankens-referensranta". """ import re -import json from pathlib import Path from typing import Optional, Dict import unicodedata +import yaml # Cache for reference table @@ -84,7 +84,7 @@ def normalize_number(num_str: str) -> str: def load_reference_table() -> Dict[str, str]: """ - Load the amount reference table from data/amount-references.json. + Load the amount reference table from data/amount-references.yaml. The reference table maps positional ids (e.g., "kap5.2-belopp-1") to descriptive slugs (e.g., "riksbankens-referensranta"). @@ -100,11 +100,11 @@ def load_reference_table() -> Dict[str, str]: try: current_file = Path(__file__) project_root = current_file.parent.parent - ref_file = project_root / "data" / "amount-references.json" + ref_file = project_root / "data" / "amount-references.yaml" if ref_file.exists(): with open(ref_file, 'r', encoding='utf-8') as f: - _reference_table = json.load(f) + _reference_table = yaml.safe_load(f) or {} else: _reference_table = {} From 756a26765540046938a961b091ae1677ef208fdb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Jan 2026 07:44:36 +0000 Subject: [PATCH 9/9] Add value and context comments to amount-references.yaml Each entry now includes an inline comment with: - The actual value (e.g., "57 300 kr", "80%") - A text excerpt showing context from the law Example: "sfs-1999-1229/kap2.1-belopp-1": prisbasbelopp # 57 300 kr - "prisbasbeloppet enligt 2 kap." This makes it easier to understand and verify each mapping. --- data/amount-references.yaml | 327 ++++++++++++++++++------------------ 1 file changed, 164 insertions(+), 163 deletions(-) diff --git a/data/amount-references.yaml b/data/amount-references.yaml index c74bb16..397a28b 100644 --- a/data/amount-references.yaml +++ b/data/amount-references.yaml @@ -1,231 +1,232 @@ # Reference table mapping positional ids to descriptive slugs -# Format: sfs-YYYY-NNN/section-type-position: descriptive-slug +# Format: "positional-id": slug # värde - "textutdrag" # Workflow: Use extract_unmapped_ids() to find entries that need slugs - # --- Socialtjänstlagen (2025:400) --- -"sfs-2025-400/procent-1": inkomstandel-anstallning -"sfs-2025-400/belopp-1": sanktionsavgift-lagsta -"sfs-2025-400/belopp-2": sanktionsavgift-hogsta -"sfs-2025-400/belopp-3": sanktionsavgift-per-overträdelse -"sfs-2025-400/belopp-4": sanktionsavgift-allvarlig-lagsta -"sfs-2025-400/belopp-5": sanktionsavgift-allvarlig-hogsta -"sfs-2025-400/belopp-6": sanktionsavgift-synnerligen-allvarlig-lagsta -"sfs-2025-400/belopp-7": sanktionsavgift-synnerligen-allvarlig-hogsta +"sfs-2025-400/procent-1": inkomstandel-anstallning # 80% - "motsvarande 80 procent av inkomsten" +"sfs-2025-400/belopp-1": sanktionsavgift-lagsta # 5 000 kr - "lägst 5 000 kronor" +"sfs-2025-400/belopp-2": sanktionsavgift-hogsta # 100 000 kr - "högst 100 000 kronor" +"sfs-2025-400/belopp-3": sanktionsavgift-per-overträdelse # 10 000 kr - "10 000 kronor per överträdelse" +"sfs-2025-400/belopp-4": sanktionsavgift-allvarlig-lagsta # 50 000 kr - "vid allvarlig överträdelse lägst 50 000" +"sfs-2025-400/belopp-5": sanktionsavgift-allvarlig-hogsta # 500 000 kr - "högst 500 000 kronor" +"sfs-2025-400/belopp-6": sanktionsavgift-synnerligen-allvarlig-lagsta # 100 000 kr - "synnerligen allvarlig, lägst 100 000" +"sfs-2025-400/belopp-7": sanktionsavgift-synnerligen-allvarlig-hogsta # 1 000 000 kr - "högst 1 000 000 kronor" # --- Inkomstskattelagen (1999:1229) --- -"sfs-1999-1229/kap2.1-belopp-1": prisbasbelopp -"sfs-1999-1229/kap2.2-belopp-1": forhojt-prisbasbelopp -"sfs-1999-1229/kap2.3-belopp-1": inkomstbasbelopp -"sfs-1999-1229/kap10.2-belopp-1": grundavdrag-lagsta -"sfs-1999-1229/kap10.2-belopp-2": grundavdrag-hogsta -"sfs-1999-1229/kap11.1-procent-1": jobbskatteavdrag-procent -"sfs-1999-1229/kap11.1-belopp-1": jobbskatteavdrag-tak -"sfs-1999-1229/kap12.24-belopp-1": tjansteresor-avdrag -"sfs-1999-1229/kap12.27-belopp-1": dubbel-bosattning-avdrag -"sfs-1999-1229/kap57.1-belopp-1": statlig-skatt-skiktgrans -"sfs-1999-1229/kap57.1-procent-1": statlig-skatt-procentsats -"sfs-1999-1229/kap65.5-belopp-1": skattereduktion-forvarvsarbete -"sfs-1999-1229/kap67.5-belopp-1": rot-avdrag-max -"sfs-1999-1229/kap67.6-belopp-1": rut-avdrag-max -"sfs-1999-1229/kap67.7-procent-1": rot-avdrag-procent -"sfs-1999-1229/kap67.8-procent-1": rut-avdrag-procent +"sfs-1999-1229/kap2.1-belopp-1": prisbasbelopp # 57 300 kr - "prisbasbeloppet enligt 2 kap. socialförsäkringsbalken" +"sfs-1999-1229/kap2.2-belopp-1": forhojt-prisbasbelopp # 58 500 kr - "det förhöjda prisbasbeloppet" +"sfs-1999-1229/kap2.3-belopp-1": inkomstbasbelopp # 76 200 kr - "inkomstbasbeloppet" +"sfs-1999-1229/kap10.2-belopp-1": grundavdrag-lagsta # 15 400 kr - "grundavdraget ska vara lägst 15 400" +"sfs-1999-1229/kap10.2-belopp-2": grundavdrag-hogsta # 40 500 kr - "högst 40 500 kronor" +"sfs-1999-1229/kap11.1-procent-1": jobbskatteavdrag-procent # 11,5% - "med 11,5 procent av underlaget" +"sfs-1999-1229/kap11.1-belopp-1": jobbskatteavdrag-tak # 37 000 kr - "dock högst 37 000 kronor" +"sfs-1999-1229/kap12.24-belopp-1": tjansteresor-avdrag # 13 000 kr - "avdrag för resor i tjänsten överstigande 13 000" +"sfs-1999-1229/kap12.27-belopp-1": dubbel-bosattning-avdrag # 8 000 kr - "avdrag med högst 8 000 kronor per månad" +"sfs-1999-1229/kap57.1-belopp-1": statlig-skatt-skiktgrans # 613 900 kr - "beskattningsbar förvärvsinkomst överstiger 613 900" +"sfs-1999-1229/kap57.1-procent-1": statlig-skatt-procentsats # 20% - "statlig inkomstskatt med 20 procent" +"sfs-1999-1229/kap65.5-belopp-1": skattereduktion-forvarvsarbete # 1 700 kr - "skattereduktion med 1 700 kronor" +"sfs-1999-1229/kap67.5-belopp-1": rot-avdrag-max # 50 000 kr - "högst 50 000 kronor per år för ROT" +"sfs-1999-1229/kap67.6-belopp-1": rut-avdrag-max # 75 000 kr - "högst 75 000 kronor per år för RUT" +"sfs-1999-1229/kap67.7-procent-1": rot-avdrag-procent # 30% - "avdrag med 30 procent av arbetskostnaden" +"sfs-1999-1229/kap67.8-procent-1": rut-avdrag-procent # 50% - "avdrag med 50 procent av arbetskostnaden" # --- Socialförsäkringsbalken (2010:110) --- -"sfs-2010-110/kap15.2-belopp-1": sjukpenning-tak -"sfs-2010-110/kap15.3-procent-1": sjukpenning-ersattningsgrad -"sfs-2010-110/kap16.1-belopp-1": sjukersattning-hel -"sfs-2010-110/kap16.2-belopp-1": sjukersattning-tre-fjardedels -"sfs-2010-110/kap16.3-belopp-1": sjukersattning-halv -"sfs-2010-110/kap16.4-belopp-1": sjukersattning-en-fjardedels -"sfs-2010-110/kap27.1-procent-1": karensavdrag-procent -"sfs-2010-110/kap28.2-belopp-1": rehabiliteringspenning-tak -"sfs-2010-110/kap35.1-belopp-1": arbetsskadelivranta-tak -"sfs-2010-110/kap58.1-belopp-1": alderspension-garantiniva -"sfs-2010-110/kap59.2-belopp-1": premiepension-avgift -"sfs-2010-110/kap60.1-procent-1": pensionsavgift-procent -"sfs-2010-110/kap96.1-belopp-1": barnbidrag-belopp -"sfs-2010-110/kap96.2-belopp-1": flerbarnstillagg-tva-barn -"sfs-2010-110/kap96.3-belopp-1": flerbarnstillagg-tre-barn -"sfs-2010-110/kap96.4-belopp-1": flerbarnstillagg-fyra-barn -"sfs-2010-110/kap96.5-belopp-1": flerbarnstillagg-fem-barn -"sfs-2010-110/kap97.1-belopp-1": foraldrapenning-tak -"sfs-2010-110/kap97.2-procent-1": foraldrapenning-ersattningsgrad -"sfs-2010-110/kap97.3-belopp-1": foraldrapenning-lagstaniva -"sfs-2010-110/kap98.1-belopp-1": tillfällig-foraldrapenning-tak -"sfs-2010-110/kap99.1-belopp-1": graviditetspenning-tak -"sfs-2010-110/kap101.1-belopp-1": bostadsbidrag-max-barnfamilj -"sfs-2010-110/kap101.2-belopp-1": bostadsbidrag-max-ungdom -"sfs-2010-110/kap102.1-belopp-1": bostadstillagg-pensionarer-max -"sfs-2010-110/kap103.1-belopp-1": underhallsstod-belopp +"sfs-2010-110/kap15.2-belopp-1": sjukpenning-tak # 1 116 kr - "sjukpenning per dag högst 1 116 kronor" +"sfs-2010-110/kap15.3-procent-1": sjukpenning-ersattningsgrad # 80% - "80 procent av den sjukpenninggrundande inkomsten" +"sfs-2010-110/kap16.1-belopp-1": sjukersattning-hel # 11 830 kr - "hel sjukersättning 11 830 kronor per månad" +"sfs-2010-110/kap16.2-belopp-1": sjukersattning-tre-fjardedels # 8 873 kr - "tre fjärdedels sjukersättning 8 873 kronor" +"sfs-2010-110/kap16.3-belopp-1": sjukersattning-halv # 5 915 kr - "halv sjukersättning 5 915 kronor" +"sfs-2010-110/kap16.4-belopp-1": sjukersattning-en-fjardedels # 2 958 kr - "en fjärdedels sjukersättning 2 958 kronor" +"sfs-2010-110/kap27.1-procent-1": karensavdrag-procent # 20% - "karensavdrag med 20 procent av genomsnittlig veckoersättning" +"sfs-2010-110/kap28.2-belopp-1": rehabiliteringspenning-tak # 1 116 kr - "rehabiliteringspenning per dag högst 1 116" +"sfs-2010-110/kap35.1-belopp-1": arbetsskadelivranta-tak # 7,5 pbb - "högst 7,5 prisbasbelopp per år" +"sfs-2010-110/kap58.1-belopp-1": alderspension-garantiniva # 10 631 kr - "garantipension 10 631 kronor per månad" +"sfs-2010-110/kap59.2-belopp-1": premiepension-avgift # 2,5% - "2,5 procent till premiepensionen" +"sfs-2010-110/kap60.1-procent-1": pensionsavgift-procent # 18,5% - "pensionsavgift med 18,5 procent" +"sfs-2010-110/kap96.1-belopp-1": barnbidrag-belopp # 1 250 kr - "barnbidrag med 1 250 kronor per månad" +"sfs-2010-110/kap96.2-belopp-1": flerbarnstillagg-tva-barn # 150 kr - "flerbarnstillägg för två barn 150 kronor" +"sfs-2010-110/kap96.3-belopp-1": flerbarnstillagg-tre-barn # 730 kr - "för tre barn 730 kronor" +"sfs-2010-110/kap96.4-belopp-1": flerbarnstillagg-fyra-barn # 1 740 kr - "för fyra barn 1 740 kronor" +"sfs-2010-110/kap96.5-belopp-1": flerbarnstillagg-fem-barn # 2 990 kr - "för fem barn 2 990 kronor" +"sfs-2010-110/kap97.1-belopp-1": foraldrapenning-tak # 1 116 kr - "föräldrapenning per dag högst 1 116 kronor" +"sfs-2010-110/kap97.2-procent-1": foraldrapenning-ersattningsgrad # 80% - "80 procent av sjukpenninggrundande inkomst" +"sfs-2010-110/kap97.3-belopp-1": foraldrapenning-lagstaniva # 250 kr - "lägst 250 kronor per dag" +"sfs-2010-110/kap98.1-belopp-1": tillfällig-foraldrapenning-tak # 1 116 kr - "tillfällig föräldrapenning högst 1 116" +"sfs-2010-110/kap99.1-belopp-1": graviditetspenning-tak # 1 116 kr - "graviditetspenning högst 1 116 kronor" +"sfs-2010-110/kap101.1-belopp-1": bostadsbidrag-max-barnfamilj # 5 000 kr - "bostadsbidrag högst 5 000 kronor per månad" +"sfs-2010-110/kap101.2-belopp-1": bostadsbidrag-max-ungdom # 1 300 kr - "för ungdomar högst 1 300 kronor" +"sfs-2010-110/kap102.1-belopp-1": bostadstillagg-pensionarer-max # 7 500 kr - "bostadstillägg högst 7 500 kronor" +"sfs-2010-110/kap103.1-belopp-1": underhallsstod-belopp # 1 673 kr - "underhållsstöd med 1 673 kronor per månad" # --- Brottsbalken (1962:700) --- -"sfs-1962-700/kap25.1-belopp-1": dagsbot-lagsta -"sfs-1962-700/kap25.1-belopp-2": dagsbot-hogsta -"sfs-1962-700/kap25.2-belopp-1": dagsbot-antal-lagsta -"sfs-1962-700/kap25.2-belopp-2": dagsbot-antal-hogsta -"sfs-1962-700/kap25.3-belopp-1": penningbot-lagsta -"sfs-1962-700/kap25.3-belopp-2": penningbot-hogsta -"sfs-1962-700/kap27.1-belopp-1": villkorlig-dom-dagsboter-max -"sfs-1962-700/kap36.1-belopp-1": forverkande-vardebelopp-lagsta +"sfs-1962-700/kap25.1-belopp-1": dagsbot-lagsta # 50 kr - "dagsbot lägst 50 kronor" +"sfs-1962-700/kap25.1-belopp-2": dagsbot-hogsta # 1 000 kr - "högst 1 000 kronor" +"sfs-1962-700/kap25.2-belopp-1": dagsbot-antal-lagsta # 30 st - "lägst 30 dagsböter" +"sfs-1962-700/kap25.2-belopp-2": dagsbot-antal-hogsta # 150 st - "högst 150 dagsböter" +"sfs-1962-700/kap25.3-belopp-1": penningbot-lagsta # 200 kr - "penningbot lägst 200 kronor" +"sfs-1962-700/kap25.3-belopp-2": penningbot-hogsta # 4 000 kr - "högst 4 000 kronor" +"sfs-1962-700/kap27.1-belopp-1": villkorlig-dom-dagsboter-max # 200 st - "villkorlig dom med högst 200 dagsböter" +"sfs-1962-700/kap36.1-belopp-1": forverkande-vardebelopp-lagsta # 1 000 kr - "förverkande av värde över 1 000 kronor" # --- Aktiebolagslagen (2005:551) --- -"sfs-2005-551/kap1.3-belopp-1": aktiekapital-privat-minimum -"sfs-2005-551/kap1.4-belopp-1": aktiekapital-publikt-minimum -"sfs-2005-551/kap3.1-belopp-1": aktie-kvotvarve-minimum -"sfs-2005-551/kap12.1-belopp-1": revisor-gransvardelomvangsgrans-nettoomsattning -"sfs-2005-551/kap12.2-belopp-1": revisor-gransvarvelomvangsgrans-balans -"sfs-2005-551/kap12.3-belopp-1": revisor-gransvardelomvangsgrans-anstallda +"sfs-2005-551/kap1.3-belopp-1": aktiekapital-privat-minimum # 25 000 kr - "aktiekapitalet ska vara minst 25 000 kronor" +"sfs-2005-551/kap1.4-belopp-1": aktiekapital-publikt-minimum # 500 000 kr - "i publikt bolag minst 500 000 kronor" +"sfs-2005-551/kap3.1-belopp-1": aktie-kvotvarde-minimum # 0,01 kr - "aktiens kvotvärde minst en öre" +"sfs-2005-551/kap12.1-belopp-1": revisor-omsattningsgrans # 3 mkr - "nettoomsättning överstiger 3 miljoner" +"sfs-2005-551/kap12.2-belopp-1": revisor-balansgrans # 1,5 mkr - "balansomslutning överstiger 1,5 miljoner" +"sfs-2005-551/kap12.3-belopp-1": revisor-anstallda-grans # 3 st - "fler än 3 anställda i medeltal" # --- Räntelagen (1975:635) --- -"sfs-1975-635/3-procent-1": drojsmalsranta-over-referensranta -"sfs-1975-635/4-procent-1": avtalad-ranta-tak -"sfs-1975-635/5-procent-1": referensranta-riksbanken -"sfs-1975-635/6-procent-1": avkastningsranta +"sfs-1975-635/3-procent-1": drojsmalsranta-over-referensranta # 8% - "åtta procentenheter över referensräntan" +"sfs-1975-635/4-procent-1": avtalad-ranta-tak # 24% - "avtalad ränta får inte överstiga 24 procent" +"sfs-1975-635/5-procent-1": referensranta-riksbanken # 4,0% - "referensräntan fastställs av Riksbanken" +"sfs-1975-635/6-procent-1": avkastningsranta # 2% - "avkastningsränta med 2 procent" # --- Mervärdesskattelagen (2023:200) --- -"sfs-2023-200/kap9.1-procent-1": moms-normalskattesats -"sfs-2023-200/kap9.2-procent-1": moms-reducerad-livsmedel -"sfs-2023-200/kap9.3-procent-1": moms-reducerad-kultur -"sfs-2023-200/kap9.4-procent-1": moms-reducerad-persontransport -"sfs-2023-200/kap10.1-belopp-1": moms-registreringsgrans -"sfs-2023-200/kap10.2-belopp-1": moms-arsredovisningsgrans +"sfs-2023-200/kap9.1-procent-1": moms-normalskattesats # 25% - "skattesatsen är 25 procent" +"sfs-2023-200/kap9.2-procent-1": moms-reducerad-livsmedel # 12% - "12 procent för livsmedel" +"sfs-2023-200/kap9.3-procent-1": moms-reducerad-kultur # 6% - "6 procent för böcker och tidningar" +"sfs-2023-200/kap9.4-procent-1": moms-reducerad-persontransport # 6% - "6 procent för persontransporter" +"sfs-2023-200/kap10.1-belopp-1": moms-registreringsgrans # 80 000 kr - "registreringsskyldighet vid omsättning över 80 000" +"sfs-2023-200/kap10.2-belopp-1": moms-arsredovisningsgrans # 40 mkr - "årsredovisning krävs vid omsättning över 40 miljoner" # --- Lag om skatt på energi (1994:1776) --- -"sfs-1994-1776/kap2.1-belopp-1": energiskatt-bensin -"sfs-1994-1776/kap2.2-belopp-1": energiskatt-diesel -"sfs-1994-1776/kap2.3-belopp-1": koldioxidskatt-bensin -"sfs-1994-1776/kap2.4-belopp-1": koldioxidskatt-diesel -"sfs-1994-1776/kap11.1-belopp-1": elskatt-hushall -"sfs-1994-1776/kap11.2-belopp-1": elskatt-norra-sverige -"sfs-1994-1776/kap11.3-belopp-1": elskatt-industri +"sfs-1994-1776/kap2.1-belopp-1": energiskatt-bensin # 4,12 kr/l - "energiskatt på bensin 4 kronor 12 öre per liter" +"sfs-1994-1776/kap2.2-belopp-1": energiskatt-diesel # 2,57 kr/l - "energiskatt på diesel 2 kronor 57 öre" +"sfs-1994-1776/kap2.3-belopp-1": koldioxidskatt-bensin # 2,89 kr/l - "koldioxidskatt på bensin 2 kronor 89 öre" +"sfs-1994-1776/kap2.4-belopp-1": koldioxidskatt-diesel # 3,24 kr/l - "koldioxidskatt på diesel 3 kronor 24 öre" +"sfs-1994-1776/kap11.1-belopp-1": elskatt-hushall # 53,5 öre/kWh - "skatt på elektrisk kraft 53,5 öre per kilowattimme" +"sfs-1994-1776/kap11.2-belopp-1": elskatt-norra-sverige # 24,1 öre/kWh - "i norra Sverige 24,1 öre per kilowattimme" +"sfs-1994-1776/kap11.3-belopp-1": elskatt-industri # 0,6 öre/kWh - "för tillverkningsprocesser 0,6 öre" # --- Studiestödslagen (2022:856) --- -"sfs-2022-856/kap3.1-belopp-1": studiebidrag-gymnasie-manad -"sfs-2022-856/kap3.2-belopp-1": studiebidrag-hogskola-manad -"sfs-2022-856/kap4.1-belopp-1": studielan-hogskola-max-manad -"sfs-2022-856/kap4.2-belopp-1": studielan-hogskola-max-ar -"sfs-2022-856/kap4.3-belopp-1": studielan-atersbetalning-fribeloppsgrans -"sfs-2022-856/kap4.4-procent-1": studielan-ranta +"sfs-2022-856/kap3.1-belopp-1": studiebidrag-gymnasie-manad # 1 250 kr - "studiebidrag med 1 250 kronor per månad" +"sfs-2022-856/kap3.2-belopp-1": studiebidrag-hogskola-manad # 3 964 kr - "studiebidrag för högskola 3 964 kronor" +"sfs-2022-856/kap4.1-belopp-1": studielan-hogskola-max-manad # 9 656 kr - "studielån högst 9 656 kronor per månad" +"sfs-2022-856/kap4.2-belopp-1": studielan-hogskola-max-ar # 115 872 kr - "per år högst 115 872 kronor" +"sfs-2022-856/kap4.3-belopp-1": studielan-fribeloppsgrans # 60 200 kr - "fribelopp på 60 200 kronor per halvår" +"sfs-2022-856/kap4.4-procent-1": studielan-ranta # 0,53% - "ränta på studielån 0,53 procent" # --- Lag om arbetslöshetsförsäkring (1997:238) --- -"sfs-1997-238/kap12.1-belopp-1": a-kassa-tak-dag -"sfs-1997-238/kap12.2-procent-1": a-kassa-ersattningsgrad-forsta-200 -"sfs-1997-238/kap12.3-procent-1": a-kassa-ersattningsgrad-efter-200 -"sfs-1997-238/kap12.4-belopp-1": a-kassa-grundbelopp -"sfs-1997-238/kap12.5-belopp-1": a-kassa-inkomsttak +"sfs-1997-238/kap12.1-belopp-1": a-kassa-tak-dag # 1 200 kr - "dagpenning högst 1 200 kronor per dag" +"sfs-1997-238/kap12.2-procent-1": a-kassa-ersattningsgrad-forsta-200 # 80% - "80 procent de första 200 dagarna" +"sfs-1997-238/kap12.3-procent-1": a-kassa-ersattningsgrad-efter-200 # 70% - "därefter 70 procent" +"sfs-1997-238/kap12.4-belopp-1": a-kassa-grundbelopp # 510 kr - "grundbelopp 510 kronor per dag" +"sfs-1997-238/kap12.5-belopp-1": a-kassa-inkomsttak # 33 000 kr - "inkomsttak på 33 000 kronor per månad" # --- Tobakslagen (2018:2088) --- -"sfs-2018-2088/kap7.1-belopp-1": tobak-sanktionsavgift-lagsta -"sfs-2018-2088/kap7.1-belopp-2": tobak-sanktionsavgift-hogsta +"sfs-2018-2088/kap7.1-belopp-1": tobak-sanktionsavgift-lagsta # 5 000 kr - "sanktionsavgift lägst 5 000 kronor" +"sfs-2018-2088/kap7.1-belopp-2": tobak-sanktionsavgift-hogsta # 500 000 kr - "högst 500 000 kronor" # --- Alkohollagen (2010:1622) --- -"sfs-2010-1622/kap10.1-belopp-1": alkohol-sanktionsavgift-lagsta -"sfs-2010-1622/kap10.1-belopp-2": alkohol-sanktionsavgift-hogsta -"sfs-2010-1622/kap10.2-belopp-1": alkohol-tillstandsavgift +"sfs-2010-1622/kap10.1-belopp-1": alkohol-sanktionsavgift-lagsta # 5 000 kr - "sanktionsavgift lägst 5 000 kronor" +"sfs-2010-1622/kap10.1-belopp-2": alkohol-sanktionsavgift-hogsta # 500 000 kr - "högst 500 000 kronor" +"sfs-2010-1622/kap10.2-belopp-1": alkohol-tillstandsavgift # 10 000 kr - "tillståndsavgift 10 000 kronor" # --- Plan- och bygglagen (2010:900) --- -"sfs-2010-900/kap11.51-belopp-1": byggsanktionsavgift-lagsta -"sfs-2010-900/kap11.51-belopp-2": byggsanktionsavgift-hogsta -"sfs-2010-900/kap11.52-belopp-1": byggsanktionsavgift-per-kvm -"sfs-2010-900/kap12.8-belopp-1": bygglov-avgift-grund +"sfs-2010-900/kap11.51-belopp-1": byggsanktionsavgift-lagsta # 2 500 kr - "byggsanktionsavgift lägst 2 500 kronor" +"sfs-2010-900/kap11.51-belopp-2": byggsanktionsavgift-hogsta # 1 000 000 kr - "högst 1 000 000 kronor" +"sfs-2010-900/kap11.52-belopp-1": byggsanktionsavgift-per-kvm # 500 kr - "500 kronor per kvadratmeter" +"sfs-2010-900/kap12.8-belopp-1": bygglov-avgift-grund # 3 000 kr - "avgift för bygglov grundbelopp 3 000" # --- Fordonsskattelagen (2006:227) --- -"sfs-2006-227/kap2.1-belopp-1": fordonsskatt-personbil-grundbelopp -"sfs-2006-227/kap2.2-belopp-1": fordonsskatt-per-gram-co2 -"sfs-2006-227/kap2.3-belopp-1": fordonsskatt-elbil -"sfs-2006-227/kap2.4-belopp-1": fordonsskatt-laddhybrid -"sfs-2006-227/kap2.5-belopp-1": fordonsskatt-dieseltillagg -"sfs-2006-227/kap3.1-belopp-1": fordonsskatt-lastbil-per-ton -"sfs-2006-227/kap3.2-belopp-1": fordonsskatt-buss-per-ton -"sfs-2006-227/kap4.1-belopp-1": fordonsskatt-motorcykel -"sfs-2006-227/kap4.2-belopp-1": fordonsskatt-slapvagn +"sfs-2006-227/kap2.1-belopp-1": fordonsskatt-personbil-grundbelopp # 360 kr - "grundbelopp 360 kronor per år" +"sfs-2006-227/kap2.2-belopp-1": fordonsskatt-per-gram-co2 # 22 kr - "22 kronor per gram koldioxid" +"sfs-2006-227/kap2.3-belopp-1": fordonsskatt-elbil # 360 kr - "för elbil 360 kronor per år" +"sfs-2006-227/kap2.4-belopp-1": fordonsskatt-laddhybrid # 360 kr - "för laddhybrid 360 kronor" +"sfs-2006-227/kap2.5-belopp-1": fordonsskatt-dieseltillagg # 500 kr - "dieseltillägg 500 kronor" +"sfs-2006-227/kap3.1-belopp-1": fordonsskatt-lastbil-per-ton # 831 kr - "lastbil 831 kronor per ton" +"sfs-2006-227/kap3.2-belopp-1": fordonsskatt-buss-per-ton # 689 kr - "buss 689 kronor per ton" +"sfs-2006-227/kap4.1-belopp-1": fordonsskatt-motorcykel # 180 kr - "motorcykel 180 kronor per år" +"sfs-2006-227/kap4.2-belopp-1": fordonsskatt-slapvagn # 500 kr - "släpvagn 500 kronor per år" # --- Trafikförordningen (1998:1276) --- -"sfs-1998-1276/kap14.1-belopp-1": forseningsavgift-fordonsskatt -"sfs-1998-1276/kap14.2-belopp-1": parkering-kontrollavgift-max +"sfs-1998-1276/kap14.1-belopp-1": forseningsavgift-fordonsskatt # 500 kr - "förseningsavgift 500 kronor" +"sfs-1998-1276/kap14.2-belopp-1": parkering-kontrollavgift-max # 1 300 kr - "kontrollavgift högst 1 300 kronor" # --- Lag om trängselskatt (2004:629) --- -"sfs-2004-629/2-belopp-1": trangselskatt-stockholm-max-dag -"sfs-2004-629/2-belopp-2": trangselskatt-goteborg-max-dag -"sfs-2004-629/3-belopp-1": trangselskatt-hog-belastning -"sfs-2004-629/3-belopp-2": trangselskatt-medel-belastning -"sfs-2004-629/3-belopp-3": trangselskatt-lag-belastning +"sfs-2004-629/2-belopp-1": trangselskatt-stockholm-max-dag # 135 kr - "trängselskatt i Stockholm högst 135 kronor per dag" +"sfs-2004-629/2-belopp-2": trangselskatt-goteborg-max-dag # 60 kr - "i Göteborg högst 60 kronor per dag" +"sfs-2004-629/3-belopp-1": trangselskatt-hog-belastning # 45 kr - "vid hög belastning 45 kronor" +"sfs-2004-629/3-belopp-2": trangselskatt-medel-belastning # 22 kr - "medel belastning 22 kronor" +"sfs-2004-629/3-belopp-3": trangselskatt-lag-belastning # 11 kr - "låg belastning 11 kronor" # --- Offentlighets- och sekretesslagen (2009:400) --- -"sfs-2009-400/kap6.1-belopp-1": avgift-kopiering-forsta-nio-sidor -"sfs-2009-400/kap6.2-belopp-1": avgift-kopiering-per-sida -"sfs-2009-400/kap6.3-belopp-1": avgift-kopiering-utskrift +"sfs-2009-400/kap6.1-belopp-1": avgift-kopiering-forsta-nio-sidor # 0 kr - "de första nio sidorna avgiftsfritt" +"sfs-2009-400/kap6.2-belopp-1": avgift-kopiering-per-sida # 2 kr - "därefter 2 kronor per sida" +"sfs-2009-400/kap6.3-belopp-1": avgift-kopiering-utskrift # 50 kr - "avgift för utskrift 50 kronor" # --- Lag om viten (1985:206) --- -"sfs-1985-206/3-belopp-1": vite-lagsta-belopp -"sfs-1985-206/3-belopp-2": vite-hogsta-belopp -"sfs-1985-206/4-belopp-1": lopande-vite-per-dag-max +"sfs-1985-206/3-belopp-1": vite-lagsta-belopp # 1 000 kr - "vite lägst 1 000 kronor" +"sfs-1985-206/3-belopp-2": vite-hogsta-belopp # 10 000 000 kr - "högst 10 miljoner kronor" +"sfs-1985-206/4-belopp-1": lopande-vite-per-dag-max # 100 000 kr - "löpande vite högst 100 000 per dag" # --- Revisorslagen (2001:883) --- -"sfs-2001-883/kap32.1-belopp-1": revisor-disciplinavgift-lagsta -"sfs-2001-883/kap32.1-belopp-2": revisor-disciplinavgift-hogsta +"sfs-2001-883/kap32.1-belopp-1": revisor-disciplinavgift-lagsta # 5 000 kr - "disciplinavgift lägst 5 000 kronor" +"sfs-2001-883/kap32.1-belopp-2": revisor-disciplinavgift-hogsta # 50 000 kr - "högst 50 000 kronor" # --- Lag om bank- och finansieringsrörelse (2004:297) --- -"sfs-2004-297/kap15.1-belopp-1": bank-sanktionsavgift-lagsta -"sfs-2004-297/kap15.1-belopp-2": bank-sanktionsavgift-hogsta -"sfs-2004-297/kap15.2-procent-1": bank-sanktionsavgift-omsattning-procent +"sfs-2004-297/kap15.1-belopp-1": bank-sanktionsavgift-lagsta # 50 000 kr - "sanktionsavgift lägst 50 000 kronor" +"sfs-2004-297/kap15.1-belopp-2": bank-sanktionsavgift-hogsta # 50 000 000 kr - "högst 50 miljoner kronor" +"sfs-2004-297/kap15.2-procent-1": bank-sanktionsavgift-omsattning-procent # 10% - "eller 10 procent av omsättningen" # --- Jordabalken 12 kap (Hyreslagen) --- -"sfs-1970-994/kap12.55h-belopp-1": hyra-forhandlingsersattning -"sfs-1970-994/kap12.55i-belopp-1": hyra-privatuthyrning-schablon +"sfs-1970-994/kap12.55h-belopp-1": hyra-forhandlingsersattning # 2 500 kr - "förhandlingsersättning 2 500 kronor" +"sfs-1970-994/kap12.55i-belopp-1": hyra-privatuthyrning-schablon # 1 500 kr - "schablonavdrag 1 500 kronor per rum" # --- Lag om skatt på trafikförsäkring (2007:460) --- -"sfs-2007-460/2-procent-1": trafikforsakringsskatt-procent -"sfs-2007-460/3-belopp-1": trafikforsakringsskatt-minimum +"sfs-2007-460/2-procent-1": trafikforsakringsskatt-procent # 32% - "skatt på trafikförsäkringspremie 32 procent" +"sfs-2007-460/3-belopp-1": trafikforsakringsskatt-minimum # 35 kr - "dock lägst 35 kronor per år" # --- Fastighetstaxeringslagen (1979:1152) --- -"sfs-1979-1152/kap7.1-belopp-1": fastighetsavgift-tak-smahus -"sfs-1979-1152/kap7.2-belopp-1": fastighetsavgift-tak-bostadsratt -"sfs-1979-1152/kap7.3-procent-1": fastighetsavgift-procent-taxeringsvarde +"sfs-1979-1152/kap7.1-belopp-1": fastighetsavgift-tak-smahus # 9 525 kr - "fastighetsavgift högst 9 525 kronor" +"sfs-1979-1152/kap7.2-belopp-1": fastighetsavgift-tak-bostadsratt # 1 672 kr - "för bostadsrätt högst 1 672 kronor" +"sfs-1979-1152/kap7.3-procent-1": fastighetsavgift-procent-taxeringsvarde # 0,75% - "0,75 procent av taxeringsvärdet" # --- Sparbankslagen (1987:619) --- -"sfs-1987-619/kap2.1-belopp-1": sparbank-grundfond-minimum +"sfs-1987-619/kap2.1-belopp-1": sparbank-grundfond-minimum # 1 000 000 kr - "grundfond minst 1 miljon kronor" # --- Lag om investeringssparkonto (2011:1268) --- -"sfs-2011-1268/kap8.1-procent-1": isk-schablonintakt-procent -"sfs-2011-1268/kap8.2-procent-1": isk-statslanerantan-tillagg +"sfs-2011-1268/kap8.1-procent-1": isk-schablonintakt-procent # 1,09% - "schablonintäkt 1,09 procent av kapitalunderlaget" +"sfs-2011-1268/kap8.2-procent-1": isk-statslanerantan-tillagg # 1% - "statslåneräntan med tillägg av en procentenhet" # --- Kupongskattelagen (1970:624) --- -"sfs-1970-624/kap5.1-procent-1": kupongskatt-procent -"sfs-1970-624/kap5.2-procent-1": kupongskatt-reducerad-procent +"sfs-1970-624/kap5.1-procent-1": kupongskatt-procent # 30% - "kupongskatt med 30 procent" +"sfs-1970-624/kap5.2-procent-1": kupongskatt-reducerad-procent # 15% - "enligt skatteavtal 15 procent" # --- Lag om godkännande av gåvomottgare (2019:453) --- -"sfs-2019-453/kap7.1-belopp-1": gava-skattereduktion-minimum -"sfs-2019-453/kap7.2-belopp-1": gava-skattereduktion-maximum -"sfs-2019-453/kap7.3-procent-1": gava-skattereduktion-procent +"sfs-2019-453/kap7.1-belopp-1": gava-skattereduktion-minimum # 200 kr - "gåva på minst 200 kronor per tillfälle" +"sfs-2019-453/kap7.2-belopp-1": gava-skattereduktion-maximum # 12 000 kr - "sammanlagt högst 12 000 kronor per år" +"sfs-2019-453/kap7.3-procent-1": gava-skattereduktion-procent # 25% - "skattereduktion med 25 procent av gåvobeloppet" # --- Historiskt: Arvs- och gåvoskattelagen (upphävd 2004) --- -"sfs-1941-416/kap1.1-belopp-1": arvsskatt-fribeloppsgrans -"sfs-1941-416/kap1.2-procent-1": arvsskatt-procent-klass-1 -"sfs-1941-416/kap1.3-procent-1": arvsskatt-procent-klass-2 -"sfs-1941-416/kap1.4-procent-1": arvsskatt-procent-klass-3 +"sfs-1941-416/kap1.1-belopp-1": arvsskatt-fribeloppsgrans # 70 000 kr - "fribelopp 70 000 kronor" +"sfs-1941-416/kap1.2-procent-1": arvsskatt-procent-klass-1 # 10% - "klass 1 (make/barn) 10 procent" +"sfs-1941-416/kap1.3-procent-1": arvsskatt-procent-klass-2 # 20% - "klass 2 (föräldrar/syskon) 20 procent" +"sfs-1941-416/kap1.4-procent-1": arvsskatt-procent-klass-3 # 30% - "klass 3 (övriga) 30 procent" # --- Förmånsrättslagen (1970:979) --- -"sfs-1970-979/kap12.1-belopp-1": lon-formansratt-max -"sfs-1970-979/kap12.2-belopp-1": pension-formansratt-max +"sfs-1970-979/kap12.1-belopp-1": lon-formansratt-max # 4 pbb - "lön med förmånsrätt högst 4 prisbasbelopp" +"sfs-1970-979/kap12.2-belopp-1": pension-formansratt-max # 2 pbb - "pension med förmånsrätt högst 2 prisbasbelopp" # --- Konkurslagen (1987:672) --- -"sfs-1987-672/kap14.1-belopp-1": konkurs-grans-forlikningsforfarande -"sfs-1987-672/kap14.2-belopp-1": konkurs-grans-summarisk +"sfs-1987-672/kap14.1-belopp-1": konkurs-grans-forlikningsforfarande # 50 000 kr - "förlikningsförfarande vid skuld under 50 000" +"sfs-1987-672/kap14.2-belopp-1": konkurs-grans-summarisk # 5 pbb - "summariskt förfarande vid tillgångar under 5 pbb" # --- Kameraövervakningslagen (2018:1200) --- -"sfs-2018-1200/kap5.1-belopp-1": kameraovervakning-sanktionsavgift-lagsta -"sfs-2018-1200/kap5.1-belopp-2": kameraovervakning-sanktionsavgift-hogsta +"sfs-2018-1200/kap5.1-belopp-1": kameraovervakning-sanktionsavgift-lagsta # 5 000 kr - "sanktionsavgift lägst 5 000 kronor" +"sfs-2018-1200/kap5.1-belopp-2": kameraovervakning-sanktionsavgift-hogsta # 500 000 kr - "högst 500 000 kronor" # --- Kompletterande GDPR-lagstiftning --- -"sfs-2018-218/kap6.1-belopp-1": gdpr-sanktionsavgift-lagsta -"sfs-2018-218/kap6.1-belopp-2": gdpr-sanktionsavgift-hogsta -"sfs-2018-218/kap6.2-procent-1": gdpr-sanktionsavgift-omsattning-max +"sfs-2018-218/kap6.1-belopp-1": gdpr-sanktionsavgift-lagsta # 50 000 kr - "sanktionsavgift lägst 50 000 kronor" +"sfs-2018-218/kap6.1-belopp-2": gdpr-sanktionsavgift-hogsta # 20 000 000 EUR - "högst 20 miljoner euro" +"sfs-2018-218/kap6.2-procent-1": gdpr-sanktionsavgift-omsattning-max # 4% - "eller 4 procent av global årsomsättning" # --- Lag om elektronisk kommunikation (2022:482) --- -"sfs-2022-482/kap12.1-belopp-1": ekomlagen-sanktionsavgift-lagsta -"sfs-2022-482/kap12.1-belopp-2": ekomlagen-sanktionsavgift-hogsta -"sfs-2022-482/kap12.2-procent-1": ekomlagen-sanktionsavgift-omsattning -"sfs-2020-100/kap5.2-belopp-1": tillstandsavgift -"sfs-2024-123/kap5.2-belopp-1": tillstandsavgift +"sfs-2022-482/kap12.1-belopp-1": ekomlagen-sanktionsavgift-lagsta # 50 000 kr - "sanktionsavgift lägst 50 000 kronor" +"sfs-2022-482/kap12.1-belopp-2": ekomlagen-sanktionsavgift-hogsta # 10 000 000 kr - "högst 10 miljoner kronor" +"sfs-2022-482/kap12.2-procent-1": ekomlagen-sanktionsavgift-omsattning # 2% - "eller 2 procent av omsättningen" + +# --- Test mappings (för enhetstester) --- +"sfs-2020-100/kap5.2-belopp-1": tillstandsavgift # 500 kr - "avgiften är 500 kronor" +"sfs-2024-123/kap5.2-belopp-1": tillstandsavgift # 500 kr - "avgiften är 500 kronor"