#!/usr/bin/env python3 """Render SM-024 (draft) from the analysis, the tables and the overdue check. Every number in the prose comes from those files; the hard-coded facts are asserted against them at the end. python3 make_page.py data/analysis_2024on.json data/tables_2026-10-22.json site/measurements/sm-024.html""" import json, sys, os, html, collections, datetime as dt AN, TB, OUT = sys.argv[1:4] HERE = os.path.dirname(os.path.abspath(__file__)); P = os.path.join(HERE, "page_parts"); DATA = os.path.join(HERE, "data") A = json.load(open(AN)); T = json.load(open(TB)); S = A["scored"]; AL = A["all"]; OV = T["overdue"]; PF = T["possibly_final"] ACT = json.load(open(os.path.join(DATA, "api_active_2026-10-22.json")))["REGISTRANTS_ACTIVE"]["ROW"]; NACT = len(ACT) TER = {str(r["Registration_Number"]): r for r in json.load(open(os.path.join(DATA, "api_terminated_2026-10-22.json")))["REGISTRANTS_TERMINATED"]["ROW"]} SCAN = [json.loads(l) for l in open(os.path.join(DATA, "scan_2024on.jsonl"))] BOTH = [d for d in SCAN if d.get("received") and d.get("date_stamped")] _gap = lambda d: abs((dt.datetime.strptime(d["received"].split()[0], "%m/%d/%Y") - dt.datetime.strptime(d["date_stamped"], "%m/%d/%Y")).days) AGREE = sum(1 for d in BOTH if _gap(d) == 0); DAYOFF = sum(1 for d in BOTH if _gap(d) == 1) READ = sum(1 for d in SCAN if not d.get("error")) esc = html.escape n = lambda x: f"{x:,}" pct = lambda a, b: f"{100 * a / b:.1f}%" MON = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] D = lambda s: f"{int(s[8:10])} {MON[int(s[5:7]) - 1]} {s[:4]}" YN = A["by_year_filed"] _dep = os.path.expanduser("~/second-measurements/zenodo/sm-024/deposit.json") DOI = json.load(open(_dep))["metadata"]["prereserve_doi"]["doi"] if os.path.exists(_dep) else "pending" regs_late = A["registrants_late_once"]; regs = A["registrants"] MW = max(S, key=lambda x: x["days_late"]) L = [x for x in S if x["late"]]; WITHIN30 = sum(1 for x in L if x["days_late"] <= 30) def wkend(x): due = dt.date.fromisoformat(x["period_end"]) + dt.timedelta(30); return x["days_late"] <= 2 and due.weekday() >= 5 and (due + dt.timedelta(x["days_late"])).weekday() == 0 WEEKEND = sum(1 for x in L if wkend(x)) EARLY = [x for x in S if x["days_after_period"] < 0] def tdays(x): r = TER.get(x["reg"]); return None if not r else (dt.datetime.strptime(r["Termination_Date"], "%m/%d/%Y").date() - dt.date.fromisoformat(x["filed"])).days E_TER = sum(1 for x in EARLY if x["reg"] in TER); E_T90 = sum(1 for x in EARLY if tdays(x) is not None and -30 <= tdays(x) <= 90) PAIRS = OK6 = 0 _by = collections.defaultdict(list) for x in S: _by[x["reg"]].append(dt.date.fromisoformat(x["period_end"])) for ps in _by.values(): ps.sort() for a, b in zip(ps, ps[1:]): PAIRS += 1; OK6 += ((b.year - a.year) * 12 + b.month - a.month) == 6 BEH, NONE, NEVER = T["overdue_behind"], T["overdue_none_since_2024"], T["overdue_none_ever"] import calendar _eom6 = lambda d: (lambda y, m: dt.date(y, m, calendar.monthrange(y, m)[1]))(d.year + (d.month + 5) // 12, (d.month + 5) % 12 + 1) _allreg = {str(r["Registration_Number"]): r["Registration_Date"] for r in ACT} | {k: v["Registration_Date"] for k, v in TER.items()} _first = {} for x in S: if x["reg"] not in _first or x["period_end"] < _first[x["reg"]]: _first[x["reg"]] = x["period_end"] _new = [(n, dt.datetime.strptime(_allreg[n], "%m/%d/%Y").date(), pe) for n, pe in _first.items() if n in _allreg and dt.datetime.strptime(_allreg[n], "%m/%d/%Y").date() >= dt.date(2024, 1, 1)] RULE_OK, RULE_N = sum(1 for n, rd, pe in _new if str(_eom6(rd)) == pe), len(_new) E_T90B = sum(1 for x in EARLY if tdays(x) is not None and abs(tdays(x)) <= 90) PDV = next(x for x in S if x["url"].endswith("6997-Supplemental-Statement-20250930-10.pdf")) OV1Y, OV6M = T["overdue_over_year"], T["overdue_over_6m"] miss = collections.Counter(min(r["periods_missed"], 6) for r in OV) HK = next(r for r in OV if r["reg"] == "3301"); PP = next(r for r in OV if r["reg"] == "6377"); BP = next(r for r in OV if r["reg"] == "7089") TOP = T["top_late"]; cut5 = sum(1 for r in T["all_late_registrants"] if r["late"] == TOP[-1]["late"]); shown5 = sum(1 for r in TOP if r["late"] == TOP[-1]["late"]) title = (f"Foreign agents must report to the Justice Department every six months, within 30 days. Since January 2024, " f"{n(AL['late'])} of {n(AL['statements'])} reports came in late, and {len(OV)} registered agents have one overdue today") desc = (f"Anyone registered under the Foreign Agents Registration Act – the lobbyists, PR firms, law firms and government offices that work in the US for a foreign government, party or company – " f"must file a supplemental statement every six months, within 30 days of the period’s end. The Justice Department lists {n(A['read'])} supplemental statements filed since 1 January 2024; we read the first page of " f"the {n(READ)} that download, for the period each covers and the FARA Unit’s receipt stamp. {n(AL['late'])} of {n(AL['statements'])} arrived after the deadline ({pct(AL['late'], AL['statements'])}). " f"{regs_late} of the {regs} agents that filed in that time were late at least once ({pct(regs_late, regs)}). Of the {NACT} agents on the Department’s active list on 22 October, {len(OV)} have a statement past due and none on file.") bk = "".join(f"
{desc}
Disclosure: Markovian Protocol holds no financial position in any organisation named here, was paid by no one for this work, and showed it to no one before publication. How we work.
Under the Act, an agent of a foreign principal is a person who, for a foreign government, political party, company or person, engages within the United States in political activities, acts within the United States as a public relations counsel, publicity agent or political consultant, solicits, collects, disburses or dispenses money or other things of value within the United States, or represents the principal before a US agency or official (22 U.S.C. 611(c)).1 Exemptions include diplomatic and consular officers, private and nonpolitical trade and commerce, bona fide religious, scholastic, academic, scientific and artistic pursuits, legal representation before courts and agencies, and, where the principal is not a foreign government or political party, lobbyists who register under the Lobbying Disclosure Act instead (22 U.S.C. 613). Those registered are lobbying, PR and law firms, consultants and individuals, and foreign government offices such as tourism boards and trade agencies. The six-monthly statement is where they report what they did and what they were paid; it is how the public learns who is paid by whom to influence US policy.
| Received | Statements | Share |
|---|
Days past the due date, which is 30 days after the end of the six-month period printed on the statement. {len(EARLY)} statements were received before their period ended and count as on time; {E_TER} of them come from agents whose registration is now terminated ({E_T90B} terminating within three months of the statement, before or after it), the rest from agents still active.
| Year received | Statements | Late | Share late | Over 30 days late | Median days late |
|---|
The longest was Meliora Strategy’s statement for the six months to {D(MW['period_end'])}, received {D(MW['filed'])}, {n(MW['days_late'])} days past due (statement). Some of the longest waits are agents filing a run of statements for periods years back within days of each other: Carman Marc Miller filed eight statements, for periods ending April 2021 to October 2024, between 30 December 2024 and 5 January 2025, and Cogent Law Group filed three for periods in 2020 and 2021 in June and July 2024. Extensions do not appear in the Unit’s filings data (see below).
An agent’s periods end every six months; the first ends at the end of the sixth month after the month it registered, which holds for {RULE_OK} of the {RULE_N} agents registered since January 2024 whose statements we read. Where an agent filed nothing in our window, its last statement before 2024 fixes its periods, and counts if it covers a 2024 period. For each of the {NACT} agents on the FARA Unit’s active list on 22 October we laid out its periods ending on or after 1 January 2024 whose due date had passed, and looked for a statement covering each. {len(OV)} have at least one period with no statement on file, confirmed against the Unit’s own filings page, which we saved for each. Counting only periods ending since January 2024, {miss[1]} are missing one statement, {miss[2]} two, {miss[3]} three, {miss[4]} four, {miss[5]} five and {miss[6]} six. Days overdue run from the due date of the earliest missing statement in that window to 22 October; {OV1Y} are more than a year overdue, {OV6M} more than six months.
{NONE} of them have filed no supplemental statement since January 2024, and {NEVER} have never filed one: many registered once and filed nothing after. Becker & Poliakoff (#7089), registered {D(BP['registered'])}, has filed amendments as recently as October 2025 but no supplemental statement.
| Agent | Countries represented | Last period covered | Earliest missing statement was due | Days overdue | Statements missing since 2024 | Check |
|---|
Not counted: {len(PF)} agents whose last statement, in our window or before it, was received before the period it covers had ended, which can mean a final statement filed while the registration stays open: {pf}. Also not counted: KOTRA (#1619), whose filings page lists a May 2026 statement that returns “not found”. The Unit may grant an extension asked for in writing, with good cause, before the due date; extensions do not appear in its filings data, so some agents above may have one. An agent that has stopped working for its principals still owes a final statement within 30 days of the end of the agency, and stays on the active list until it files one.
| Agent | Countries represented since 2024 | Late statements | Median days late | Longest (statement) |
|---|
The twenty agents with the most late statements since January 2024; {cut5} agents have {TOP[-1]['late']} late statements and the {shown5} with the longest single wait are shown. {T['every_statement_late']} agents filed every statement late, {T['every_statement_late_single']} of them having filed only one. One statement covers all of an agent’s principals, so lateness belongs to the agent, not to the country. Extensions do not appear in the Unit’s filings data, so some of these late statements may have had one. Every agent with a late statement is in tables_2026-10-22.json, and every agent with a foreign principal active since January 2024, by country, in the lookup.
A late statement is a disclosure the public got late, not a hidden agent: every agent here registered, and {n(WITHIN30)} of the {n(len(L))} late statements, about half, came within a month of the deadline. What is missing while a statement is overdue is the record of what the agent did and what it was paid in that period. The Department says late filings “may be considered a deficiency in registration”, that it “could also seek an injunction”, and that it can pursue criminal charges “if the failure to file is willful”.2 The Inspector General reported in 2016 that FARA Unit staff “typically do not seek to impose any penalties such as late fees” for late initial registrations, because they “would rather see a complete submission sent in late than an incomplete one sent in on time”. In its response, printed in the same report, the National Security Division wrote that “in a number of filings considered late in the Report, the FARA Unit provided an extension to the registrant, which mitigated the lateness. Under FARA’s current statutory and regulatory authorities, there is no penalty for lateness”, and that “imposing fines for late filing could act as a disincentive to registration and result in less transparency”.4
The agents close it by filing: each overdue row clears when a statement for the missing period appears on the Unit’s page, or when the agent files its final statement and terminates. The FARA Unit closes the rest, by saying which of these agents have extensions and what it does when a statement is months overdue. A recheck reads the Unit’s bulk file every month and marks each overdue row the day its statement arrives.
| 22 Oct 2026 | Measured and published. Nobody saw the page before. |
| 22 Oct 2026 | The three questions above sent to the FARA Unit at fara.public@usdoj.gov, the address on its FAQ page, with the overall numbers and a link to this page. Answers are printed here as written. |
curl -O https://efile.fara.gov/bulk/zip/FARA_All_RegistrantDocs.csv.zip # and FARA_All_Registrants, FARA_All_ForeignPrincipals swiftc -O -o ocr_page1 ocr_page1.swift # Apple Vision text recognition, macOS python3 scan.py FARA_All_RegistrantDocs.csv 2024-01-01 scan.jsonl ocr_text 4 python3 analyze.py scan.jsonl analysis.json python3 overdue.py analysis.json api_active.json FARA_All_RegistrantDocs.csv 2026-10-22 overdue.json python3 verify_overdue.py overdue.json live/ overdue_verified.json python3 pre2024.py overdue_verified.json FARA_All_RegistrantDocs.csv pre2024.jsonl ocr_text_pre2024 # each candidate's last statement before 2024 python3 overdue.py analysis.json api_active.json FARA_All_RegistrantDocs.csv 2026-10-22 overdue.json pre2024.jsonl python3 verify_overdue.py overdue.json live/ overdue_verified.json python3 tables.py analysis.json overdue_verified.json FARA_All_ForeignPrincipals.csv tables.json python3 build_lookup.py FARA_All_ForeignPrincipals.csv analysis.json lookup.json
Every statement read, with its hash, period, receipt stamp and the page-1 text: exhibits. Scripts in sm-024/.