( text: string, fallback: PortfolioRating = "Hold", )
| 56 | * Returns the rating, or `fallback` (default "Hold") when none is found. |
| 57 | */ |
| 58 | export function parseRating( |
| 59 | text: string, |
| 60 | fallback: PortfolioRating = "Hold", |
| 61 | ): PortfolioRating { |
| 62 | if (text) { |
| 63 | const lines = text.split(/\r?\n/); |
| 64 | |
| 65 | // Pass 1: explicit "Rating: X" label. |
| 66 | for (const line of lines) { |
| 67 | RATING_LABEL_RE.lastIndex = 0; |
| 68 | const m = RATING_LABEL_RE.exec(line); |
| 69 | if (m && RATING_SET.has((m[1] ?? "").toLowerCase())) { |
| 70 | return titleCase(m[1]!) as PortfolioRating; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // Pass 2: first 5-tier rating word anywhere. |
| 75 | for (const line of lines) { |
| 76 | for (const word of line.toLowerCase().split(/\s+/)) { |
| 77 | const clean = stripEdges(word); |
| 78 | if (clean && RATING_SET.has(clean)) { |
| 79 | return titleCase(clean) as PortfolioRating; |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | return fallback; |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Read the 5-tier rating out of a Portfolio Manager decision. Alias kept for |
no test coverage detected