utils/leetcode_coach/leetcode_coach/shape.py
Krzysztof kuhy Rudnicki 46ab26fa15
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
leetcode-coach pre-commit / pre-commit (push) Waiting to run
leetcode-coach tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
pylint --fail-under=10 everywhere, and clear what…
Todo: fda71ed1
2026-11-08 20:55:09 +01:00

72 lines
2.3 KiB
Python

"""SQL problems state the OUTPUT SHAPE (columns, rows, grouping) before any code.
The SQL counterpart of the ``req:`` gate: a ``shape:`` message is judged
mechanically (no model, nothing to fail open), and the verdict rides the same
``req`` / ``req_check`` log events (``via: "shape"``), so readers need no change.
"""
from __future__ import annotations
import re
from typing import Final, NamedTuple
from leetcode_coach import req_check
class Facet(NamedTuple):
"""One part of an output shape: how to spot it, and what to ask for."""
pattern: re.Pattern[str]
hint: str
VIA: Final = "shape"
SHAPE_RE: Final = re.compile(r"^\s*shape\s*:", re.IGNORECASE)
SQL_LANGS: Final = frozenset(
{"mysql", "mssql", "oraclesql", "postgresql", "pythondata"}
)
LANGUAGES_RE: Final = re.compile(r"^Languages available:(.*)$", re.MULTILINE)
TAGS_RE: Final = re.compile(r"^Tags:(.*)$", re.MULTILINE)
FACETS: Final = {
"columns": Facet(
re.compile(r"\b(?:columns?|fields?|headers?|named)\b", re.IGNORECASE),
"which columns come back, named how",
),
"rows": Facet(
re.compile(r"\b(?:rows?|records?|per)\b", re.IGNORECASE),
"what one row stands for (one row per ...)",
),
"grouping": Facet(
re.compile(
r"\b(?:group\w*|aggregat\w*|distinct|unique|per|sum|count|ungrouped)\b",
re.IGNORECASE,
),
"how rows are grouped or aggregated, or that nothing is",
),
}
def _items(line: re.Match[str] | None) -> set[str]:
return set() if line is None else {p.strip() for p in line.group(1).split(",")}
def is_sql(statement: str) -> bool:
"""A rendered statement offers a SQL dialect or carries the Database tag."""
langs = _items(LANGUAGES_RE.search(statement))
return bool(langs & SQL_LANGS) or "Database" in _items(TAGS_RE.search(statement))
def missing_facets(text: str) -> tuple[str, ...]:
return tuple(
name for name, facet in FACETS.items() if not facet.pattern.search(text)
)
def check(text: str) -> req_check.Check:
missing = missing_facets(text)
return req_check.Check("incomplete" if missing else "pass", missing, VIA)
def hints(missing: tuple[str, ...] | list[str]) -> list[str]:
return [f"{name}: {FACETS[name].hint}" for name in missing if name in FACETS]