Files
boc/iom/ledger/iom_ledger_mapping.py
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

508 lines
15 KiB
Python

"""
IOM to Ledger Mapping
Integration between Infrastructure Object Model and aamos-ledger
"""
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import date
from enum import Enum
class BASAccount(str, Enum):
"""BAS-konton för IOM-objekt"""
# Anläggningstillgångar
BUILDINGS = "1220"
MACHINERY = "1230"
IMPROVEMENTS = "1240"
# Underhåll
MAINTENANCE = "6110"
CONTRACTOR = "6130"
# Avskrivningar
DEPRECIATION_BUILDINGS = "7830"
DEPRECIATION_MACHINERY = "7831"
# Försäkring
INSURANCE = "6310"
# Förlust
IMPAIRMENT = "7970"
# Leverantörsskulder
ACCOUNTS_PAYABLE = "2440"
# Moms
VAT_IN = "2640"
# Ackumulerade avskrivningar
ACCUM_DEPRECIATION = "1229"
@dataclass
class AssetMapping:
"""Mapping between IOM object and ledger asset"""
goid: str
account: BASAccount
acquisition_value: float
acquisition_date: date
useful_life_years: int
depreciation_method: str = "linear"
# IOM metadata
object_type: str = ""
material: str = ""
dimensions: str = ""
# Ledger state
accumulated_depreciation: float = 0.0
book_value: float = 0.0
def calculate_depreciation(self, year: int) -> float:
"""Calculate annual depreciation"""
if self.depreciation_method == "linear":
return self.acquisition_value / self.useful_life_years
return 0.0
def update_book_value(self, year: int):
"""Update book value after depreciation"""
annual_dep = self.calculate_depreciation(year)
self.accumulated_depreciation += annual_dep
self.book_value = self.acquisition_value - self.accumulated_depreciation
class ObjectTypeAccountMapping:
"""Maps IOM object types to BAS accounts"""
MAPPINGS = {
# Buildings and structures
"abutment": BASAccount.BUILDINGS,
"pier": BASAccount.BUILDINGS,
"deck": BASAccount.BUILDINGS,
"facade": BASAccount.BUILDINGS,
"roof": BASAccount.BUILDINGS,
"window": BASAccount.IMPROVEMENTS,
"entrance": BASAccount.IMPROVEMENTS,
"balcony": BASAccount.IMPROVEMENTS,
# Machinery and equipment
"transformer": BASAccount.MACHINERY,
"charging_station": BASAccount.MACHINERY,
"street_light": BASAccount.MACHINERY,
"traffic_signal": BASAccount.MACHINERY,
# Infrastructure
"road_surface": BASAccount.BUILDINGS,
"guardrail": BASAccount.BUILDINGS,
"sign": BASAccount.BUILDINGS,
"parking_surface": BASAccount.BUILDINGS,
"bollard": BASAccount.BUILDINGS,
}
@classmethod
def get_account(cls, object_type: str) -> BASAccount:
"""Get BAS account for object type"""
return cls.MAPPINGS.get(object_type.lower(), BASAccount.BUILDINGS)
@classmethod
def get_useful_life(cls, object_type: str) -> int:
"""Get useful life in years for object type"""
lifespans = {
"abutment": 50,
"pier": 50,
"deck": 40,
"facade": 30,
"roof": 25,
"window": 20,
"entrance": 25,
"balcony": 30,
"transformer": 30,
"charging_station": 15,
"street_light": 20,
"traffic_signal": 15,
"road_surface": 20,
"guardrail": 25,
"sign": 10,
"parking_surface": 20,
"bollard": 20,
}
return lifespans.get(object_type.lower(), 25)
class LedgerTransaction:
"""Represents a ledger transaction"""
def __init__(
self,
date: date,
verno: str,
account: BASAccount,
amount: float,
description: str,
goid: str,
metadata: Optional[Dict] = None
):
self.date = date
self.verno = verno
self.account = account
self.amount = amount
self.description = description
self.goid = goid
self.metadata = metadata or {}
def to_dict(self) -> Dict:
return {
"date": self.date.isoformat(),
"verno": self.verno,
"account": self.account.value,
"account_name": self.account.name,
"amount": self.amount,
"description": self.description,
"goid": self.goid,
"metadata": self.metadata
}
class IOMLedgerMapper:
"""Maps IOM events to ledger transactions"""
def __init__(self):
self.transaction_counter = 0
def _generate_verno(self, year: int) -> str:
"""Generate verifikationsnummer"""
self.transaction_counter += 1
return f"{year}-{self.transaction_counter:07d}"
def map_installation(
self,
goid: str,
object_type: str,
cost: float,
installation_date: date,
contractor: str = "",
project: str = ""
) -> List[LedgerTransaction]:
"""
Map object installation to ledger transactions
Returns:
List of ledger transactions
"""
account = ObjectTypeAccountMapping.get_account(object_type)
verno = self._generate_verno(installation_date.year)
transactions = []
# Debit asset account
transactions.append(LedgerTransaction(
date=installation_date,
verno=verno,
account=account,
amount=cost,
description=f"Installation: {goid}",
goid=goid,
metadata={
"event_type": "installation",
"contractor": contractor,
"project": project
}
))
# Credit accounts payable
transactions.append(LedgerTransaction(
date=installation_date,
verno=verno,
account=BASAccount.ACCOUNTS_PAYABLE,
amount=-cost,
description=f"Leverantörsskuld: {goid}",
goid=goid,
metadata={
"event_type": "installation",
"contractor": contractor
}
))
return transactions
def map_maintenance(
self,
goid: str,
cost: float,
maintenance_date: date,
observation_id: str = "",
defect_code: str = "",
contractor: str = "",
description: str = ""
) -> List[LedgerTransaction]:
"""
Map maintenance to ledger transactions
Returns:
List of ledger transactions
"""
verno = self._generate_verno(maintenance_date.year)
transactions = []
# Debit maintenance account
transactions.append(LedgerTransaction(
date=maintenance_date,
verno=verno,
account=BASAccount.MAINTENANCE,
amount=cost,
description=description or f"Underhåll: {goid}",
goid=goid,
metadata={
"event_type": "maintenance",
"observation_id": observation_id,
"defect_code": defect_code,
"contractor": contractor
}
))
# Credit accounts payable
transactions.append(LedgerTransaction(
date=maintenance_date,
verno=verno,
account=BASAccount.ACCOUNTS_PAYABLE,
amount=-cost,
description=f"Leverantörsskuld: {goid}",
goid=goid,
metadata={
"event_type": "maintenance",
"contractor": contractor
}
))
return transactions
def map_depreciation(
self,
goid: str,
object_type: str,
acquisition_value: float,
useful_life: int,
depreciation_date: date,
accumulated_depreciation: float = 0.0
) -> List[LedgerTransaction]:
"""
Map annual depreciation to ledger transactions
Returns:
List of ledger transactions
"""
annual_depreciation = acquisition_value / useful_life
verno = self._generate_verno(depreciation_date.year)
account = ObjectTypeAccountMapping.get_account(object_type)
dep_account = (
BASAccount.DEPRECIATION_BUILDINGS
if account == BASAccount.BUILDINGS
else BASAccount.DEPRECIATION_MACHINERY
)
transactions = []
# Debit depreciation expense
transactions.append(LedgerTransaction(
date=depreciation_date,
verno=verno,
account=dep_account,
amount=annual_depreciation,
description=f"Avskrivning: {goid}",
goid=goid,
metadata={
"event_type": "depreciation",
"annual_amount": annual_depreciation,
"useful_life": useful_life,
"accumulated": accumulated_depreciation + annual_depreciation
}
))
# Credit accumulated depreciation
transactions.append(LedgerTransaction(
date=depreciation_date,
verno=verno,
account=BASAccount.ACCUM_DEPRECIATION,
amount=-annual_depreciation,
description=f"Ackumulerad avskrivning: {goid}",
goid=goid,
metadata={
"event_type": "depreciation",
"annual_amount": annual_depreciation
}
))
return transactions
def map_component_replacement(
self,
goid: str,
parent_goid: str,
cost: float,
replacement_date: date,
old_component_goid: str = "",
new_component_goid: str = "",
contractor: str = ""
) -> List[LedgerTransaction]:
"""
Map component replacement to ledger transactions
Returns:
List of ledger transactions
"""
verno = self._generate_verno(replacement_date.year)
transactions = []
# Debit maintenance (or capitalize if major)
transactions.append(LedgerTransaction(
date=replacement_date,
verno=verno,
account=BASAccount.MAINTENANCE,
amount=cost,
description=f"Komponentbyte: {goid}",
goid=goid,
metadata={
"event_type": "component_replacement",
"parent_goid": parent_goid,
"old_component": old_component_goid,
"new_component": new_component_goid,
"contractor": contractor
}
))
# Credit accounts payable
transactions.append(LedgerTransaction(
date=replacement_date,
verno=verno,
account=BASAccount.ACCOUNTS_PAYABLE,
amount=-cost,
description=f"Leverantörsskuld: {goid}",
goid=goid,
metadata={
"event_type": "component_replacement",
"contractor": contractor
}
))
return transactions
def map_impairment(
self,
goid: str,
book_value: float,
recoverable_amount: float,
impairment_date: date,
reason: str = "",
observation_id: str = ""
) -> List[LedgerTransaction]:
"""
Map impairment to ledger transactions
Returns:
List of ledger transactions
"""
impairment_loss = book_value - recoverable_amount
if impairment_loss <= 0:
return []
verno = self._generate_verno(impairment_date.year)
transactions = []
# Debit impairment loss
transactions.append(LedgerTransaction(
date=impairment_date,
verno=verno,
account=BASAccount.IMPAIRMENT,
amount=impairment_loss,
description=f"Nedskrivning: {goid}",
goid=goid,
metadata={
"event_type": "impairment",
"book_value": book_value,
"recoverable_amount": recoverable_amount,
"reason": reason,
"observation_id": observation_id
}
))
# Credit asset account
transactions.append(LedgerTransaction(
date=impairment_date,
verno=verno,
account=BASAccount.BUILDINGS, # Simplified - should look up actual account
amount=-impairment_loss,
description=f"Nedskrivning tillgång: {goid}",
goid=goid,
metadata={
"event_type": "impairment",
"impairment_loss": impairment_loss
}
))
return transactions
if __name__ == '__main__':
# Example usage
mapper = IOMLedgerMapper()
from datetime import date
# Example 1: Installation
print("=== Installation ===")
transactions = mapper.map_installation(
goid="TRN-BRG-ABT-CON-001",
object_type="abutment",
cost=4500000,
installation_date=date(2019, 3, 15),
contractor="Skanska AB",
project="Bromöllan bro"
)
for t in transactions:
print(f"{t.account.value}: {t.amount:>12.2f} - {t.description}")
# Example 2: Maintenance
print("\n=== Maintenance ===")
transactions = mapper.map_maintenance(
goid="TRN-BRG-ABT-CON-001",
cost=350000,
maintenance_date=date(2028, 5, 20),
observation_id="OBS-2028-006633",
defect_code="1300",
contractor="Cementa AB",
description="Sprickreparation"
)
for t in transactions:
print(f"{t.account.value}: {t.amount:>12.2f} - {t.description}")
# Example 3: Depreciation
print("\n=== Depreciation ===")
transactions = mapper.map_depreciation(
goid="TRN-BRG-ABT-CON-001",
object_type="abutment",
acquisition_value=4500000,
useful_life=50,
depreciation_date=date(2026, 12, 31),
accumulated_depreciation=720000
)
for t in transactions:
print(f"{t.account.value}: {t.amount:>12.2f} - {t.description}")
# Example 4: Impairment
print("\n=== Impairment ===")
transactions = mapper.map_impairment(
goid="TRN-BRG-ABT-CON-001",
book_value=3600000,
recoverable_amount=2100000,
impairment_date=date(2031, 6, 15),
reason="Structural degradation",
observation_id="OBS-2031-001847"
)
for t in transactions:
print(f"{t.account.value}: {t.amount:>12.2f} - {t.description}")