ib-flex 0.2.3

Type-safe parser for Interactive Brokers (IBKR) FLEX XML statements: trades, positions, cash flows, corporate actions, and P&L.
Documentation
# CLAUDE.md

`ib-flex` is a pure-Rust parser for Interactive Brokers **FLEX** (Flex Web Query)
XML statements — both **Activity FLEX** and **Trade Confirmation FLEX**. It is a
standalone, MIT-licensed crate published on crates.io. Keep it dependency-light
(`quick-xml`, `serde`, `rust_decimal`, `chrono`, `thiserror`; `reqwest`/`tokio`
only behind the `api-client` feature) and free of any coupling to other repos.

## Before every commit (required)

```bash
cargo fmt && cargo clippy --all-targets -- -D warnings
```

Both must be clean (zero warnings) before committing. Then run `cargo test`.

## Commands

```bash
cargo build                         # library (add --features api-client for the HTTP client)
cargo test                          # unit + integration + doctests
cargo test --doc                    # doctests only
cargo bench                         # benchmarks
# Optional smoke test against a real (private) statement; skipped if the env var is unset:
IB_FLEX_REAL_XML=path/to/statement.xml cargo test --test real_data_smoke -- --nocapture
```

Never commit real account XML — put it under `tmp/` (gitignored).

## Layout

- `src/lib.rs` — public API: `parse_activity_flex`, `parse_activity_flex_all`,
  `parse_trade_confirmation`, `detect_statement_type` (+ re-exports). `api::FlexApiClient`
  lives behind the `api-client` feature.
- `src/types/`
  - `common.rs` — shared enums (`AssetCategory`, `BuySell`, `OpenClose`, `OrderType`, `TradeType`, …).
  - `activity.rs``Trade`, `Position`, `CashTransaction`, `CorporateAction`, `SecurityInfo`,
    `ConversionRate`, the top-level `ActivityFlexStatement`, and the per-section wrapper structs.
  - `extended.rs` — the remaining element structs (transfers, accruals, performance summaries,
    SLB, fees, summaries, …).
  - `trade_confirmation.rs``TradeConfirmationStatement`.
- `src/parsers/`
  - `activity.rs`, `trade_confirmation.rs` — parse entry points (serde + quick-xml).
  - `xml_utils.rs` — the custom optional deserializers (see below).
- `tests/` — integration tests + XML fixtures. `wild_fixtures.rs` covers real-world-shaped
  edge cases; `real_data_smoke.rs` is the env-gated real-data test.

## Conventions and gotchas

- **`ibflex` is the ground truth for attribute names.** The Python library
  [csingley/ibflex]https://github.com/csingley/ibflex has been validated against real IB
  output for years; its dataclass field names ARE the real camelCase XML attribute names.
  When adding or renaming a field, verify the attribute name there — synthetic fixtures have
  historically used wrong names (e.g. `price` instead of the real `tradePrice`).
- **Every attribute-derived field is `Option<T>`.** Real statements may omit any attribute, and
  a missing one must never fail the whole parse. Use the helpers in `xml_utils.rs`:
  - `Option<Decimal>``deserialize_optional_decimal` (strips thousands separators; `-`/`--`/`N/A`/`""``None`)
  - `Option<NaiveDate>``deserialize_optional_date` (accepts `yyyy-MM-dd` / `yyyyMMdd`, the `MULTI` sentinel, and a trailing time component)
  - `Option<String>``deserialize_optional_string` (`""` and sentinels → `None`)
  - `Option<bool>``deserialize_optional_bool` (`Y`/`N`/`Yes`/`No`)
  - enums → `#[serde(rename = "@x", default)]` (each enum has an `#[serde(other)] Unknown` catch-all)
- **Datetimes are kept as `Option<String>`** (IB format `yyyyMMdd;HHmmss`), not parsed to a chrono type.
- IB only emits dates as ISO-8601 (`yyyy-MM-dd`) or compact (`yyyyMMdd`); European `dd/MM/yyyy` is unsupported.
- The `<Trades>` container interleaves `<Trade>`, `<Lot>`, `<Order>`, `<SymbolSummary>`, and
  `<AssetSummary>` rows (all trade-shaped); they are captured into
  `TradesWrapper::{items, lots, orders, symbol_summaries, asset_summaries}`.
- `CorporateActionType` and `TransactionCode` enums exist but are currently **unwired** — the raw
  code string is captured instead. Wiring or removing them is an open follow-up.

## Release

1. Bump `version` in `Cargo.toml`; move CHANGELOG `[Unreleased]` to `[x.y.z]` with the date.
2. Commit `chore: Release vX.Y.Z`, tag `git tag -a vX.Y.Z -m "Release vX.Y.Z"`, push the commit + tag.
3. Pushing a `v*.*.*` tag triggers `.github/workflows/release.yml`, which verifies the tag matches
   `Cargo.toml` and runs `cargo publish`.
4. Create the matching GitHub Release for the tag.