Spaces:
Running
Running
| """The opening block of every report: three plain sentences and a world map. | |
| Kalam's brief, twice: "open the page with high level analysis and summary plus | |
| a world map of relevant regions highlighted with notes on hover — then when you | |
| scroll down it all slots in for the reader." The hover half became a tap: | |
| the team reads this on phones. | |
| **What is here and what is not.** The cartography is not new. The country | |
| outlines are the same vendored Natural Earth 110m paths | |
| (`marathon/static/world_paths.json`, keyed ISO-2) that `marathon/trackmap.py` | |
| already renders a standalone map from. This module reuses the file and the | |
| verdict bands, and adapts them into the report. | |
| **Why the interaction is anchors and not script.** Scripts injected into a | |
| Gradio HTML component do not run — the same constraint that made `render.py` | |
| resolve Deezer previews server-side. So every tappable thing here is an | |
| `<a href="#market-XX">`, which is the browser's own behaviour and needs no | |
| JavaScript at all. `render.py` gives each market card a matching `id`, so a | |
| tap lands on the card, and the note the reader wants is the card. | |
| Two of those live side by side on purpose: | |
| - the countries on the map, each shown market's outline wrapped in an `<a>`; | |
| - a chip row directly under the map, one chip per market card. | |
| **Measured in the real thing, Gradio 6.19.0 at a 375px viewport.** The block | |
| is in the ordinary document — no shadow DOM, no iframe — and both kinds of | |
| anchor jump correctly: `scrollTop` 594 → 3952 with the card landing at the | |
| 14px `scroll-margin-top`. But Gradio's own chrome takes the map down to | |
| 261px wide, which puts Kenya's centre 4px from Uganda's and 7px from | |
| Tanzania's. A marker dot big enough to see there is a blob over the exact | |
| cluster that matters, and an invisible tap target big enough to hit is a | |
| coin flip between three markets — a tap that opens the wrong card is a bug, | |
| not an affordance. So there are no markers. The countries carry the colour, | |
| and the chips are the interaction: 138 × 34px each, in rank order. | |
| `<title>` on each coloured country gives the native tooltip where a pointer | |
| exists. Touch browsers do not show it on long-press, so nothing load-bearing | |
| is written there — it repeats what the card says. | |
| """ | |
| from __future__ import annotations | |
| import html | |
| import json | |
| import re | |
| from pathlib import Path | |
| import livematch | |
| ESC = html.escape | |
| # Above this cosine, the nearest charting record IS the track being analysed. | |
| # The re-embed parity checks land at 1.0000000 for the same audio through two | |
| # code paths, and a released track measured from its own Deezer preview meets | |
| # its corpus row at the same place. 0.995 leaves room for the preview being a | |
| # different master or encode of the record. It happened the first time the | |
| # archive was seeded: Joshua Baraka's "What Do I Know" read 100.0% in Uganda | |
| # because it is charting in Uganda. Without a line saying so that reads as a | |
| # spectacular fit rather than as the track recognising its own reflection. | |
| SAME_RECORDING = 0.995 | |
| # Short band words. The long form with the cut-offs lives in render.CAPTION and | |
| # is printed next to every chip; these are the same three verdicts inside a | |
| # sentence. | |
| BAND_WORD = {"place": "a direct fit", | |
| "derive": "bridgeable with an edit or remix", | |
| "pass": "no real connection"} | |
| # --- the flat field -------------------------------------------------------- | |
| # | |
| # A ranked list always has a top row, and a top row always looks like a | |
| # finding. Sometimes it is not one. `reports/AU_CA_CHART_CHECK.md` traced | |
| # Joshua Baraka's "What Do I Know" reading Australia, Canada and New Zealand | |
| # as its three best markets at 84%: the charts were right, the audio was | |
| # right, and the reading was still empty. The track scores 0.76–0.84 in every | |
| # one of 88 markets (median 0.8088, sd 0.0204), clears the place line nowhere, | |
| # and those three markets tie to the fourth decimal because they are the only | |
| # three carrying one Punjabi record — Parmish Verma — "Dekhi Ja" — that | |
| # happens to be its nearest texture neighbour. Drop that single sound and | |
| # Australia falls below the global median. | |
| # | |
| # So the shape of the field is read before anything is said about a market. | |
| # When the field is flat the opening says so, the map stops colouring anything | |
| # as a choice, and the ranking below is introduced as the weak signal it is. | |
| FLAT_SPREAD = 0.05 | |
| HERE = Path(__file__).resolve().parent | |
| # The staged Space is flat — `livedeploy.stage` copies the vendored paths in | |
| # next to this file, the same way it copies `livematch.py`. The dev checkout | |
| # reads the one original in the package, so there is no second copy to drift. | |
| PATH_CANDIDATES = (HERE / "world_paths.json", | |
| HERE.parent / "marathon" / "static" / "world_paths.json") | |
| _world: dict | None = None | |
| def world() -> dict: | |
| """The vendored outlines, loaded once. `{viewBox, countries: {ISO2: {name, d}}}`.""" | |
| global _world | |
| if _world is None: | |
| for p in PATH_CANDIDATES: | |
| if p.exists(): | |
| _world = json.loads(p.read_text()) | |
| break | |
| else: | |
| raise FileNotFoundError( | |
| "world_paths.json not found in " + | |
| " or ".join(str(p) for p in PATH_CANDIDATES)) | |
| return _world | |
| # --- region families ------------------------------------------------------- | |
| # | |
| # The first sentence of the report answers "which part of the world" before it | |
| # answers "which country", because a green cluster across Kenya, Uganda and | |
| # Tanzania is one finding and three numbers are not. Families are the coarse | |
| # groupings a label actually plans against, not UN statistical regions: | |
| # Latin America is one family rather than four, because that is how the | |
| # markets are worked. | |
| _FAMILY_MEMBERS = { | |
| "East Africa": "KE UG TZ ET RW BI SO DJ ER SS MU MG KM SC", | |
| "West Africa": "NG GH CI SN ML BF BJ TG GN LR SL GM NE MR CV GW", | |
| "Central Africa": "CM CD CG GA TD CF GQ AO ST", | |
| "Southern Africa": "ZA ZW MZ BW NA LS SZ ZM MW", | |
| "North Africa": "EG MA DZ TN LY SD", | |
| "the Middle East": "AE SA IL TR QA KW OM BH JO LB IQ IR YE PS SY", | |
| "South Asia": "IN PK BD LK NP BT MV AF", | |
| "Southeast Asia": "ID MY PH TH VN SG KH MM LA BN TL", | |
| "East Asia": "JP KR TW HK CN MN KP", | |
| "Western Europe": "GB IE FR DE NL BE LU AT CH", | |
| "Northern Europe": "SE NO DK FI IS EE LV LT", | |
| "Southern Europe": "ES PT IT GR CY MT HR SI RS AL BA ME MK XK", | |
| "Central and Eastern Europe": "PL CZ SK HU RO BG UA RU BY MD", | |
| "North America": "US CA", | |
| "Latin America": ("MX GT HN SV NI CR PA DO JM CU TT PR HT BR AR CL CO PE " | |
| "VE EC BO PY UY BZ GY SR BS BB"), | |
| "Oceania": "AU NZ FJ PG NC VU SB WS TO", | |
| } | |
| FAMILY = {iso: fam for fam, isos in _FAMILY_MEMBERS.items() | |
| for iso in isos.split()} | |
| # The coarse grouping above the family. Read against the real YO YO result, | |
| # the family alone undersold the finding: Kenya, Tanzania and Uganda are three | |
| # of the eight closest markets, but Portugal sits at number three and Senegal | |
| # and Nigeria at five and seven, so "2 of its 5 nearest markets are in East | |
| # Africa" reads as a weak claim about a strong one. Six of the eight are in | |
| # Africa. The continent is the headline and the family is the detail inside | |
| # it. | |
| _CONTINENT_OF = { | |
| "East Africa": "Africa", "West Africa": "Africa", | |
| "Central Africa": "Africa", "Southern Africa": "Africa", | |
| "North Africa": "Africa", | |
| "Western Europe": "Europe", "Northern Europe": "Europe", | |
| "Southern Europe": "Europe", "Central and Eastern Europe": "Europe", | |
| "North America": "the Americas", "Latin America": "the Americas", | |
| "South Asia": "Asia", "Southeast Asia": "Asia", "East Asia": "Asia", | |
| "the Middle East": "the Middle East", "Oceania": "Oceania", | |
| } | |
| CONTINENT = {iso: _CONTINENT_OF[fam] for iso, fam in FAMILY.items()} | |
| # --- geometry -------------------------------------------------------------- | |
| _NUM = re.compile(r"-?\d+(?:\.\d+)?") | |
| def centre(d: str) -> tuple[float, float] | None: | |
| """A point to hang a marker on: the vertex mean of the country's largest | |
| subpath. | |
| Largest, not all, because a mean over every subpath drags the United States | |
| into the Pacific and Norway into the Atlantic — Alaska, Hawaii and a few | |
| hundred islands outvote the mainland. Vertex mean rather than bounding-box | |
| centre because a long thin country (Chile, Norway) has a box centre that | |
| sits well off it. | |
| """ | |
| best_n, best = 0, None | |
| for sub in d.split("M"): | |
| nums = [float(n) for n in _NUM.findall(sub)] | |
| xs, ys = nums[0::2], nums[1::2] | |
| if len(xs) < 3 or len(xs) != len(ys): | |
| continue | |
| if len(xs) > best_n: | |
| best_n = len(xs) | |
| best = (sum(xs) / len(xs), sum(ys) / len(ys)) | |
| return best | |
| # --- the summary sentences ------------------------------------------------- | |
| def _pct(x: float) -> str: | |
| return livematch.pct(x) | |
| # Every market whose outline the 110m country set does not carry. The corpus | |
| # takes its market names out of that same file, so these came through with | |
| # the ISO code as the name and the app printed them that way — on the market | |
| # cards, on the chips, and inside the pitch prose a label manager pastes into | |
| # a delivery form: "records charting in Czechia and SG" (23 Aug audit). Four | |
| # of them are in this week's charts; the rest are here so the next small | |
| # market to chart does not repeat it. | |
| NO_OUTLINE = { | |
| "AD": "Andorra", "AG": "Antigua and Barbuda", "AI": "Anguilla", | |
| "AS": "American Samoa", "AW": "Aruba", "BB": "Barbados", | |
| "BH": "Bahrain", "BM": "Bermuda", "CV": "Cape Verde", "CW": "Curaçao", | |
| "DM": "Dominica", "FM": "Micronesia", "FO": "the Faroe Islands", | |
| "GD": "Grenada", "GI": "Gibraltar", "GU": "Guam", "HK": "Hong Kong", | |
| "JE": "Jersey", "KI": "Kiribati", "KM": "Comoros", | |
| "KN": "St Kitts and Nevis", "KY": "the Cayman Islands", "LC": "St Lucia", | |
| "LI": "Liechtenstein", "MC": "Monaco", "MH": "the Marshall Islands", | |
| "MO": "Macao", "MT": "Malta", "MU": "Mauritius", "MV": "the Maldives", | |
| "NR": "Nauru", "PW": "Palau", "SC": "Seychelles", "SG": "Singapore", | |
| "SM": "San Marino", "ST": "São Tomé and Príncipe", | |
| "SX": "Sint Maarten", "TO": "Tonga", "TV": "Tuvalu", | |
| "VC": "St Vincent and the Grenadines", "VG": "the British Virgin Islands", | |
| "VI": "the US Virgin Islands", "WS": "Samoa", | |
| } | |
| # Natural Earth's formal names are not what anyone says, and a chip row on a | |
| # 375px phone cannot spend three words on one country: "United States of | |
| # America" pushed the mix off the end of its own chip. Only the names that | |
| # actually differ are listed; everything else comes through unchanged. | |
| # | |
| # This lived in `weekly.py` and only the sheet used it, so the report and the | |
| # map — one tap away — said "United States of America" and "Dominican Rep." | |
| # where the sheet said "United States" and "Dominican Republic" (23 Aug | |
| # audit). One table, read by both. | |
| SHORT_NAME = { | |
| "GB": "Britain", "US": "United States", "KR": "South Korea", | |
| "RU": "Russia", "AE": "the UAE", "CD": "DR Congo", "CI": "Côte d'Ivoire", | |
| "TZ": "Tanzania", "LA": "Laos", "SY": "Syria", "VE": "Venezuela", | |
| "BO": "Bolivia", "IR": "Iran", "MD": "Moldova", "MK": "North Macedonia", | |
| "CZ": "Czechia", "VA": "the Vatican", "DO": "Dominican Republic", | |
| "BA": "Bosnia and Herzegovina", "SS": "South Sudan", | |
| "CF": "Central African Republic", "GQ": "Equatorial Guinea", | |
| "SZ": "Eswatini", "TL": "Timor-Leste", "GW": "Guinea-Bissau", | |
| } | |
| # The same countries as they read inside a sentence: "at #1 in the United | |
| # States". A chip cannot afford the article and a sentence cannot do without | |
| # it. | |
| IN_MARKET = {"US": "the United States", "PH": "the Philippines", | |
| "NL": "the Netherlands", "AE": "the UAE", | |
| "CD": "the DRC", "DO": "the Dominican Republic", | |
| "GB": "Britain", "CZ": "Czechia", | |
| "CF": "the Central African Republic", "GM": "the Gambia", | |
| "BS": "the Bahamas", "MV": "the Maldives"} | |
| def in_market(iso: str, *names: str) -> str: | |
| """The market's name as it reads after "in" or "into". | |
| "charting in United States" is what a table of names gives you and what | |
| a sentence cannot use. Everything not in the table reads the same in | |
| both places and comes back through `market_name`. | |
| """ | |
| return IN_MARKET.get((iso or "").strip().upper()) or market_name(iso, | |
| *names) | |
| def market_name(iso: str, *names: str) -> str: | |
| """A market's name in words. | |
| Takes whatever names the caller holds — the corpus's, the map's — in the | |
| order it would have used them, and treats a name that is only the ISO | |
| code as no name at all, because that is exactly what the markets above | |
| carry. A code with no entry still comes back as itself: a code says less | |
| than a name, and a wrong name says worse than nothing. | |
| """ | |
| code = (iso or "").strip().upper() | |
| if code in SHORT_NAME: | |
| return SHORT_NAME[code] | |
| for n in names: | |
| n = (n or "").strip() | |
| if n and n.upper() != code: | |
| return n | |
| return NO_OUTLINE.get(code) or (iso or "") | |
| def name_regions(regions: dict | None) -> dict | None: | |
| """Give every market in a result a name a reader can say. | |
| Called where a result enters a renderer rather than at each of the dozen | |
| places that print a market name, so a new one cannot be added without it. | |
| It fixes the reading in place and is safe to run twice: a market that | |
| already has a name keeps it. | |
| """ | |
| for iso, r in (regions or {}).items(): | |
| if isinstance(r, dict): | |
| r["name"] = market_name(iso, r.get("name")) | |
| return regions | |
| def _name(regions: dict, iso: str) -> str: | |
| return market_name(iso, regions.get(iso, {}).get("name")) | |
| def name_of(region: dict, country: dict) -> str: | |
| """The corpus name when it has a real one, the Natural Earth name when the | |
| corpus only carries the ISO code (SG, HK, MT, CV come through that way).""" | |
| return region.get("name") or (country or {}).get("name") or "" | |
| def _plural(n: int, unit: str) -> str: | |
| """"1 market", "5 markets". A count that reads "1 markets" tells the | |
| reader the page cannot count (23 Aug audit).""" | |
| return f'{n} {unit if n == 1 else unit + "s"}' | |
| def _join(names: list[str]) -> str: | |
| if len(names) == 1: | |
| return names[0] | |
| return ", ".join(names[:-1]) + " and " + names[-1] | |
| def field(res: dict) -> dict: | |
| """How much the market ranking is worth before any market is named. | |
| Returns `{flat, n, lo, hi, median, shared}`. `flat` is the case above: | |
| nothing clears the place line, and the strongest market sits within | |
| `FLAT_SPREAD` of the median of every market measured. `shared` is the one | |
| record that two or more of the leading markets both point at, when they | |
| do — the fact that turns "three markets" back into "one sound". | |
| The spread is taken over every region in the corpus, not the eight shown, | |
| because the eight shown are the top of the very distribution being | |
| questioned. | |
| """ | |
| regions = res.get("regions") or {} | |
| # The track's own chart entry is not a market reading, and leaving it in | |
| # hides exactly the case this function exists for: "What Do I Know" reads | |
| # 100% in Uganda because it charts in Uganda, which puts the maximum above | |
| # the place line and makes a dead-flat field look like a strong one. | |
| bests = sorted(float(r["best"]) for r in regions.values() | |
| if float(r["best"]) < SAME_RECORDING) | |
| if not bests: | |
| return {"flat": False, "n": 0, "lo": 0.0, "hi": 0.0, "median": 0.0, | |
| "shared": None, "shared_markets": []} | |
| mid = len(bests) // 2 | |
| median = bests[mid] if len(bests) % 2 else (bests[mid - 1] + bests[mid]) / 2 | |
| hi = bests[-1] | |
| shown = [i for i in (res.get("shown_markets") or []) if i in regions | |
| and regions[i]["best"] < SAME_RECORDING] | |
| names = [] | |
| for iso in shown[:3]: | |
| top = (regions[iso].get("top") or [None])[0] | |
| names.append((top.get("artist"), top.get("title")) if top else None) | |
| shared = (names[0] if len(names) >= 2 and None not in names | |
| and len(set(names)) == 1 else None) | |
| return {"flat": bool(hi < livematch.PLACE_THRESHOLD | |
| and hi - median <= FLAT_SPREAD), | |
| "n": len(bests), "lo": bests[0], "hi": hi, "median": median, | |
| # How much company the leader has above the place line. On the | |
| # real YO YO upload 44 of 88 markets clear it, because a | |
| # full-length upload scores higher against everything than the | |
| # 30-second previews the corpus is built from. A leader quoted | |
| # with no company count reads as if it were alone up there. | |
| "clearing": sum(1 for b in bests if b >= livematch.PLACE_THRESHOLD), | |
| "shared": shared, "shared_markets": shown[:3] if shared else []} | |
| def measured(res: dict, f: dict | None = None) -> str: | |
| """"87 markets measured" — the count the field is read over. | |
| It is not the number the report opens on. That one is every market in | |
| the week; this one drops the markets the track already charts in, | |
| because a record meeting its own chart entry is not a market reading. | |
| Two counts four lines apart with nothing joining them read as arithmetic | |
| that does not add up (23 Aug audit), so the sentence that names the | |
| self-matched markets says they are left out of both. | |
| """ | |
| f = field(res) if f is None else f | |
| return f'{f["n"]} markets measured' | |
| def summary(res: dict) -> list[str]: | |
| """Two or three plain sentences, generated from the result alone. | |
| Nothing here is a judgement the numbers do not already carry: which family | |
| the markets cluster in, the leading markets with their verdict, and one | |
| fact that changes how the rest of the page should be read — the track | |
| meeting its own chart entry, or nothing clearing the direct-fit line, or | |
| the record the top market's number is measured against. | |
| """ | |
| regions = res.get("regions") or {} | |
| shown = [i for i in (res.get("shown_markets") or []) if i in regions] | |
| if not shown: | |
| return ["No market could be ranked for this track this week."] | |
| self_isos = [i for i in shown if regions[i]["best"] >= SAME_RECORDING] | |
| # The self-match is a fact about the archive, not a market. Everything the | |
| # market sentences say is about markets the track is not already on. | |
| others = [i for i in shown if i not in self_isos] | |
| lead_pool = others or shown | |
| f = field(res) | |
| if f["flat"]: | |
| lead = lead_pool[0] | |
| # Both facts can be true at once, and "What Do I Know" is the record | |
| # where they are: it charts in Uganda AND reads near-level everywhere | |
| # else. Telling only one of them leaves the other looking like a find. | |
| if self_isos: | |
| # All of them. Naming only the first left the others wearing a | |
| # SELF chip with nothing on the page explaining it. | |
| named = _join([f'{ESC(_name(regions, i))} at ' | |
| f'{_pct(regions[i]["best"])}' for i in self_isos]) | |
| many = len(self_isos) > 1 | |
| opener = ( | |
| f'<b>{named} {"read" if many else "reads"} that high because ' | |
| f'the track is already charting ' | |
| f'{"in them" if many else "there"} and is being compared ' | |
| f'against itself.</b> Set that aside and no market stands ' | |
| f'out: this track reads between {_pct(f["lo"])} and ' | |
| f'{_pct(f["hi"])} across the other {f["n"]} markets') | |
| else: | |
| opener = ( | |
| f'<b>No market stands out.</b> This track reads between ' | |
| f'{_pct(f["lo"])} and {_pct(f["hi"])} across all ' | |
| f'{measured(res, f)}') | |
| lines = [ | |
| f'{opener}, with the middle of that at {_pct(f["median"])}. ' | |
| f'Nothing clears the direct-fit line at ' | |
| f'{livematch.PLACE_THRESHOLD:.0%}, and the gap between the ' | |
| f'strongest of them and the middle is ' | |
| f'{(f["hi"] - f["median"]) * 100:.0f} points — so the ordering ' | |
| f'below carries little signal.' | |
| ] | |
| if f["shared"]: | |
| artist, title = f["shared"] | |
| where = _join([_name(regions, i) for i in f["shared_markets"]]) | |
| lines.append( | |
| f'Its top markets — {ESC(where)} — all rest on the same single ' | |
| f'record, <b>{ESC(artist or "—")} — {ESC(title or "—")}</b>. ' | |
| f'They lead the list because they are the markets that record ' | |
| f'charts in, not because the track has a market there.') | |
| else: | |
| lines.append( | |
| f'The strongest reading is {ESC(_name(regions, lead))} at ' | |
| f'{_pct(regions[lead]["best"])}, which is close enough to the ' | |
| f'middle to be a coincidence of which sounds happen to chart ' | |
| f'there this week.') | |
| lines.append( | |
| 'Read the sections below for what the track sounds like. Treat ' | |
| 'the market order as a list of places that have been checked, not ' | |
| 'a ranking to act on.') | |
| return lines | |
| lines: list[str] = [] | |
| # 1. which part of the world — the continent leads, the family qualifies | |
| pool = lead_pool[:8] | |
| conts: dict[str, list[str]] = {} | |
| fams: dict[str, list[str]] = {} | |
| for iso in pool: | |
| if iso in CONTINENT: | |
| conts.setdefault(CONTINENT[iso], []).append(iso) | |
| fams.setdefault(FAMILY[iso], []).append(iso) | |
| # Ties break toward whichever grouping owns the highest-ranked market. | |
| key = lambda kv: (len(kv[1]), -pool.index(kv[1][0])) # noqa: E731 | |
| top_cont = max(conts.items(), key=key) if conts else None | |
| top_fam = max(fams.items(), key=key) if fams else None | |
| if top_cont and len(top_cont[1]) * 2 >= len(pool): | |
| cont, c_isos = top_cont | |
| line = (f'<b>{len(c_isos)} of the {_plural(len(pool), "market")} ' | |
| f'ranked below {"is" if len(c_isos) == 1 else "are"} in ' | |
| f'{ESC(cont)}.</b>') | |
| if top_fam and len(top_fam[1]) >= 2 and top_fam[0] != cont: | |
| line += (f' {len(top_fam[1])} of them are in ' | |
| f'<b>{ESC(top_fam[0])}</b> — ' | |
| f'{_join([_name(regions, i) for i in top_fam[1]])}.') | |
| lines.append(line) | |
| elif top_fam and len(top_fam[1]) >= 2: | |
| fam, isos = top_fam | |
| lines.append( | |
| f'<b>{len(isos)} of the {_plural(len(pool), "market")} ranked ' | |
| f'below {"is" if len(isos) == 1 else "are"} in ' | |
| f'{ESC(fam)}</b> ({_join([_name(regions, i) for i in isos])}). ' | |
| f'The rest are spread across the world rather than clustered ' | |
| f'next to them.') | |
| else: | |
| lines.append( | |
| f'Its closest markets do not cluster: the {len(pool)} ranked ' | |
| f'below sit in {len(conts) or 1} different parts of the world, ' | |
| f'led by ' | |
| f'<b>{ESC(_name(regions, pool[0]))}</b>.') | |
| # 2. the leading markets, with the verdict the number earns | |
| lead = lead_pool[0] | |
| b = livematch.band(regions[lead]["best"]) | |
| rest = lead_pool[1:3] | |
| rest_s = "" | |
| if rest: | |
| rest_s = (' Then ' + _join([f'{_name(regions, i)} at {_pct(regions[i]["best"])}' | |
| for i in rest]) + '.') | |
| lines.append( | |
| f'The strongest market is <b>{ESC(_name(regions, lead))}</b> at ' | |
| f'{_pct(regions[lead]["best"])}, which reads as {BAND_WORD[b]}.{rest_s}') | |
| # 3. how much company the leader has, then the record behind its number. | |
| # | |
| # The company count is the sentence the first pass was missing. On the real | |
| # YO YO upload Kenya reads 95% and 44 of the 88 markets also clear the | |
| # place line, because a full-length upload scores higher against everything | |
| # than the 30-second previews the corpus is built from — the bias the | |
| # caveats at the foot of the report already name. Quoting the leader with | |
| # no company count puts it alone at the top of a page where it is not. | |
| third = [] | |
| if f["clearing"] > 1 and b == "place": | |
| third.append( | |
| f'{f["clearing"]} of the {measured(res, f)} clear that same ' | |
| f'line and the middle of the field is {_pct(f["median"])}, so ' | |
| f'{ESC(_name(regions, lead))} leads by ' | |
| f'{(f["hi"] - f["median"]) * 100:.0f} points rather than standing ' | |
| f'alone.') | |
| elif b != "place": | |
| third.append( | |
| f'No market clears the direct-fit line at ' | |
| f'{livematch.PLACE_THRESHOLD:.0%} this week, so every market below ' | |
| f'is a derive: close enough to build an edit or a remix toward, ' | |
| f'not close enough to place as it stands.') | |
| top = (regions[lead].get("top") or [None])[0] | |
| if top: | |
| third.append( | |
| f'The record {ESC(_name(regions, lead))}\'s number is measured ' | |
| f'against is <b>{ESC(top.get("artist") or "—")} — ' | |
| f'{ESC(top.get("title") or "—")}</b>, the nearest of the ' | |
| f'{regions[lead]["matched"]} charting sounds there that could be ' | |
| f'measured.') | |
| if third: | |
| lines.append(" ".join(third)) | |
| if self_isos: | |
| # Every one of them, not just the first: with three self-matched | |
| # markets the other two wore a SELF chip with nothing on the page | |
| # explaining it (23 Aug audit). This is also where the counts above | |
| # are reconciled — the markets set aside here are the difference | |
| # between "the 5 ranked below" and the week's 88. | |
| named = _join([f'{ESC(_name(regions, i))} at {_pct(regions[i]["best"])}' | |
| for i in self_isos]) | |
| many = len(self_isos) > 1 | |
| lines.append( | |
| f'{named} {"read" if many else "reads"} that high because the ' | |
| f'track is already charting {"in them" if many else "there"} — it ' | |
| f'is being compared against itself, not measured against a ' | |
| f'market. {"They are" if many else "It is"} left out of the ' | |
| f'counts above.') | |
| return lines | |
| # --- the map --------------------------------------------------------------- | |
| def svg(res: dict, linked: list[str] | None = None, | |
| flat: bool = False) -> str: | |
| """The world, coloured by verdict band, with the carded markets linked. | |
| Every market in the corpus is coloured, not only the eight with cards | |
| below: the cluster is the finding, and eight dots on a blank world would | |
| hide it. Only the carded ones become links, because those are the only | |
| ones with somewhere to land. | |
| `flat` drops the whole map to one tone for every charted market. A track | |
| that reads the same everywhere has no map, and painting its top markets | |
| in a confident colour would put a picture of a finding on top of a | |
| paragraph saying there isn't one. | |
| """ | |
| w = world() | |
| regions = res.get("regions") or {} | |
| linked = [i for i in (linked or []) if i in regions] | |
| esc = ESC | |
| shapes = [] | |
| for iso, c in w["countries"].items(): | |
| r = regions.get(iso) | |
| if not r: | |
| shapes.append(f'<path class="cty" d="{c["d"]}"/>') | |
| continue | |
| # A self-match gets its own tone rather than the green its number | |
| # would earn. It is the track's own chart entry, and painting it as | |
| # the strongest market on the map is the one thing the card | |
| # underneath spends a paragraph undoing. | |
| if r["best"] >= SAME_RECORDING: | |
| b = "self" | |
| tip = (f'{name_of(r, c)} — {_pct(r["best"])}, already charting ' | |
| f'here: the track is being compared against itself') | |
| else: | |
| b = livematch.band(r["best"]) | |
| tip = (f'{name_of(r, c)} — {_pct(r["best"])}, near-level with ' | |
| f'every other market' if flat else | |
| f'{name_of(r, c)} — {_pct(r["best"])}, {BAND_WORD[b]}') | |
| if iso not in linked: | |
| tip += " (not in the closest markets below)" | |
| shape = (f'<path class="cty {b} c-{esc(iso)}" d="{c["d"]}">' | |
| f'<title>{esc(tip)}</title></path>') | |
| if iso in linked: | |
| # Both spellings of the SVG link: `href` is SVG2 and is what every | |
| # current browser uses; `xlink:href` is what an old one needs. | |
| shapes.append(f'<a href="#market-{esc(iso)}" ' | |
| f'xlink:href="#market-{esc(iso)}">{shape}</a>') | |
| else: | |
| shapes.append(shape) | |
| return (f'<svg class="map{" flat" if flat else ""}" ' | |
| f'viewBox="{w["viewBox"]}" ' | |
| f'preserveAspectRatio="xMidYMid meet" role="img" ' | |
| f'xmlns:xlink="http://www.w3.org/1999/xlink" ' | |
| f'aria-label="' | |
| + ('World map: every market this track was measured against reads ' | |
| 'near-level, so none is highlighted.' if flat else | |
| 'World map: markets this track sits closest to, coloured by ' | |
| 'verdict.') | |
| + '">' | |
| + "".join(shapes) + '</svg>') | |
| def _chips(res: dict, linked: list[str], flat: bool = False) -> str: | |
| regions = res["regions"] | |
| out = [] | |
| for iso in linked: | |
| r = regions[iso] | |
| if r["best"] >= SAME_RECORDING: | |
| # No percentage on a self chip: the number is the record meeting | |
| # its own chart entry, and 100% on a chip reads as the best | |
| # market in the row. The word is the reading. | |
| b, colour, value = "self", "var(--self)", "SELF" | |
| elif flat: | |
| b, colour, value = "flat", "var(--ink3)", _pct(r["best"]) | |
| else: | |
| b = livematch.band(r["best"]) | |
| colour, value = f"var(--{b})", _pct(r["best"]) | |
| # A checkbox the label flips, not an anchor: an anchor threw the | |
| # reader down the list and left them to scroll back (Kalam, 23 Aug). | |
| # Selecting shows just that market's card directly under the map; | |
| # tapping again clears. Same machinery as the sheet's market filter. | |
| out.append( | |
| f'<input type="checkbox" class="rf" id="rf-{ESC(iso)}">' | |
| f'<label class="mchip {b}" for="rf-{ESC(iso)}">' | |
| f'<span class="d {b}"></span>{ESC(r.get("name") or iso)}' | |
| f'<span class="n" style="color:{colour}">{value}</span></label>') | |
| rules = ['.ml .rf { display:none; }', | |
| '.ml label.mchip { cursor:pointer; }', | |
| # any market selected: every card hides, the map fades … | |
| '.ml:has(.rf:checked) .g-fits .card[id^="market-"]' | |
| ' { display:none; }', | |
| '.ml:has(.rf:checked) svg.map .cty { opacity:.18; }', | |
| # … and the two counts of those cards go with them. "8 closest | |
| # markets" over the one market left showing is a count of what | |
| # is hidden (23 Aug audit). The clip and tag counts stay: a | |
| # market filter does not touch those sections. | |
| '.ml:has(.rf:checked) .g-fits h2 .tag { display:none; }', | |
| '.ml:has(.rf:checked) .rtabs label[for="rt-fits"] .n' | |
| ' { display:none; }'] | |
| for iso in linked: | |
| i = ESC(iso) | |
| # … and each selected market re-opens its own card and keeps its | |
| # colour on the map. | |
| rules += [ | |
| f'.ml:has(#rf-{i}:checked) .g-fits #market-{i}' | |
| f' {{ display:block; }}', | |
| f'.ml:has(#rf-{i}:checked) svg.map .c-{i} {{ opacity:1; }}', | |
| f'.ml:has(#rf-{i}:checked) label[for="rf-{i}"]' | |
| f' {{ border-color:var(--ink); background:var(--inset); }}', | |
| ] | |
| return ('<style>' + "".join(rules) + '</style>' | |
| '<div class="chiprow">' + "".join(out) + '</div>') | |
| SELF_KEY = '<span><i class="self"></i>already charting here</span>' | |
| def legend(flat: bool, has_self: bool) -> str: | |
| keys = (['<span><i class="flat"></i>measured this week, all within a few ' | |
| 'points of each other</span>'] if flat else | |
| ['<span><i class="place"></i>direct fit</span>', | |
| '<span><i class="derive"></i>bridgeable</span>', | |
| '<span><i class="pass"></i>not adjacent</span>']) | |
| if has_self: | |
| keys.append(SELF_KEY) | |
| keys.append('<span><i class="none"></i>no chart data this week</span>') | |
| return '<div class="maplegend">' + "".join(keys) + '</div>' | |
| def opening(res: dict, lead_action: str = "") -> str: | |
| """The whole block that sits above "Where it fits". | |
| `lead_action` is the number-one action, already written — `render.py` | |
| builds it with `_market_action`, the same function that writes the | |
| sentence on the first market card, so there is one author of it. It | |
| closes the summary rather than following the map: `design/PAGE_RUBRIC.md` | |
| §2 puts what a reader can do above what they have to weigh up, and the | |
| map is the weighing. Empty is a real state and gets no empty room — a | |
| self-match and a flat field both arrive here as "". | |
| """ | |
| regions = res.get("regions") or {} | |
| linked = [i for i in (res.get("shown_markets") or []) if i in regions] | |
| flat = field(res)["flat"] | |
| has_self = any(r["best"] >= SAME_RECORDING for r in regions.values()) | |
| out = ['<div class="lede">'] | |
| out += [f'<p>{s}</p>' for s in summary(res)] | |
| if lead_action: | |
| out.append(lead_action) | |
| out.append('</div>') | |
| out.append('<div class="mapwrap">') | |
| out.append(svg(res, linked, flat=flat)) | |
| out.append(legend(flat, has_self)) | |
| out.append('</div>') | |
| if linked: | |
| out.append('<div class="lab">' | |
| + ('Markets checked — tap one to see just its card' if flat | |
| else 'Tap a market to see just its card — tap again to show all') | |
| + '</div>') | |
| out.append(_chips(res, linked, flat=flat)) | |
| # Four charting markets are smaller than the 110m outlines carry, so they | |
| # are coloured nowhere on the map. Saying so beats a reader concluding the | |
| # market was dropped. | |
| off = [i for i in linked if i not in world()["countries"]] | |
| if off: | |
| out.append( | |
| f'<div class="den" style="margin:0 0 6px">' | |
| f'{_join([ESC(_name(regions, i)) for i in off])} ' | |
| f'{"is" if len(off) == 1 else "are"} too small to draw at this map ' | |
| f'scale — use the market {"button" if len(off) == 1 else "buttons"}' | |
| f' above.</div>') | |
| return "".join(out) | |