# AGENTS.md Guidance for AI coding agents working in this repository. ## What this repo is Frontend code for the TabArena leaderboard, deployed as a Gradio app on a Hugging Face Space. The leaderboard ranks tabular ML models across tasks, splits, and dataset subsets. - Entrypoint: `main.py` (Gradio app plus the page's CSS/JS/head; HF Space runs it via the `app_file` declared in `README.md` frontmatter). - Pages and their layout: `pages.py` (one entry per leaderboard in `PAGES`); component builders: `views.py`. - Data paths and CSV parsing: `data_loading.py`. Agent-facing endpoints: `api.py`. - UI text and copy: `website_texts.py`. - Static constants (e.g. model-type emoji map): `constants.py`. - Precomputed artifacts: `data/entrants_{...}/imputation_{no,yes}/splits_{...}/tasks_{...}/datasets_{...}/` for TabArena, `data_beyondarena/subsets/{subset}/` for BeyondArena. TabArena ships CSVs plus self-contained interactive HTML only; the static figure PNGs are no longer published, since every figure has an explorer that renders by default and exports its own SVG/PDF/PNG. BeyondArena still ships zipped PNGs. - Python: requires 3.12+. Gradio 6 (`sdk_version` in `README.md` must match the pin in `requirements.txt`). ## Local dev ```bash pip install -e ".[dev]" # or: uv pip install -r pyproject.toml python main.py # launches the Gradio app ``` ## Data pipeline (not regenerated in this repo) Leaderboard data is produced upstream and dropped into `data/`: 1. Run `run_generate_website_artifacts.py` in the autogluon/tabarena repo. 2. Clear the local `data/` directory. 3. Unzip `clean_website_artifacts.zip` into `data/`. Do not edit files under `data/` by hand — they are generated artifacts. ## Architecture notes - A leaderboard view is one cell of a grid, described by the frozen `Subset` dataclass (`entrants`, `imputation`, `splits`, `tasks`, `datasets`) whose `rel_path` *is* the artifact directory, mirroring `get_website_folder_name` in the tabarena repo segment for segment. BeyondArena uses `BeyondSubset` with a single `subset` axis. The axis dicts in `data_loading.py` (`TASK_LABELS`, `DATASET_LABELS`, `BEYOND_SUBSET_LABELS`) define both the valid values and the tab order; the first entry of each is the default. - **`entrants` is not a row filter.** Every leaderboard number is relative to who competed: Elo is pairwise over the participants, Improvability is the gap to the best of them, and the ranks are positions in the field. So each pool is a separately computed set of artifacts, exactly like `imputation_yes`/`imputation_no`. There are three system categories (`open`, `llm`, `api` in `SYSTEM_CATEGORY_LABELS`) and each can be admitted independently, which is why all eight combinations are published: `models` (none, the page default), `open`, `llm`, `api`, `open_llm`, `open_api`, `llm_api`, `open_llm_api`. `entrants_key()` builds the folder segment from the selected set, and a category whose systems do not exist yet renders as an unclickable "coming soon" chip. - The `entrants` and "I care about" selectors sit at the *top* of the page, above the cross-subset overview, because they change every number below them. Task and dataset size sit in the same card, as chip bars: every single-select axis is a chip bar and every multi-select one is a checkbox, so the card reads as one list. "I care about" only reorders the figure stack and picks the Pareto time axis (`views.make_overview_images`); it computes nothing. - Every selector lives in one card at the top of the page (`pages._render_control_band`). Their copy is never rendered as standing text: `main.taStampTitles` puts it in a native `title` so it shows on hover, fed by `main._choice_hints` (per choice) and `data_loading.AXIS_NOTES` (per row caption). Do not pass `info=` to a control in that card; Gradio renders it as visible text under the control and its markup shifts between versions. - "I care about" has two halves. The speed half reorders the figure stack and picks the Pareto time axis; the metric half decides whether panels open on Elo or Improvability, sent to the frames over `postMessage` (`main.taSendMetric`) rather than by generating an artifact per metric. Each figure is rendered exactly once, and the second panel is pinned to the *other* metric so both stay on the page. - `data_loading.load_leaderboard_csv` rewrites the legacy `Reference Pipeline` family to `System`. Not only a migration aid: every BeyondArena subset still carries the old name and is not regenerated by the TabArena pipeline, so without it those rows lose their colour and pill. - A system row is typed `System` (📊) from the artifacts' `MethodClass` column and carries chips from `Tags` (`with-llm`, `closed-source-api`). Both columns come from `website_format` upstream; nothing here infers a system from its name. - `LBContainer` (`data_loading.py`) loads one subset's `website_leaderboard.csv`, lazily unzips its PNGs, and inlines its interactive HTML plots. - The reactive render is driven by one `gr.State` per axis: selecting a tab or toggle updates that state, and the `@gr.render` block in `pages.py` rebuilds the subset's figures and table. - The full leaderboard table is `views.make_leaderboard`, which embeds the generated `leaderboard_table.html` so it reuses the explorers' family colours, variant chips, tag chips and imputation markers rather than reimplementing them. `views.make_leaderboard_gradio` is the fallback for subsets whose artifacts predate that file; its filtering lives in `views.filter_leaderboard`, kept pure so it can be tested without a Gradio context. Neither path may reintroduce the `gradio_leaderboard` custom component, which pinned Gradio < 6. - The per-dataset browser (`views.make_per_dataset_block`, embedding `per_dataset_explorer.html`) is read from the `tasks_all/datasets_all` sibling of the selected cell, not from the cell itself: a dataset's own rank and gap do not depend on which *other* datasets share its leaderboard, so upstream publishes one per (entrants, imputation, splits) rather than for all 60 task/dataset cells. The current task and dataset-size selection travels to the frame as `data-task` / `data-size`, which `main.taSendPerDatasetFilter` forwards over `postMessage` when the frame reports its height; the frame's own chips can widen it again. - That section and the full table are collapsed by default, and the per-dataset one only builds its content once its accordion has been expanded (its artifact is the largest the site ships). Both anchor ids are carried on their contents chip as `data-open`, which `main.taOpenSection` uses to expand the accordion as well as scroll to it. ## The agent-facing API (`api.py`) Hugging Face serves a generated `agents.md` for every Gradio Space at `huggingface.co/spaces//agents.md`, reachable from the **Agents** button on the Space page. It is not this file and cannot be overridden from the repo: it tells an agent to read `/gradio_api/info` and call what it finds. So `/gradio_api/info` is the real contract, and `api.py` owns it. - Four endpoints, registered by `register_api()` from inside the `gr.Blocks` context in `main.py`: `list_leaderboards`, `get_tabarena_leaderboard`, `get_beyondarena_leaderboard` and `get_pareto_frontier`. They read the same published CSVs the UI reads and reuse `data_loading.parse_model`, so the API cannot disagree with the table. - `get_pareto_frontier` is the one to point "which model should I use" at: the leaderboard's top row is often orders of magnitude slower than a model just behind it, so it returns the outright best, the non-dominated frontier on quality vs train/predict time (each entry with its speedup and quality gap), the best model inside a stated time budget, and a summary sentence to relay. - `kind` separates individual models from whole AutoML systems (rows the artifacts mark `method_class="system"`, e.g. AutoGluon). It defaults to `models`, because "the best tabular model" answered with an AutoML system is a wrong answer. - **Every UI event listener must pass `api_visibility="private"`.** Without it, each `.click` / `.change` / `.select` lambda lands in the public schema as `/lambda_7` with a parameter named `value_11`. Before this rule the Space advertised 23 such endpoints and nothing callable. - The same four functions are served as MCP tools at `/gradio_api/mcp/` (`launch(mcp_server=True)`, `gradio[mcp]` extra). Gradio derives each tool's schema from the type hints, its description from `api_description` (falling back to the docstring), and its per-parameter descriptions from the `Args:` block. All of it is load-bearing for agents, not just for readers, under two constraints: - **Every description has to introduce the subject.** Gradio 6 has no server-level instructions field, and a client may show one tool with no sibling for context. So each `api_description` names the domain (predicting a target column from structured, rows-and-columns data) and the methods people ask about by name (TabPFN, LightGBM, CatBoost, AutoGluon, ...), and says which questions it answers. "Ranked TabArena results" alone is invisible to an agent whose user asked about tabular foundation models or about which model to run on a CSV; a maintainer hit exactly that, and the tool only fired once the prompt mentioned TabPFN. - **Each `Args:` entry must fit on one line.** `gradio.utils.get_function_description` splits each docstring line on its first colon and drops continuations, so a wrapped parameter description reaches the schema cut off mid-sentence. The lines in `api.py` are long on purpose. To check both without a browser, feed a stub carrying the docstring to `gradio.utils.get_function_description` and read what comes back. - Record keys are the CSV headers with the sort-direction marker stripped and snake_cased (`Improvability (%) [⬇️]` becomes `improvability_pct`); `Model` is split into `model`, `variant` and `model_url`; `Verified` becomes a bool. `_api_key` in `api.py` is the single place that mapping lives. - Bulk readers should be pointed at the raw CSV instead, which `list_leaderboards` returns as a URL template. It serves identical numbers with no queue and no token. The generated `agents.md` tells agents to POST to `/gradio_api/call/v2/` with named parameters. That route only exists in Gradio 6, which is why the Space is pinned there; on 5.x it returned 405 and agents had to fall back to `/gradio_api/call/` with a positional `{"data": [...]}` body. Both work now. ## Conventions - Keep changes minimal — this is a small display app, not a framework. Avoid speculative abstraction. - Don't add comments that just describe what code does; the file is short enough to read. - User-facing copy lives in `website_texts.py`. Update it there, not inline. - Version bumps: edit `version` in `pyproject.toml` and the matching changelog entry in `website_texts.py` (`VERSION_HISTORY_BUTTON_TEXT`). - Prose you write, including user-facing copy in `website_texts.py`, follows [AI Writing Tropes to Avoid](#ai-writing-tropes-to-avoid) at the bottom of this file. ## Things to verify before reporting a UI change as done - Launch `python main.py` and click through the affected tabs in a browser. - Type checks and unit tests aren't meaningful here (none configured) — manual verification is the bar. --- # AI Writing Tropes to Avoid Applies to everything you write in this repo that a human reads: docstrings, comments, markdown docs, commit messages, PR descriptions, user-facing copy, and your replies in the chat. Source: [tropes.fyi](https://tropes.fyi) by [ossama.is](https://ossama.is) --- ## Word Choice ### "Quietly" and Other Magic Adverbs Overuse of "quietly" and similar adverbs to convey subtle importance or understated power. AI reaches for these adverbs to make mundane descriptions feel significant. Also includes: "deeply", "fundamentally", "remarkably", "arguably". **Avoid patterns like:** - "quietly orchestrating workflows, decisions, and interactions" - "the one that quietly suffocates everything else" - "a quiet intelligence behind it" ### "Delve" and Friends Used to be the most infamous AI tell. "Delve" went from an uncommon English word to appearing in a staggering percentage of AI-generated text. Part of a family of overused AI vocabulary including "certainly", "utilize", "leverage" (as a verb), "robust", "streamline", and "harness". **Avoid patterns like:** - "Let's delve into the details..." - "Delving deeper into this topic..." - "We certainly need to leverage these robust frameworks..." ### "Tapestry" and "Landscape" Overuse of ornate or grandiose nouns where simpler words would do. "Tapestry" is used to describe anything interconnected. "Landscape" is used to describe any field or domain. Other offenders: "paradigm", "synergy", "ecosystem", "framework". **Avoid patterns like:** - "The rich tapestry of human experience..." - "Navigating the complex landscape of modern AI..." - "The ever-evolving landscape of technology..." ### The "Serves As" Dodge Replacing simple "is" or "are" with pompous alternatives like "serves as", "stands as", "marks", or "represents". AI avoids basic copulas because its repetition penalty pushes it toward fancier constructions (I've studied this!). **Avoid patterns like:** - "The building serves as a reminder of the city's heritage." - "Gallery 825 serves as LAAA's exhibition space for contemporary art." - "The station marks a pivotal moment in the evolution of regional transit." --- ## Sentence Structure ### Negative Parallelism The "It's not X -- it's Y" pattern, often with an em dash. The single most commonly identified AI writing tell. Man I f*cking hate it. AI uses this to create false profundity by framing everything as a surprising reframe. One in a piece can be effective; ten in a blog post is a genuine insult to the reader. Before LLMs, people simply did not write like this at scale. Includes the causal variant "not because X, but because Y" where every explanation is framed as a surprise reveal, the em-dash dismissal "X -- not Y", and the cross-sentence reframe where the same noun is negated then repositioned: "The question isn't X. The question is Y." **Avoid patterns like:** - "It's not bold. It's backwards." - "Feeding isn't nutrition. It's dialysis." - "Half the bugs you chase aren't in your code. They're in your head." ### "Not X. Not Y. Just Z." The dramatic countdown pattern. AI builds tension by negating two or more things before revealing the actual point. Creates a false sense of narrowing down to the truth. **Avoid patterns like:** - "Not a bug. Not a feature. A fundamental design flaw." - "Not ten. Not fifty. Five hundred and twenty-three lint violations across 67 files." - "not recklessly, not completely, but enough" ### "The X? A Y." Self-posed rhetorical questions answered immediately in the next sentence or clause. The model asks a question nobody was asking, then answers it for dramatic effect. Thinks this is the epitome of great writing. **Avoid patterns like:** - "The result? Devastating." - "The worst part? Nobody saw it coming." - "The scary part? This attack vector is perfect for developers." ### Anaphora Abuse Repeating the same sentence opening multiple times in quick succession. **Avoid patterns like:** - "They assume that users will pay... They assume that developers will build... They assume that ecosystems will emerge... They assume that..." - "They could expose... They could offer... They could provide... They could create... They could let... They could unlock..." - "They have built engines, but not vehicles. They have built power, but not leverage. They have built walls, but not doors." ### Tricolon Abuse Overuse of the rule-of-three pattern, often extended to four or five. A single tricolon is elegant; three back-to-back tricolons are a pattern recognition failure. **Avoid patterns like:** - "Products impress people; platforms empower them. Products solve problems; platforms create worlds. Products scale linearly; platforms scale exponentially." - "identity, payments, compute, distribution" - "workflows, decisions, and interactions" ### "It's Worth Noting" Filler transitions that signal nothing. AI uses these phrases to introduce new points without actually connecting them to the previous argument. Also includes: "It bears mentioning", "Importantly", "Interestingly", "Notably". **Avoid patterns like:** - "It's worth noting that this approach has limitations." - "Importantly, we must consider the broader implications." - "Interestingly, this pattern repeats across industries." ### Superficial Analyses Tacking a present participle ("-ing") phrase onto the end of a sentence to inject shallow analysis that says nothing. The model attaches significance, legacy, or broader meaning to mundane facts using phrases like "highlighting its importance", "reflecting broader trends", or "contributing to the development of...". **Avoid patterns like:** - "contributing to the region's rich cultural heritage" - "This etymology highlights the enduring legacy of the community's resistance and the transformative power of unity in shaping its identity." - "underscoring its role as a dynamic hub of activity and culture" ### False Ranges Using "from X to Y" constructions where X and Y aren't on any real scale. In legitimate use, "from X to Y" implies a spectrum with a meaningful middle. AI uses it as a fancy way to list two loosely related things. "From innovation to cultural transformation" -- what's in between???? Nothing! **Avoid patterns like:** - "From innovation to implementation to cultural transformation." - "From the singularity of the Big Bang to the grand cosmic web." - "From problem-solving and tool-making to scientific discovery, artistic expression, and technological innovation." --- ## Paragraph Structure ### Short Punchy Fragments Excessive use of very short sentences or sentence fragments as standalone paragraphs for manufactured emphasis. RLHF training has pushed models toward "writing for readability" aimed at the lowest common denominator: one thought per sentence, no mental state-keeping required. It's an inhuman style. No real person writes first drafts this way because it doesn't match how humans think or speak. **Avoid patterns like:** - "He published this. Openly. In a book. As a priest." - "These weren't just products. And the software side matched. Then it professionalised. But I adapted." - "Platforms do." ### Listicle in a Trench Coat Numbered or labeled points dressed up as continuous prose. The model writes what is essentially a listicle but wraps each point in a paragraph that starts with "The first... The second... The third..." to disguise the format. Perhaps you told it to stop generating lists and it decided to do this instead... still very common. **Avoid patterns like:** - "The first wall is the absence of a free, scoped API... The second wall is the lack of delegated access... The third wall is the absence of scoped permissions..." - "The second takeaway is that... The third takeaway is that... The fourth takeaway is that..." --- ## Tone ### "Here's the Kicker" False suspense transitions that promise a revelation but deliver a point that did NOT need the buildup. The model uses these phrases to manufacture drama before an otherwise unremarkable observation LOL. Also includes: "Here's the thing", "Here's where it gets interesting", "Here's what most people miss", "Here's the starting point", "Here's the deal". **Avoid patterns like:** - "Here's the kicker." - "Here's the thing about AI adoption." - "Here's where it gets interesting." ### "Think of It As..." The patronizing analogy. AI constantly reaches for "Think of it as..." or "It's like a..." to simplify concepts. The model defaults to teacher mode and assumes the reader needs a metaphor to understand anything. Often produces analogies that are less clear than the original concept. **Avoid patterns like:** - "Think of it like a highway system for data." - "Think of it as a Swiss Army knife for your workflow." - "It's like asking someone to buy a car they're only allowed to sit in while it's parked." ### "Imagine a World Where..." The classic AI invitation to futurism. To sell the argument usually begins with "Imagine" followed by a list of wonderful things that will happen if the reader agrees with the premise. **Avoid patterns like:** - "Imagine a world where every tool you use -- your calendar, your inbox, your documents, your CRM, your code editor -- has a quiet intelligence behind it..." - "In that world, workflows stop being collections of manual steps and start becoming orchestrations." ### False Vulnerability Simulated self-awareness or honesty that reads as performative. The model pretends to break the fourth wall or admit a bias, creating a false sense of authenticity. Real vulnerability is specific and uncomfortable; AI vulnerability is polished and risk-free!!!! **Avoid patterns like:** - "And yes, I'm openly in love with the platform model" - "And yes, since we're being honest: I'm looking at you, OpenAI, Google, Anthropic, Meta" - "This is not a rant; it's a diagnosis" ### "The Truth Is Simple" Asserting that something is obvious, clear or simple instead of actually proving it. If you have to tell the reader your point is clear, it very likely isn't. Also includes the dramatic reveal variant: "but none of them is the real story. The real story is..." -- claiming privileged insight while waving away everything before it. **Avoid patterns like:** - "The reality is simpler and less flattering" - "History is unambiguous on this point" - "History is clear, the metrics are clear, the examples are clear" ### Grandiose Stakes Inflation Everything is the most important thing ever. AI inflates the stakes of every argument to world-historical significance. A blog post about API pricing becomes a meditation on the fate of civilization. **Avoid patterns like:** - "This will fundamentally reshape how we think about everything." - "will define the next era of computing" - "something entirely new" ### "Let's Break This Down" The pedagogical voice that assumes the reader needs hand-holding. AI defaults to a teacher-student dynamic even when writing for expert audiences. Also includes: "Let's unpack this", "Let's explore", "Let's dive in". **Avoid patterns like:** - "Let's break this down step by step." - "Let's unpack what this really means." - "Let's explore this idea further." ### Vague Attributions Attributing claims to unnamed authorities instead of being specific. AI loves to invoke "experts", "observers", "industry reports", and "several publications" without naming anyone. It also inflates the quantity of sources -- presenting what one person said as a widely held view, or writing "several publications have cited" when it means two. If you can't name the expert, you don't have a source. **Avoid patterns like:** - "Experts argue that this approach has significant drawbacks." - "Industry reports suggest that adoption is accelerating." - "Observers have cited the initiative as a turning point." ### Invented Concept Labels AI clusters invented compound labels that sound analytical without being grounded. It appends abstract problem-nouns (paradox, trap, creep, divide, vacuum, inversion) to domain words -- "supervision paradox", "acceleration trap", "workload creep" -- and uses them as if they're established, rigorously defined terms. They function as rhetorical shorthand: name a thing, skip the argument. Multiple such labels in the same piece is a strong signal of AI slop. **Avoid patterns like:** - "the supervision paradox" - "the acceleration trap" - "workload creep" --- ## Formatting ### Em-Dash Addiction Compulsive overuse of em dashes for dramatic pauses, parenthetical asides and pivot points. A human writer might use 2-3 per piece (and naturally); AI will use 20+. **Avoid patterns like:** - "The problem -- and this is the part nobody talks about -- is systemic." - "The tinkerer spirit didn't die of natural causes -- it was bought out." - "Not recklessly, not completely -- but enough -- enough to matter." ### Double-Hyphen Dash The em dash wearing a false moustache. Once "em dash means AI" became common knowledge, the character started getting swapped for a double hyphen: sometimes because the text passed through a markdown conversion, sometimes because someone ran a find-and-replace to look more human, sometimes because the model was steered off the character while keeping the habit. Either way the compulsive mid-sentence pivot survives the substitution, which is what actually gives it away. Writers who reach for double hyphens honestly do so once or twice out of typographic laziness, rarely fifteen times in one post. Flagged at five or more per thousand words. **Avoid patterns like:** - "The problem -- and this is the part nobody talks about -- is systemic." - "It's not a rewrite -- it's a reckoning." - "We shipped it fast -- maybe too fast -- and paid for it later." ### Bold-First Bullets Every bullet point or list item starts with a bolded phrase or sentence. Extremely common in Claude and ChatGPT markdown output. Almost nobody formats lists this way when writing by hand. It's a telltale sign of AI-generated documentation and blog posts AND README files (especially with emojis). **Avoid patterns like:** - "Every single bullet point begins with a bold keyword." - "**Security**: Environment-based configuration with..." - "**Performance**: Lazy loading of expensive resources..." ### Unicode Decoration Use of unicode arrows (->), smart/curly quotes, and other special characters that can't be easily typed on a standard keyboard. Real writers typing in a text editor produce straight quotes and -> or =>. Claude in particular loves the -> arrow. **Avoid patterns like:** - "Input → Processing → Output" - "This leads to better outcomes → which means higher engagement" - "“Smart quotes” instead of straight "quotes" that you’d actually type" --- ## Composition ### Fractal Summaries "What I'm going to tell you; what I'm telling you; what I just told you" -- applied at every level of the document. Every subsection gets a summary. Every section gets a summary. The document itself gets a summary. **Avoid patterns like:** - "In this section, we'll explore... [3000 words later] ...as we've seen in this section." - "A conclusion that restates every point already made in the previous 3000 words" - "And so we return to where we began." ### The Dead Metaphor Latching onto a single metaphor and beating it into the ground across the entire thing. A human writer would introduce a metaphor, use it then move on. AI will repeat the same metaphor 5-10 times. **Avoid patterns like:** - "The ecosystem needs ecosystems to build ecosystem value." - "Walls and doors used 30+ times in the same article" - "Every paragraph finds a way to say "primitives" again" ### Historical Analogy Stacking ESPECIALLY COMMON IN TECHNICAL WRITING: Rapid-fire listing of historical companies or tech revolutions to build false authority. **Avoid patterns like:** - "Apple didn't build Uber. Facebook didn't build Spotify. Stripe didn't build Shopify. AWS didn't build Airbnb." - "Every major technological shift -- the web, mobile, social, cloud -- followed the same pattern." - "Take Spotify... Or consider Uber... Airbnb followed a similar path... Shopify is another example... Even Discord..." ### One-Point Dilution Making a single argument and restating it in 10 different ways across thousands of words. The model pads a simple thesis to feel "comprehensive" by rephrasing the same idea with different metaphors, examples, and framings. An 800-word argument becomes 4000 words of circular repetition. **Avoid patterns like:** - "The same point, restated eight ways across 4000 words." - "Each section rephrases the thesis with a different metaphor but adds nothing new" ### Content Duplication Repeating entire sections or paragraphs verbatim within the same piece. This happens when the model loses track of what it has already written, especially in longer pieces. A dead giveaway of unedited AI output. Less common nowadays. **Avoid patterns like:** - "The same section appeared twice, word-for-word identical." - "Paragraph 3 and paragraph 17 are the same sentence reworded" ### The Signposted Conclusion Explicitly announcing the conclusion with "In conclusion", "To sum up", or "In summary". Competent writing doesn't need to tell you it's concluding. The reader can feel it. AI signals its structural moves because it's following a template, not writing organically. **Avoid patterns like:** - "In conclusion, the future of AI depends on..." - "To sum up, we've explored three key themes..." - "In summary, the evidence suggests..." ### "Despite Its Challenges..." The rigid formula where AI acknowledges problems only to immediately dismiss them. Always follows the same beat: "Despite its [positive words], [subject] faces challenges..." then ends with "Despite these challenges, [optimistic conclusion].". **Avoid patterns like:** - "Despite these challenges, the initiative continues to thrive." - "Despite its industrial and residential prosperity, Korattur faces challenges typical of urban areas." - "Despite their promising applications, pyroelectric materials face several challenges that must be addressed for broader adoption." --- Remember: any of these patterns used once might be fine. The problem is when multiple tropes appear together or when a single trope is used repeatedly. Write like a human: varied, imperfect, specific.