Spaces:
Sleeping
Sleeping
Download analytics/segments.py from Viney/readthrough: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/spaces/Viney/readthrough/resolve/main/analytics/segments.py
- Command line
-
hf download hf://spaces/Viney/readthrough/analytics/segments.py
-
curl -L -o segments.py https://huggingface.co/spaces/Viney/readthrough/resolve/main/analytics/segments.py
10.1 kB
| """Turn stored segment rows into a display-ready breakdown. | |
| Two problems sit between the database and a chart, and both are solved here | |
| rather than in the renderer so they can be tested without Streamlit: | |
| **Aggregates double-count.** A filing tags both the detail members and their | |
| subtotals -- Apple reports iPhone, Mac, iPad and Wearables *and* a "Products" | |
| line equal to their sum. Plotting all five inflates the total by 72%. The | |
| subtotal is identified structurally, by being the sum of other members, so no | |
| per-issuer list of member names is needed. | |
| **Year-on-year needs the comparable period.** It comes free: a filing carries | |
| its prior-year quarter in the same instance document, so the comparison is a | |
| second read against a period end the store already holds. | |
| """ | |
| from __future__ import annotations | |
| from itertools import combinations | |
| from typing import Optional | |
| from ingestion.segments import PRODUCT_AXIS, SEGMENT_AXIS | |
| from storage import segments_db | |
| # How close a member must be to a subtotal of others to count as one. Filers | |
| # round to the million, so exact equality is too strict and anything looser | |
| # starts swallowing real members. | |
| _AGGREGATE_TOLERANCE = 0.005 | |
| # Enumerating subsets is exponential; a filer with more members than this is | |
| # reporting at a granularity where subtotals are not the failure mode. | |
| _MAX_MEMBERS_FOR_SUBSET_SCAN = 14 | |
| def _is_subtotal(value: float, others: list[float]) -> bool: | |
| """True when *value* equals the sum of two or more of *others*.""" | |
| if value <= 0 or len(others) > _MAX_MEMBERS_FOR_SUBSET_SCAN: | |
| return False | |
| # Every subset is tested. An early break on an over-target sum looks like | |
| # a free prune but is wrong: itertools.combinations does not yield subsets | |
| # in ascending-sum order, so breaking skips the matching subset that comes | |
| # later at the same size -- which is exactly how Apple's "Products" line | |
| # survived its first pass here. | |
| for size in range(2, len(others) + 1): | |
| for combo in combinations(others, size): | |
| total = sum(combo) | |
| if total and abs(total - value) / value <= _AGGREGATE_TOLERANCE: | |
| return True | |
| return False | |
| def _smallest_subset_summing_to(rows: list[dict], target: float) -> list[dict]: | |
| """The fewest rows whose revenue sums to *target*, or [].""" | |
| priced = [r for r in rows if r.get("revenue")] | |
| if len(priced) > _MAX_MEMBERS_FOR_SUBSET_SCAN: | |
| return [] | |
| for size in range(1, len(priced) + 1): | |
| for combo in combinations(priced, size): | |
| total = sum(float(r["revenue"]) for r in combo) | |
| if abs(total - target) / target <= _AGGREGATE_TOLERANCE: | |
| return list(combo) | |
| return [] | |
| def drop_aggregate_members(rows: list[dict], total: Optional[float] = None) -> list[dict]: | |
| """Remove members that restate the sum of other members. | |
| With *total* -- the period's consolidated revenue -- the rule is exact: | |
| whatever the members overshoot the company's own top line by is the | |
| subtotal, so the rows summing to that excess are the ones to drop. This is | |
| the path that runs in production. | |
| Without it the fallback asks whether each member equals a sum of others. | |
| That is enough to spot a subtotal but it can also fire on a genuine member | |
| whose value happens to match a combination of its peers, so it is only | |
| used when no consolidated figure is available to anchor against. | |
| """ | |
| priced = [(r, float(r["revenue"])) for r in rows if r.get("revenue")] | |
| if not priced: | |
| return list(rows) | |
| if total: | |
| excess = sum(v for _r, v in priced) - float(total) | |
| if excess / float(total) <= _AGGREGATE_TOLERANCE: | |
| return list(rows) | |
| drop = {id(r) for r in _smallest_subset_summing_to(rows, excess)} | |
| if drop: | |
| return [r for r in rows if id(r) not in drop] | |
| # The overshoot matches no combination of members: the mix and the | |
| # top line disagree for a reason this code cannot name. Show every | |
| # member rather than guessing which one to hide. | |
| return list(rows) | |
| keep = [] | |
| for row in rows: | |
| value = row.get("revenue") | |
| if not value: | |
| keep.append(row) | |
| continue | |
| others = [v for r, v in priced if r is not row] | |
| if not _is_subtotal(float(value), others): | |
| keep.append(row) | |
| return keep | |
| def _consolidated_revenue(ticker: str, period_end: str) -> Optional[float]: | |
| """The period's top-line revenue from the metrics table, if stored. | |
| ``metrics.report_date`` holds a period END despite its name, which is what | |
| makes it join cleanly against a segment period_end. | |
| """ | |
| try: | |
| from storage import metrics_db | |
| for row in metrics_db.get_all_metrics(ticker.upper()): | |
| if row.get("report_date") == period_end and row.get("revenue"): | |
| return float(row["revenue"]) | |
| except Exception: | |
| return None | |
| return None | |
| def breakdown(ticker: str, axis: str) -> dict: | |
| """The latest quarter's lines for *axis*, with year-on-year and margin. | |
| Returns ``{}`` when nothing is stored, so a caller can treat "no segment | |
| data" and "no segment block" as the same case. | |
| """ | |
| period_end = segments_db.latest_period_end(ticker, axis) | |
| if not period_end: | |
| return {} | |
| current = drop_aggregate_members( | |
| segments_db.read_segments(ticker, axis, period_end), | |
| total=_consolidated_revenue(ticker, period_end), | |
| ) | |
| if not current: | |
| return {} | |
| prior_end = segments_db.prior_year_period_end(ticker, axis, period_end) | |
| prior = { | |
| r["member"]: r | |
| for r in (segments_db.read_segments(ticker, axis, prior_end) if prior_end else []) | |
| } | |
| lines = [] | |
| for row in current: | |
| revenue = row.get("revenue") | |
| was = (prior.get(row["member"]) or {}).get("revenue") | |
| operating_income = row.get("operating_income") | |
| lines.append({ | |
| "label": row.get("label") or row.get("member"), | |
| "member": row.get("member"), | |
| "revenue": revenue, | |
| "prior_revenue": was, | |
| "yoy_pct": ((revenue - was) / was * 100) if revenue and was else None, | |
| "operating_income": operating_income, | |
| "margin_pct": ( | |
| operating_income / revenue * 100 | |
| if operating_income is not None and revenue else None | |
| ), | |
| "source_url": row.get("source_url") or "", | |
| }) | |
| total = sum(line["revenue"] for line in lines if line["revenue"]) | |
| for line in lines: | |
| line["share_pct"] = (line["revenue"] / total * 100) if total and line["revenue"] else None | |
| return { | |
| "axis": axis, | |
| "period_end": period_end, | |
| "prior_period_end": prior_end, | |
| "total_revenue": total or None, | |
| "lines": lines, | |
| "source_url": next((line["source_url"] for line in lines if line["source_url"]), ""), | |
| } | |
| def products(ticker: str) -> dict: | |
| return breakdown(ticker, PRODUCT_AXIS) | |
| def regions(ticker: str) -> dict: | |
| return breakdown(ticker, SEGMENT_AXIS) | |
| def outliers(view: dict) -> dict: | |
| """The two lines worth a sentence: fastest and slowest year-on-year. | |
| Returned as ``{"fastest": line, "slowest": line}`` with either key absent | |
| when there is no year-on-year to rank by. This is what makes the block | |
| interpretable rather than merely accurate -- a reader should not have to | |
| scan five percentages to find the one that diverges. | |
| """ | |
| ranked = [line for line in view.get("lines") or [] if line.get("yoy_pct") is not None] | |
| if len(ranked) < 2: | |
| return {} | |
| ranked.sort(key=lambda line: line["yoy_pct"]) | |
| return {"slowest": ranked[0], "fastest": ranked[-1]} | |
| def margin_spread(view: dict) -> Optional[dict]: | |
| """The widest gap in segment margin, when the axis carries margins. | |
| Apple's Greater China runs six points below every other region; that gap | |
| is a fact about the business the consolidated margin cannot show. | |
| """ | |
| ranked = [line for line in view.get("lines") or [] if line.get("margin_pct") is not None] | |
| if len(ranked) < 2: | |
| return None | |
| ranked.sort(key=lambda line: line["margin_pct"]) | |
| return { | |
| "lowest": ranked[0], | |
| "highest": ranked[-1], | |
| "spread_pct": ranked[-1]["margin_pct"] - ranked[0]["margin_pct"], | |
| } | |
| def _money(value: float) -> str: | |
| for cutoff, unit in ((1e12, "T"), (1e9, "B"), (1e6, "M")): | |
| if abs(value) >= cutoff: | |
| return f"${value / cutoff:,.2f}{unit}" | |
| return f"${value:,.0f}" | |
| def digest_lines(ticker: str) -> list[dict]: | |
| """Segment results as sentences the narrative digest can cite. | |
| These are the company's own tagged figures, so they enter the article's | |
| evidence pool on the same footing as a verified filing fact -- and they | |
| carry the product and region granularity nothing else in the brief has. | |
| Every number the sentence states is in the sentence, which is what lets | |
| the article quote it and still pass the number check. | |
| """ | |
| lines: list[dict] = [] | |
| for view, origin in ((products(ticker), "product"), (regions(ticker), "region")): | |
| period_end = view.get("period_end") or "" | |
| for line in view.get("lines") or []: | |
| revenue = line.get("revenue") | |
| if not revenue: | |
| continue | |
| parts = [f"{line['label']} revenue {_money(revenue)} in the quarter ended {period_end}"] | |
| if line.get("yoy_pct") is not None: | |
| parts.append(f"{line['yoy_pct']:+.1f}% year-on-year") | |
| if line.get("share_pct") is not None: | |
| parts.append(f"{line['share_pct']:.0f}% of total revenue") | |
| if line.get("margin_pct") is not None: | |
| parts.append(f"segment operating margin {line['margin_pct']:.1f}%") | |
| lines.append({ | |
| "text": ", ".join(parts) + ".", | |
| "origin": f"{origin} mix", | |
| "source_url": line.get("source_url") or view.get("source_url") or None, | |
| }) | |
| return lines | |