"""合成SEC式期间事实生成季度面板;保留原期间、单位、申报与来源。""" import json from decimal import Decimal as D from collections import defaultdict quarters = [("2025-01-01", "2025-03-31"), ("2025-04-01", "2025-06-30"), ("2025-07-01", "2025-09-30"), ("2025-10-01", "2025-12-31")] def fact(symbol, start, end, millions, filed, accession, form="10-Q", unit="USD", tag="Revenues"): return {"symbol": symbol, "start": start, "end": end, "val": str(D(millions)*1000000), "filed": filed, "accession": accession, "form": form, "unit": unit, "tag": tag} facts = [fact("AAA", "2025-01-01", "2025-03-31", "100", "2025-05-01", "a-q1"), fact("AAA", "2025-01-01", "2025-03-31", "100", "2025-08-01", "a-q2-repeat"), fact("AAA", "2025-01-01", "2025-06-30", "260", "2025-08-01", "a-q2"), fact("AAA", "2025-04-01", "2025-06-30", "160", "2025-08-01", "a-q2-direct"), fact("AAA", "2025-01-01", "2025-09-30", "445", "2025-11-01", "a-q3-original"), fact("AAA", "2025-01-01", "2025-09-30", "450", "2025-11-02", "a-q3-amend", "10-Q/A"), fact("AAA", "2025-07-01", "2025-09-30", "190", "2025-11-02", "a-q3-direct", "10-Q/A"), fact("AAA", "2025-01-01", "2025-12-31", "700", "2026-02-01", "a-annual", "10-K"), fact("BBB", "2025-01-01", "2025-03-31", "50", "2025-05-02", "b-q1"), fact("BBB", "2025-01-01", "2025-09-30", "180", "2025-11-03", "b-q3"), fact("AAA", "2025-01-01", "2025-06-30", "999", "2025-08-01", "wrong-unit", unit="EUR")] def build(records, cutoff): selected, excluded, grouped = {}, [], defaultdict(list) for row in records: if row["filed"] > cutoff: continue if row["unit"] != "USD" or row["tag"] != "Revenues" or row["form"] not in {"10-Q", "10-Q/A", "10-K", "10-K/A"}: excluded.append(row["accession"]); continue if not D(row["val"]).is_finite(): raise ValueError("nonfinite_fact_value:"+row["accession"]) key = (row["symbol"], row["start"], row["end"]) grouped[key].append(row) for key, values in grouped.items(): newest = max(r["filed"] for r in values) candidates = [r for r in values if r["filed"] == newest] if len({r["val"] for r in candidates}) != 1: raise ValueError("same_date_conflict") selected[key] = min(candidates, key=lambda r: r["accession"]) output = [] for symbol in sorted({r["symbol"] for r in records}): for q, (start, end) in enumerate(quarters, 1): direct = selected.get((symbol, start, end)) current = selected.get((symbol, "2025-01-01", end)) previous = selected.get((symbol, "2025-01-01", quarters[q-2][1])) if q > 1 else None derivable = current is not None and (q == 1 or previous is not None) derived = D(current["val"])-(D(previous["val"]) if previous else 0) if derivable else None if direct is not None and derived is not None and D(direct["val"]) != derived: raise ValueError("direct_ytd_conflict") sources = [direct] if direct is not None else [r for r in [current, previous] if r] if derivable else [] value = D(direct["val"]) if direct else derived output.append({"symbol": symbol, "quarter": q, "start": start, "end": end, "value_usd_millions": str(value/1000000) if value is not None else None, "method": "direct" if direct else "ytd_difference" if derivable else "unavailable", "available_on": max(r["filed"] for r in sources) if sources else None, "source_facts": sources}) return output, excluded panel, excluded = build(facts, "2026-03-01") assert [r["value_usd_millions"] for r in panel if r["symbol"] == "AAA"] == ["100", "160", "190", "250"] assert panel[3]["available_on"] == "2026-02-01" and panel[3]["method"] == "ytd_difference" assert panel[6]["value_usd_millions"] is None old, _ = build(facts, "2025-11-01") assert old[2]["value_usd_millions"] == "185" and old[3]["value_usd_millions"] is None assert excluded == ["wrong-unit"] assert build(list(reversed(facts)), "2026-03-01")[0] == panel try: build(facts + [fact("AAA", "2025-01-01", "2025-06-30", "261", "2025-08-02", "conflicting-ytd")], "2026-03-01") except ValueError as exc: assert str(exc) == "direct_ytd_conflict" else: raise AssertionError("单季与累计差冲突必须阻断") assert D("135217")-D("69632") == D("65585") for token in ["NaN", "Infinity", "-Infinity"]: try: build([{**facts[0], "val": token, "accession": "nonfinite-probe"}], "2026-03-01") except ValueError as exc: assert str(exc) == "nonfinite_fact_value:nonfinite-probe" else: raise AssertionError("非有限财务事实必须阻断") print(json.dumps({"panel": panel, "excluded": excluded, "prior_q3": old[2]["value_usd_millions"], "original_records_retained": len(facts), "manual_filing_check": {"issuer": "Microsoft", "period_end": "2024-12-31", "quarter_revenue_usd_millions": "69632", "six_month_revenue_usd_millions": "135217", "accession": "0000950170-25-010491", "filed": "2025-01-29", "source_check": "separate authoring verification; this script does not fetch"}}, sort_keys=True))