codice-fiscale-italia 1.0.1
Enterprise-grade Italian Fiscal Code library with Clean Architecture
License: MIT
Requires Python: >=3.8
Keywords: fiscal code, codice fiscale, italian, italy, tax, identification

# Italian Fiscal Code Library

[![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]() [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen)]() [![Python](https://img.shields.io/badge/python-3.8%2B-blue)]() [![License](https://img.shields.io/badge/license-MIT-blue)]()

A robust, enterprise-grade Python library for generating and validating Italian Fiscal Codes (Codice Fiscale) following Clean Architecture principles.

## Features

✨ **Core Functionality**

- Generate fiscal codes from personal information
- Validate existing fiscal codes with checksum verification
- **Decode fiscal codes to extract personal information**
- Handle Omocodia cases (collision resolution via letter substitution)
- Support for all Italian municipalities
- **Support for foreign countries (birth abroad with Z-codes)**

🏗️ **Architecture**

- Clean Architecture implementation
- Immutable domain models
- Dependency injection support
- Singleton pattern for efficient data management
- Lazy loading for optimal performance

🧪 **Quality**

- 98% test coverage
- Type hints for better IDE support
- Comprehensive error handling
- Thread-safe operations

## Installation

```bash
pip install codice_fiscale_italia
```

Or install from source:

```bash
git clone https://github.com/remahmoud/codice_fiscale_italia.git
cd codice_fiscale_italia
pip install -e .
```

## Quick Start

### Generating a Fiscal Code

```python
from datetime import date
from codice_fiscale_italia import FiscalCodeGenerator, Person, Gender

# Create a person
person = Person(
    name="Mario",
    surname="Rossi",
    birth_date=date(1990, 1, 15),
    gender=Gender.MALE,
    birth_place="Roma"
)

# Generate fiscal code
generator = FiscalCodeGenerator()
fiscal_code = generator.generate(person)
print(fiscal_code)  # Output: RSSMRA90A15H501K
```

### Validating a Fiscal Code

```python
from codice_fiscale_italia import FiscalCodeValidator

# Validate a fiscal code
is_valid = FiscalCodeValidator.validate("RSSMRA90A15H501K")
print(is_valid)  # Output: True

# Invalid fiscal code
is_valid = FiscalCodeValidator.validate("RSSMRA90A15H501X")
print(is_valid)  # Output: False
```

### Decoding a Fiscal Code

Extract personal information from an existing fiscal code:

```python
from codice_fiscale_italia import FiscalCodeDecoder

# Create decoder
decoder = FiscalCodeDecoder()

# Decode a fiscal code
result = decoder.decode("RSSMRA90A15H501K")

print(result)
# Output:
# {
#     'surname_code': 'RSS',
#     'name_code': 'MRA',
#     'birth_year': 1990,
#     'birth_month': 1,
#     'birth_day': 15,
#     'gender': 'M',
#     'birth_place_code': 'H501',
#     'birth_place': 'Roma',
#     'is_valid': True
# }

# Access specific fields
print(f"Birth year: {result['birth_year']}")  # 1990
print(f"Gender: {result['gender']}")          # M
print(f"Birth place: {result['birth_place']}")  # Roma
```

### Handling Omocodia

The library automatically handles Omocodia cases (when letters replace digits to resolve collisions):

```python
from codice_fiscale_italia import FiscalCodeValidator

# Fiscal code with omocodia substitution (L replaces 0)
code_with_omocodia = "RSSMRA90A15H5L1K"
is_valid = FiscalCodeValidator.validate(code_with_omocodia)
print(is_valid)  # Output: True (automatically normalized)
```

### Foreign Country Support

Generate fiscal codes for people born abroad using Italian country names:

```python
from datetime import date
from codice_fiscale_italia import FiscalCodeGenerator, FiscalCodeDecoder, Person, Gender

# Person born in France
person = Person(
    name="Mario",
    surname="Rossi",
    birth_date=date(1990, 1, 15),
    gender=Gender.MALE,
    birth_place="Francia"  # Italian name for France
)

generator = FiscalCodeGenerator()
fiscal_code = generator.generate(person)
print(fiscal_code)  # Output: RSSMRA90A15Z110R

# Decode reveals the foreign country
decoder = FiscalCodeDecoder()
result = decoder.decode(fiscal_code)
print(result['birth_place'])       # Output: Francia
print(result['birth_place_code'])  # Output: Z110
```

Foreign country codes use the Z-code format (e.g., Z110 for France, Z112 for Germany,
Z404 for USA). Country names should be provided in Italian (e.g., "Francia", "Germania",
"Stati Uniti d'America").

## Detailed Usage

### Person Model

The `Person` class is an immutable value object representing personal information:

```python
from datetime import date
from codice_fiscale_italia import Person, Gender

person = Person(
    name="Maria",           # First name
    surname="Bianchi",      # Last name
    birth_date=date(1985, 3, 25),  # Birth date
    gender=Gender.FEMALE,   # Gender (MALE or FEMALE)
    birth_place="Milano"    # Birth place (Italian municipality or foreign country)
)
```

**Note:** The `Person` object is immutable (frozen dataclass), so you cannot modify its attributes after creation.

### Gender Enum

```python
from codice_fiscale_italia import Gender

# Available options
Gender.MALE    # 'M'
Gender.FEMALE  # 'F'
```

### Decoding Details

The decoder extracts the following information from fiscal codes:

```python
from codice_fiscale_italia import FiscalCodeDecoder

decoder = FiscalCodeDecoder()
result = decoder.decode("BNCMRA85C65F205F")

# Extracted information:
# - surname_code: 'BNC' (encoded surname)
# - name_code: 'MRA' (encoded name)
# - birth_year: 1985 (full 4-digit year with smart century detection)
# - birth_month: 3 (March)
# - birth_day: 25 (decoded from 65, which is 25 + 40 for females)
# - gender: 'F' (automatically detected from day encoding)
# - birth_place_code: 'F205' (cadastral code)
# - birth_place: 'Milano' (resolved municipality name)
# - is_valid: True (checksum validation result)
```

**Note:** The decoder extracts encoded name/surname representations, not the original names. It handles:

- Automatic gender detection (day > 40 = female)
- Smart year detection (determines if 1900s or 2000s)
- Omocodia codes (letters that replace digits)
- Place name resolution (cadastral code → city name)

### Error Handling

The library provides specific exceptions for different error scenarios:

```python
from codice_fiscale_italia import FiscalCodeGenerator, Person, Gender
from codice_fiscale_italia.domain.exceptions import PlaceNotFoundError, FiscalCodeError
from datetime import date

try:
    person = Person(
        name="Mario",
        surname="Rossi",
        birth_date=date(1990, 1, 15),
        gender=Gender.MALE,
        birth_place="NonExistentCity"  # Invalid city
    )
    generator = FiscalCodeGenerator()
    code = generator.generate(person)
except PlaceNotFoundError as e:
    print(f"City not found: {e}")
except FiscalCodeError as e:
    print(f"Error generating fiscal code: {e}")
```

### Complete Example: Generate, Validate, and Decode

Here's a complete workflow showing all three operations:

```python
from datetime import date
from codice_fiscale_italia import (
    FiscalCodeGenerator,
    FiscalCodeValidator,
    FiscalCodeDecoder,
    Person,
    Gender
)

# 1. Generate a fiscal code
person = Person(
    name="Mario",
    surname="Rossi",
    birth_date=date(1990, 1, 15),
    gender=Gender.MALE,
    birth_place="Roma"
)

generator = FiscalCodeGenerator()
fiscal_code = generator.generate(person)
print(f"Generated: {fiscal_code}")
# Output: RSSMRA90A15H501K

# 2. Validate the fiscal code
validator = FiscalCodeValidator()
is_valid = validator.validate(fiscal_code)
print(f"Is valid: {is_valid}")
# Output: True

# 3. Decode the fiscal code
decoder = FiscalCodeDecoder()
info = decoder.decode(fiscal_code)
print(f"Birth year: {info['birth_year']}")
print(f"Gender: {info['gender']}")
print(f"Birth place: {info['birth_place']}")
# Output:
# Birth year: 1990
# Gender: M
# Birth place: Roma
```

### Advanced: Custom Repository

You can inject a custom repository for testing or alternative data sources:

```python
from codice_fiscale_italia import FiscalCodeGenerator
from codice_fiscale_italia.data.repository import PlaceRepository

# Use default repository (singleton)
generator = FiscalCodeGenerator()

# Or inject a custom repository
custom_repo = PlaceRepository()
generator = FiscalCodeGenerator(repository=custom_repo)
```

## How It Works

The Italian Fiscal Code is a 16-character alphanumeric code that encodes:

1. **Surname** (3 chars): Consonants from surname, then vowels if needed
2. **Name** (3 chars): Consonants from name (special rule if 4+ consonants)
3. **Year of Birth** (2 chars): Last 2 digits of year
4. **Month** (1 char): Letter code (A=Jan, B=Feb, C=Mar, etc.)
5. **Day & Gender** (2 chars): Day of month, +40 for females
6. **Place Code** (4 chars): Cadastral code of birth municipality
7. **Check Digit** (1 char): Calculated checksum

### Example Breakdown

For `RSSMRA90A15H501K`:

- `RSS` = Rossi (surname)
- `MRA` = Mario (name)
- `90` = 1990 (year)
- `A` = January (month)
- `15` = 15th day, male (day & gender)
- `H501` = Roma (place code)
- `K` = Check digit

The decoder can extract this information back from the fiscal code, automatically handling gender detection (day > 40 = female) and omocodia cases.

## API Reference

### Classes

#### `Person`

Immutable value object representing a person.

**Attributes:**

- `name: str` - First name
- `surname: str` - Last name
- `birth_date: date` - Date of birth
- `gender: Gender` - Gender (MALE or FEMALE)
- `birth_place: str` - Name of Italian municipality

#### `Gender`

Enum for gender values.

**Values:**

- `Gender.MALE` - Male ('M')
- `Gender.FEMALE` - Female ('F')

#### `FiscalCodeGenerator`

Generates fiscal codes from person data.

**Methods:**

- `__init__(repository: PlaceRepository = None)` - Initialize generator
- `generate(person: Person) -> str` - Generate fiscal code

#### `FiscalCodeValidator`

Validates fiscal codes.

**Methods:**

- `validate(code: str) -> bool` - Validate a fiscal code
- `calculate_check_digit(code_partial: str) -> str` - Calculate check digit for 15-char code

#### `FiscalCodeDecoder`

Decodes fiscal codes to extract personal information.

**Methods:**

- `__init__(repository: PlaceRepository = None)` - Initialize decoder
- `decode(fiscal_code: str) -> Dict[str, Any]` - Decode a fiscal code to extract information

**Returns:**

Dictionary containing:

- `raw_code: str` - Original fiscal code
- `is_valid: bool` - Whether check digit is valid
- `surname_code: str` - 3-character encoded surname
- `name_code: str` - 3-character encoded name
- `birth_year: int` - Full 4-digit birth year
- `birth_month: int` - Birth month (1-12)
- `birth_day: int` - Birth day (1-31)
- `gender: str` - Gender ('M' or 'F')
- `birth_place_code: str` - 4-character cadastral code
- `birth_place: str` - Municipality name (or None if not found)

### Exceptions

- `FiscalCodeError` - Base exception for all library errors
- `PlaceNotFoundError` - Raised when municipality is not found
- `InvalidFormatError` - Raised for invalid format
- `InvalidChecksumError` - Raised for invalid check digit
- `MissingDataError` - Raised for missing required data

## Command-Line Interface

The library includes a CLI for easy command-line usage. When run in an interactive
terminal, the CLI displays rich formatted output with colors and Unicode box
drawing. When piped or redirected, it outputs plain machine-friendly text.
Use `--no-color` to disable colors in interactive mode.

### Generate a Fiscal Code

```bash
python -m codice_fiscale_italia.cli generate \
  --name Mario \
  --surname Rossi \
  --birth-date 1990-01-15 \
  --gender M \
  --birth-place Roma
# Output: RSSMRA90A15H501K
```

### Generate for Foreign Birth Place

```bash
python -m codice_fiscale_italia.cli generate \
  --name Mario \
  --surname Rossi \
  --birth-date 1990-01-15 \
  --gender M \
  --birth-place Francia
# Output: RSSMRA90A15Z110R
```

### Validate a Fiscal Code

```bash
python -m codice_fiscale_italia.cli validate RSSMRA90A15H501K
# Output: valid

python -m codice_fiscale_italia.cli validate RSSMRA90A15H501K --verbose
# Output: 'RSSMRA90A15H501K' is a valid fiscal code
```

### Decode a Fiscal Code

```bash
# Pretty output (default)
python -m codice_fiscale_italia.cli decode RSSMRA90A15H501K
# Output:
# Fiscal Code: RSSMRA90A15H501K
# Valid: Yes
#
# Decoded Information:
#   Surname Code: RSS
#   Name Code: MRA
#   Birth Year: 1990
#   Birth Month: 1
#   Birth Day: 15
#   Gender: M
#   Birth Place Code: H501
#   Birth Place: Roma

# JSON output (for scripts)
python -m codice_fiscale_italia.cli decode RSSMRA90A15H501K --json
# Output:
# {
#   "raw_code": "RSSMRA90A15H501K",
#   "is_valid": true,
#   "surname_code": "RSS",
#   "name_code": "MRA",
#   "birth_year": 1990,
#   "birth_month": 1,
#   "birth_day": 15,
#   "gender": "M",
#   "birth_place_code": "H501",
#   "birth_place": "Roma"
# }
```

### CLI Help

```bash
# Show all commands
python -m codice_fiscale_italia.cli --help

# Show help for specific command
python -m codice_fiscale_italia.cli decode --help
```

## Examples

The repository includes example scripts demonstrating various use cases:

- **`examples/basic_generation.py`** - Basic fiscal code generation
- **`examples/validation.py`** - Validating fiscal codes
- **`examples/decoding.py`** - Decoding fiscal codes to extract information
- **`examples/batch_processing.py`** - Processing multiple fiscal codes

Run any example:

```bash
python examples/decoding.py
python examples/basic_generation.py
```

## Development

### Setup Development Environment

```bash
# Clone the repository
git clone https://github.com/remahmoud/codice-fiscale-italia.git
cd codice-fiscale-italia

# Create a virtual environment
python -m venv venv

# Activate the virtual environment
# On macOS/Linux:
source venv/bin/activate
# On Windows (CMD):
venv\Scripts\activate
# On Windows (PowerShell):
venv\Scripts\Activate.ps1

# Install the package in editable mode
pip install -e .

# Install development dependencies
pip install -r requirements-dev.txt
```

### Running Tests

```bash
# Run all tests
pytest

# Run with coverage
pytest --cov=codice_fiscale_italia --cov-report=html

# Run specific test file
pytest tests/test_generator.py -v
```

### Code Quality

```bash
# Format code
black codice_fiscale_italia tests

# Lint code
flake8 codice_fiscale_italia tests

# Type checking
mypy codice_fiscale_italia
```

## Data Sources

The library includes cadastral codes for all Italian municipalities. The data is loaded lazily for optimal performance.

## Performance

- **Lazy Loading**: Municipality data is loaded only when first accessed
- **Singleton Pattern**: Repository is instantiated once and reused
- **Memory Efficient**: Minimal memory footprint
- **Fast**: Optimized algorithms for generation and validation

## Compatibility

- Python 3.8+
- No external dependencies for core functionality
- Development requires pytest, black, flake8, mypy

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Ensure all tests pass
6. Submit a pull request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- Italian fiscal code algorithm based on official government specifications
- Municipality cadastral codes from official Italian government data

## Support

For issues, questions, or contributions, please visit:

- [Issue Tracker](https://github.com/remahmoud/codice-fiscale-italia/issues)
- [GitHub Repository](https://github.com/remahmoud/codice-fiscale-italia)

## Changelog

### v1.1.0 (Current)

- **New Feature**: Fiscal code decoder - extract personal information from fiscal codes
- Added `FiscalCodeDecoder` class with full decoding capabilities
- CLI decode command with pretty-print and JSON output options
- Smart gender detection (automatic from day encoding)
- Intelligent year detection (1900s vs 2000s)
- Omocodia handling in decoder
- Place name resolution from cadastral codes
- 118 tests total (23 new decoder tests)
- 98% test coverage maintained
- Complete decoder documentation and examples

### v1.0.0

- Initial release
- Fiscal code generation
- Fiscal code validation
- Omocodia support
- Comprehensive test suite (95 tests, 94% coverage)
- Complete documentation

## Future Enhancements

- [x] ~~Foreign country support for birth places~~
- [x] ~~Reverse engineering (extract info from fiscal code)~~
- [ ] Additional validation rules
- [ ] Performance optimizations
- [ ] Multi-language support
- [ ] REST API wrapper