File size: 1,359 Bytes
e5e35a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from __future__ import annotations

import re
from typing import Any, Dict, List


def parse_portfolio_input(raw_text: str) -> List[Dict[str, Any]]:
    """
    Parse a lightweight holdings string into a list of holdings.

    Supported examples:
    - "10 AAPL, 5 MSFT, 2 VTI"
    - "AAPL 10; MSFT 5; VTI 2"
    - "10 shares of AAPL\n5 shares of MSFT"
    """
    holdings: List[Dict[str, Any]] = []
    if not raw_text:
        return holdings

    chunks = re.split(r"[,\n;]+", raw_text)
    for chunk in chunks:
        text = chunk.strip()
        if not text:
            continue

        match = re.search(
            r"(?P<qty>\d+(?:\.\d+)?)\s*(?:shares?|units?)?\s*(?:of\s+)?(?P<symbol>[A-Za-z][A-Za-z0-9.\-]{0,9})",
            text,
            flags=re.IGNORECASE,
        )
        if not match:
            match = re.search(
                r"(?P<symbol>[A-Za-z][A-Za-z0-9.\-]{0,9})\s*(?:shares?|units?)?\s*(?P<qty>\d+(?:\.\d+)?)",
                text,
                flags=re.IGNORECASE,
            )
        if not match:
            continue

        symbol = match.group("symbol").strip().upper()
        try:
            quantity = float(match.group("qty"))
        except ValueError:
            continue

        if symbol and quantity > 0:
            holdings.append({"symbol": symbol, "quantity": quantity})

    return holdings