# CountryLedger

**Country-level traffic reports without exporting the visitor trail.**

CountryLedger turns existing Nginx/Apache combined access logs or traffic CSVs into a self-contained HTML report and an aggregate JSON file. It uses IP2Location's local CSV range databases as its core geolocation component. No account, API key, Python dependencies, hosted inference, or network connection is needed to run the application after obtaining your licensed database.

The report shows country request counts, HTTP response classes, lookup coverage, and malformed-row counts. Country labels below a configurable request threshold are withheld. IPs, requested URLs, precise timestamps, referrers and user agents are not exported. Original files are not deleted or modified.

## Run the reproducible demo

Requires Python 3.10 or newer. From this directory:

```sh
python3 countryledger.py examples/synthetic-traffic.csv \
  --format csv --db4 examples/synthetic-country-ranges.csv \
  --output demo-report
```

Open `demo-report/index.html` in a browser. The companion `report.json` contains only aggregates. An existing output directory is rejected; use a new name for each run.

**The demo traffic and country assignments are synthetic.** They are not observed visitors, actual geolocation claims, or a redistributed IP2Location database. The small fixture only exercises the documented CSV format. In this example, the four-request country label is hidden by the default ten-request threshold.

## Use real IP2Location data

1. Obtain an appropriately licensed [IP2Location LITE DB1 CSV](https://lite.ip2location.com/database/db1-ip-country) or commercial [IP2Location database](https://www.ip2location.com/). LITE downloads may require a free provider account; retain the provider's attribution and license conditions. No geolocation database is bundled here.
2. Extract the database CSV locally. Its first four columns must be `ip_from, ip_to, country_code, country_name`; additional DB columns are ignored. Headerless files and that exact header are supported. Ranges must be sorted, inclusive and non-overlapping.
3. Run against logs that you are authorized to process:

```sh
python3 countryledger.py /local/path/access.log \
  --db4 /local/path/IP2LOCATION-LITE-DB1.CSV \
  --minimum-requests 10 --output country-report
```

For IPv6 traffic, also supply `--db6 /local/path/IP2LOCATION-LITE-DB1.IPV6.CSV`. IPv4-mapped IPv6 addresses are normalized to IPv4. Each supplied file uses its corresponding address family. Requests without a matching range, or without a supplied IPv6 database, are counted as unmapped. Private/reserved addresses are counted separately.

CSV traffic needs `ip,status` columns. Use `--format csv`; other columns are ignored. Combined-log mode expects the standard remote-address, identity, user, bracketed timestamp, quoted request, status and size structure. Custom log formats should be exported to the two-column CSV first. Malformed rows are counted and skipped without being printed.

## Why it is different

The deliverable is a portable report, not another visitor database. Streamed records become aggregate counters; no per-address cache or visitor history is written. IP2Location's local range lookup keeps the geolocation step offline. The HTML has no scripts, fonts or remote assets and a restrictive content security policy. Country labels are escaped before rendering.

Counts refer to **requests, not unique visitors**. The threshold hides small country labels, but is not k-anonymity, differential privacy, or a guarantee against inference. Totals and repeated reports can reveal information. Review exports before sharing. Country-level IP geolocation estimates a network location, not a person's precise position; proxies and VPNs affect accuracy. This is an analytics tool, not a person-locator or an access-control decision engine.

## Verify

```sh
python3 -m unittest discover -s tests -v
```

Tests cover inclusive range edges, gaps, invalid overlaps, 128-bit precision, mapped IPv6, small-label suppression, aggregate reconciliation, HTML escaping, and omission of raw addresses and request metadata. Separately, the parser and lookup passed 202 inclusive-boundary checks over all 101 rows in IP2Location's published DB1 sample on September 20, 2026. That provider sample is not redistributed here. This verifies sample compatibility, not the accuracy of a current full database; validate that separately with your licensed database.

## License and development disclosure

Application code and synthetic fixtures: MIT, copyright 2026 Simone Maria Romeo. IP2Location data remains under its own license. Reports include IP2Location attribution. CountryLedger is independent and is not endorsed by IP2Location.

Developed with substantial AI assistance from OpenAI Codex for the IP2Location Programming Contest 2026. No customer logs, private visitor datasets, or credentials are included in this repository.
