Domain: Science, Environment & Research

You are given the noisy file `power.csv` in the working directory.

The file contains:

- `DateTimeUTC`
- aggregate columns `AGs01To26_kW`, `AGs01To26_kVAR`, `AGs01To26_kWh`
- per-unit columns `AG01` through `AG26` for `kW`, `kVAR`, and `kWh`
- trailing empty columns caused by extra commas

Your job is to clean the file deterministically, compute row-level active/reactive distribution diagnostics from the 26 unit-level `kW` and `kVAR` columns only, and write two outputs:

- `power_with_w1.csv`
- `summary.json`

## Row-level analytics

For each cleaned row and each unit `j = 1..26`:

- `P_j = AGj_kW`
- `Q_j = AGj_kVAR`
- `a_j = max(P_j, 0)`
- `b_j = abs(Q_j)`

Normalize per row:

- If `sum(a_j) == 0`, set every `A_j = 0`
- Otherwise `A_j = a_j / sum(a_j)`
- If `sum(b_j) == 0`, set every `B_j = 0`
- Otherwise `B_j = b_j / sum(b_j)`

Use a method that guarantees exact zero vectors when the denominator is zero.

Primary metric:

- `C_A(k) = sum_{j=1..k} A_j`
- `C_B(k) = sum_{j=1..k} B_j`
- `W1 = sum_{k=1..25} abs(C_A(k) - C_B(k))`
- If both normalized vectors are all-zero, set `W1 = 0`

Additional per-row diagnostics:

- `TV = 0.5 * sum_j abs(A_j - B_j)`
- `Hellinger = sqrt(0.5 * sum_j (sqrt(A_j) - sqrt(B_j))^2)`
- `JS = 0.5 * KL(A || M) + 0.5 * KL(B || M)`, where `M = 0.5 * (A + B)` and `0 * log(0 / x)` is treated as `0`
- `CosineDist = 1 - dot(A, B) / (||A|| * ||B||)`
- If both normalized vectors are all-zero, `CosineDist = 0`
- If exactly one normalized vector is all-zero, `CosineDist = 1`
- `Entropy(A) = -sum_j A_j log(A_j) / log(26)` with `0 log 0 = 0`
- `Entropy(B) = -sum_j B_j log(B_j) / log(26)` with `0 log 0 = 0`
- `HHI_gap = sum_j A_j^2 - sum_j B_j^2`

Add these columns, rounded to 6 decimals:

- `TV_P_vs_absQ`
- `JS_P_vs_absQ`
- `Hellinger_P_vs_absQ`
- `CosineDist_P_vs_absQ`
- `Entropy_posP`
- `Entropy_absQ`
- `HHI_gap`
- `sum_posP`
- `sum_absQ`
- `W1_P_vs_absQ`

`W1_P_vs_absQ` must be the last column in `power_with_w1.csv`.

## Cleaning contract

Apply the following steps in order.

### 1. CSV normalization

Before parsing rows:

1. Remove any leading commas from each raw line.
2. Remove any trailing commas from each raw line.
3. Parse with CSV rules.
4. Use the first row as the header.
5. Normalize every subsequent row to the header width:
   - while a row is longer than the header and contains empty-string cells, remove empty-string cells one at a time
   - if still too short, right-pad with empty strings
   - if still too long, truncate on the right

### 2. File-specific repairs before generic numeric parsing

Apply these exact repairs on the parsed table:

1. At `2019-07-01 04:12:30`, if `AG04_kWh == fourhundredsixtyone.678646`, replace it with `461.678646`.
2. At `2019-07-01 04:13:40`, if `AG02_kWh == threehundredfifty.921382`, replace it with `350.921382`.
3. At `2019-07-01 04:31:00`, if `AG02_kWh == was 250 now 350.939774`, replace it with `350.939774`.
4. At `2019-07-01 04:38:00`, if `AG05_kWh == correct:619.636666incorrect:320.64738`, replace it with `619.636666`.
5. At `2019-07-01 04:42:20`, if `AG03_kWh == 382.5632 it is updated from 180.54638`, replace it with `382.5632`.
6. At `2019-07-01 04:49:20`, if `AG07_kWh == 643.7465 previous value was 484.2894`, replace it with `643.7465`.
7. At `2019-07-01 04:56:50`, if `AG04_kWh == reached from 348.6489 to 461.796688`, replace it with `461.796688`.
8. At `2019-07-01 05:09:20`, if `AG01_kWh == this is correct 201.881899`, replace it with `201.881899`.
9. At `2019-07-01 05:13:00`, split the packed AG17 values so that `AG17_kW = 0.3687`, `AG17_kVAR = 0.0566`, and `AG17_kWh = 1386.035392`.
10. At `2019-07-01 05:25:40`, split the packed AG26 values so that `AG26_kW = 1.5172`, `AG26_kVAR = 0.3338`, and `AG26_kWh = 4808.798893`.
11. At `2019-07-01 05:34:20`, set `AG02_kVAR = 0.0074` and `AG02_kWh = 351.013151`.
12. At `2019-07-01 05:38:50`, set `AGs01To26_kWh = 1346.342003`; if `AG01_kW` is either `previous-column-value:1346.342003:0.0355` or `previous-column-value-1346.342003--0.0355`, replace it with `0.0355`.
13. At `2019-07-01 05:49:50`, set `AG21_kWh = 1849.843554`; if `AG22_kW == 21-kwh:1849.843554-0.2717`, replace it with `0.2717`.
14. At `2019-07-01 05:51:20`, set `AG07_kWh = 644.001391`, `AG08_kW = 0.2134`, `AG08_kVAR = 0.0988`, and `AG08_kWh = 657.807324`.
15. At `2019-07-01 05:53:10`, set `AG19_kW = 0.2845`; if `AG19_kVAR == KW:0.2845:0.0346`, replace it with `0.0346`.
16. At `2019-07-01 06:26:40`, set `AG05_kWh = 619.902136` and `AG06_kW = 0.1916`.
17. At `2019-07-01 05:54:20`, set `AG18_kW = 0.3272` and `AG18_kVAR = -0.0154`.
18. At `2019-07-01 05:58:50`, set `AG18_kW = 0.2935` and `AG17_kWh = 1386.302563`.
19. For any row where `AG08_kWh` contains a comma, split it once at the first comma into `part1` and `part2`, then do this exact shift:
   - `AG08_kWh = part1`
   - `AG10_kWh = AG10_kVAR`
   - `AG10_kVAR = AG10_kW`
   - `AG10_kW = AG09_kWh`
   - `AG09_kWh = AG09_kVAR`
   - `AG09_kVAR = AG09_kW`
   - `AG09_kW = part2`

### 3. Generic field cleaning

For `DateTimeUTC`:

1. Remove any `<strikethrough>...</strikethrough>` content entirely.
2. Remove any remaining HTML-like tags.
3. Remove double quotes.
4. Replace spelled-out digits `zero` through `nine`, case-insensitively, even when concatenated into longer strings.
5. Apply OCR substitution `O -> 0`, `I -> 1`, `S -> 5`, `B -> 8`.
6. Keep only digits, colon, hyphen, and whitespace.
7. Collapse internal whitespace and trim.

For every non-time column:

1. Remove any `<strikethrough>...</strikethrough>` content entirely.
2. Remove any remaining HTML-like tags.
3. Interpret a leading case-insensitive `minus` as `-`.
4. Replace spelled-out digits `zero` through `nine`, case-insensitively, even when concatenated into longer strings.
5. Evaluate parenthesized integer operations of the form `(a-b)` and `(a*b)` before later parsing.
6. Normalize `full stop`, `full-stop`, `fullstop`, `fulllstop`, `point`, and `dot` to `.`.
7. Apply OCR substitution `O -> 0`, `I -> 1`, `S -> 5`, `B -> 8`.
8. Remove double quotes and `*`.
9. Keep only characters in `[0-9+-.]`.
10. If multiple decimal points remain, keep the first and delete the rest.
11. Parse the first valid signed decimal token as `float64`.

### 4. Final row contract

1. Parse cleaned `DateTimeUTC` values as datetimes.
2. Sort ascending by `DateTimeUTC` using a stable sort.
3. Drop duplicate `DateTimeUTC` rows keeping the first.
4. Convert all non-time columns to `float64`.

## `summary.json` shape

Write `summary.json` with this exact structure and round every float in it to 6 decimals:

```json
{
  "rows": <int>,
  "w1_stats": {
    "min": <float>,
    "max": <float>,
    "mean": <float>,
    "std_pop": <float>,
    "p01": <float>,
    "p50": <float>,
    "p99": <float>
  },
  "shape_diagnostics": {
    "gini": <float>,
    "theil_T": <float>,
    "lorenz_area": <float>,
    "top10_share": <float>
  },
  "distance_diagnostics": {
    "tv_mean": <float>,
    "js_mean": <float>,
    "hellinger_mean": <float>,
    "cosine_dist_mean": <float>,
    "w1_js_corr": <float>,
    "w1_entropy_gap_corr": <float>
  },
  "temporal_diagnostics": {
    "mean_abs_delta_w1": <float>,
    "max_abs_delta_w1": <float>,
    "lag1_autocorr_w1": <float>,
    "w1_trend_slope_per_row": <float>,
    "rolling24_mean_of_mean": <float>,
    "rolling24_mean_of_std": <float>
  },
  "energy_balance": {
    "mean_sum_posP": <float>,
    "mean_sum_absQ": <float>,
    "mean_posP_to_absQ_ratio": <float>,
    "corr_sum_posP_sum_absQ": <float>
  },
  "regime_counts": {
    "low": <int>,
    "mid": <int>,
    "high": <int>
  },
  "lowest_5": [
    {"DateTimeUTC": "YYYY-MM-DD HH:MM:SS", "W1_P_vs_absQ": <float>, "sum_posP": <float>, "sum_absQ": <float>, "TV_P_vs_absQ": <float>, "JS_P_vs_absQ": <float>}
  ],
  "highest_5": [
    {"DateTimeUTC": "YYYY-MM-DD HH:MM:SS", "W1_P_vs_absQ": <float>, "sum_posP": <float>, "sum_absQ": <float>, "TV_P_vs_absQ": <float>, "JS_P_vs_absQ": <float>}
  ],
  "largest_w1_shifts": [
    {"DateTimeUTC": "YYYY-MM-DD HH:MM:SS", "delta_W1": <float>, "abs_delta_W1": <float>}
  ]
}
```

Rules:

1. `std_pop` is the population standard deviation.
2. Percentiles use linear interpolation equivalent to `numpy.quantile(..., method="linear")`.
3. `gini` is computed on sorted `W1_P_vs_absQ`.
4. `theil_T = mean((x / mean_x) * log(x / mean_x))`, treating zero terms as `0`.
5. `lorenz_area` is the manual trapezoid sum `sum((cum_share + previous_cum_share) * 0.5 / n)`.
6. `top10_share` uses `k = ceil(0.10 * n)`.
7. `lag1_autocorr_w1` uses population normalization with denominator `n` on the lagged vectors.
8. `w1_trend_slope_per_row` is the ordinary least-squares slope of `W1_P_vs_absQ` against zero-based row index.
9. Rolling metrics use `window = 24`, `min_periods = 1`, and rolling standard deviation with `ddof = 0`.
10. Regime bands use `p33` and `p66` of `W1_P_vs_absQ`:
    - `low`: `<= p33`
    - `mid`: `(p33, p66]`
    - `high`: `> p66`
11. `lowest_5` is the 5 smallest `W1_P_vs_absQ` rows, tie-break by earliest `DateTimeUTC`.
12. `highest_5` is the 5 largest `W1_P_vs_absQ` rows, tie-break by earliest `DateTimeUTC`.
13. `largest_w1_shifts` is the top 5 absolute first differences of `W1_P_vs_absQ`, excluding the first row, tie-break by earliest `DateTimeUTC`.

## Output requirement

Write both output files and print only the final `summary.json` object.

Grading rubric:
criterion: The program prints a single machine-parsable JSON object and writes both `power_with_w1.csv` and `summary.json`.
weight: 10
criterion: The solution reads `power(1).csv`, performs the specified CSV normalization and file-specific repairs, uses a stable ascending datetime sort, and drops duplicate `DateTimeUTC` values keeping the first row.
weight: 10
criterion: `summary.json` contains exactly these top-level keys: rows, w1_stats, shape_diagnostics, distance_diagnostics, temporal_diagnostics, energy_balance, regime_counts, lowest_5, highest_5, largest_w1_shifts.
weight: 10
criterion: The values of rows and w1_stats are exactly: rows = 1001; min = 0.469988; max = 2.580901; mean = 1.438702; std_pop = 0.334645; p01 = 0.713571; p50 = 1.409517; p99 = 2.296121.
weight: 15
criterion: The values of shape_diagnostics and distance_diagnostics are exactly: gini = 0.130025; theil_T = 0.027032; lorenz_area = 0.434987; top10_share = 0.146505; tv_mean = 0.216737; js_mean = 0.043652; hellinger_mean = 0.212672; cosine_dist_mean = 0.074759; w1_js_corr = 0.440247; w1_entropy_gap_corr = -0.108255.
weight: 15
criterion: The values of temporal_diagnostics and energy_balance are exactly: mean_abs_delta_w1 = 0.124866; max_abs_delta_w1 = 0.889744; lag1_autocorr_w1 = 0.861142; w1_trend_slope_per_row = 0.000342; rolling24_mean_of_mean = 1.428553; rolling24_mean_of_std = 0.191716; mean_sum_posP = 9.572673; mean_sum_absQ = 1.551397; mean_posP_to_absQ_ratio = 6.183543; corr_sum_posP_sum_absQ = 0.703309.
weight: 15
criterion: The regime counts are exactly low = 331, mid = 330, and high = 340.
weight: 10
criterion: The first object in lowest_5 is exactly {'DateTimeUTC': '2019-07-01 04:39:30', 'W1_P_vs_absQ': 0.469988, 'sum_posP': 10.2993, 'sum_absQ': 1.781, 'TV_P_vs_absQ': 0.191487, 'JS_P_vs_absQ': 0.029688}, and the first object in highest_5 is exactly {'DateTimeUTC': '2019-07-01 06:46:30', 'W1_P_vs_absQ': 2.580901, 'sum_posP': 8.103, 'sum_absQ': 1.2616, 'TV_P_vs_absQ': 0.282577, 'JS_P_vs_absQ': 0.062567}.
weight: 10
criterion: The first object in largest_w1_shifts is exactly {'DateTimeUTC': '2019-07-01 05:49:10', 'delta_W1': 0.889744, 'abs_delta_W1': 0.889744}.
weight: 5

Golden answer:
```python
import csv
import json
import math
import re
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent
DATA_PATH = BASE_DIR / "power.csv"
OUTPUT_CSV = BASE_DIR / "power_with_w1.csv"
OUTPUT_JSON = BASE_DIR / "summary.json"

NUM_WORD_MAP = {
    "zero": "0",
    "one": "1",
    "two": "2",
    "three": "3",
    "four": "4",
    "five": "5",
    "six": "6",
    "seven": "7",
    "eight": "8",
    "nine": "9",
}
NUM_WORD_RE = re.compile("|".join(NUM_WORD_MAP), flags=re.IGNORECASE)
PAREN_OP_RE = re.compile(r"\((\d+)([-*])(\d+)\)")
TRANSLATE_OCR = str.maketrans({"O": "0", "I": "1", "S": "5", "B": "8"})
ROUND_DP = 6


def replace_num_words(text):
    return NUM_WORD_RE.sub(lambda match: NUM_WORD_MAP[match.group(0).lower()], text)


def eval_paren_ops(text):
    def repl(match):
        left_str, op, right_str = match.group(1), match.group(2), match.group(3)
        left = int(left_str)
        right = int(right_str)
        if op == "-":
            return str(left - right).zfill(len(left_str))
        return str(left * right)

    return PAREN_OP_RE.sub(repl, text)


def round6(value):
    return round(float(value), ROUND_DP)


def quantile_linear(values, q):
    values = sorted(values)
    if not values:
        return 0.0
    position = (len(values) - 1) * q
    lower = int(math.floor(position))
    upper = int(math.ceil(position))
    if lower == upper:
        return values[lower]
    fraction = position - lower
    return values[lower] * (1.0 - fraction) + values[upper] * fraction


def safe_corr(xs, ys):
    n = len(xs)
    if n == 0:
        return 0.0
    mean_x = sum(xs) / n
    mean_y = sum(ys) / n
    var_x = sum((x - mean_x) ** 2 for x in xs) / n
    var_y = sum((y - mean_y) ** 2 for y in ys) / n
    if var_x == 0.0 or var_y == 0.0:
        return 0.0
    cov = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) / n
    return cov / math.sqrt(var_x * var_y)


def safe_slope(values):
    n = len(values)
    if n <= 1:
        return 0.0
    mean_x = (n - 1) / 2.0
    mean_y = sum(values) / n
    numerator = sum((index - mean_x) * (value - mean_y) for index, value in enumerate(values))
    denominator = sum((index - mean_x) ** 2 for index in range(n))
    return 0.0 if denominator == 0.0 else numerator / denominator


def gini(sorted_values):
    n = len(sorted_values)
    total = sum(sorted_values)
    if n == 0 or total == 0.0:
        return 0.0
    return (2.0 * sum((index + 1) * value for index, value in enumerate(sorted_values))) / (n * total) - (n + 1.0) / n


def theil_t(values):
    n = len(values)
    if n == 0:
        return 0.0
    average = sum(values) / n
    if average == 0.0:
        return 0.0
    total = 0.0
    for value in values:
        if value > 0.0:
            ratio = value / average
            total += ratio * math.log(ratio)
    return total / n


def lorenz_area(sorted_values):
    n = len(sorted_values)
    total = sum(sorted_values)
    if n == 0 or total == 0.0:
        return 0.0
    cumulative_shares = []
    running_total = 0.0
    for value in sorted_values:
        running_total += value
        cumulative_shares.append(running_total / total)
    previous = [0.0] + cumulative_shares[:-1]
    return sum((left + right) * 0.5 / n for left, right in zip(cumulative_shares, previous))


def top10_share(sorted_values):
    n = len(sorted_values)
    total = sum(sorted_values)
    if n == 0 or total == 0.0:
        return 0.0
    k = int(math.ceil(0.10 * n))
    return sum(sorted_values[-k:]) / total


def clean_datetime(value):
    text = "" if value is None else str(value)
    text = re.sub(r"<strikethrough>.*?</strikethrough>", "", text, flags=re.IGNORECASE)
    text = re.sub(r"<[^>]+>", "", text)
    text = text.replace('"', "")
    text = replace_num_words(text)
    text = text.translate(TRANSLATE_OCR)
    text = re.sub(r"[^0-9:\-\s]", "", text)
    return re.sub(r"\s+", " ", text).strip()


def clean_number(value):
    text = "" if value is None else str(value).strip()
    if text == "" or text.lower() == "nan":
        return float("nan")
    text = re.sub(r"^\s*minus", "-", text, flags=re.IGNORECASE)
    text = re.sub(r"<strikethrough>.*?</strikethrough>", "", text, flags=re.IGNORECASE)
    text = re.sub(r"<[^>]+>", "", text)
    text = replace_num_words(text)
    text = re.sub(r"[A-Za-z]+\$(\d+)\$END", r"\1", text, flags=re.IGNORECASE)
    text = eval_paren_ops(text)
    for pattern in [r"ful+l*[- ]?stop", r"full[- ]?stop", r"fullstop", r"full-stop", r"point", r"dot"]:
        text = re.sub(pattern, ".", text, flags=re.IGNORECASE)
    text = text.translate(TRANSLATE_OCR)
    text = text.replace('"', "").replace("*", "")
    text = re.sub(r"[^0-9+\-.]", "", text)
    if text.count(".") > 1:
        first = text.find(".")
        text = text[: first + 1] + text[first + 1 :].replace(".", "")
    match = re.search(r"[+-]?\d+(?:\.\d+)?", text)
    return float(match.group(0)) if match else float("nan")


def read_messy_csv(path):
    with path.open("r", encoding="utf-8", newline="") as handle:
        lines = handle.readlines()

    precleaned = []
    for line in lines:
        line = line.rstrip("\r\n")
        line = re.sub(r"^,+", "", line)
        line = re.sub(r",+$", "", line)
        precleaned.append(line)

    rows = list(csv.reader(precleaned))
    header = rows[0]
    expected_len = len(header)
    normalized_rows = [header]
    for row in rows[1:]:
        row = list(row)
        while len(row) > expected_len and "" in row:
            row.remove("")
        if len(row) < expected_len:
            row.extend([""] * (expected_len - len(row)))
        elif len(row) > expected_len:
            row = row[:expected_len]
        normalized_rows.append(row)
    return normalized_rows


def set_if(rows, header_index, dt_value, column, old_value, new_value):
    if column not in header_index:
        return
    for row in rows[1:]:
        if row[header_index["DateTimeUTC"]] == dt_value and row[header_index[column]] == old_value:
            row[header_index[column]] = new_value


def set_dt(rows, header_index, dt_value, column, new_value):
    if column not in header_index:
        return
    for row in rows[1:]:
        if row[header_index["DateTimeUTC"]] == dt_value:
            row[header_index[column]] = new_value


def apply_targeted_repairs(rows):
    header_index = {column: index for index, column in enumerate(rows[0])}

    set_if(rows, header_index, "2019-07-01 04:12:30", "AG04_kWh", "fourhundredsixtyone.678646", "461.678646")
    set_if(rows, header_index, "2019-07-01 04:13:40", "AG02_kWh", "threehundredfifty.921382", "350.921382")
    set_if(rows, header_index, "2019-07-01 04:31:00", "AG02_kWh", "was 250 now 350.939774", "350.939774")
    set_if(rows, header_index, "2019-07-01 04:38:00", "AG05_kWh", "correct:619.636666incorrect:320.64738", "619.636666")
    set_if(rows, header_index, "2019-07-01 04:42:20", "AG03_kWh", "382.5632 it is updated from 180.54638", "382.5632")
    set_if(rows, header_index, "2019-07-01 04:49:20", "AG07_kWh", "643.7465 previous value was 484.2894", "643.7465")
    set_if(rows, header_index, "2019-07-01 04:56:50", "AG04_kWh", "reached from 348.6489 to 461.796688", "461.796688")
    set_if(rows, header_index, "2019-07-01 05:09:20", "AG01_kWh", "this is correct 201.881899", "201.881899")
    set_if(rows, header_index, "2019-07-01 05:13:00", "AG17_kW", "0.3687:kvar:0.0566:kwh:1386.035392", "0.3687")
    set_dt(rows, header_index, "2019-07-01 05:13:00", "AG17_kVAR", "0.0566")
    set_dt(rows, header_index, "2019-07-01 05:13:00", "AG17_kWh", "1386.035392")
    set_if(rows, header_index, "2019-07-01 05:25:40", "AG26_kW", "1.5172:kvar:0.3338:kwh:4808.798893", "1.5172")
    set_dt(rows, header_index, "2019-07-01 05:25:40", "AG26_kVAR", "0.3338")
    set_dt(rows, header_index, "2019-07-01 05:25:40", "AG26_kWh", "4808.798893")
    set_if(rows, header_index, "2019-07-01 05:34:20", "AG02_kVAR", "0.0074:next-column-value:351.013151", "0.0074")
    set_dt(rows, header_index, "2019-07-01 05:34:20", "AG02_kWh", "351.013151")
    set_dt(rows, header_index, "2019-07-01 05:38:50", "AGs01To26_kWh", "1346.342003")
    set_if(rows, header_index, "2019-07-01 05:38:50", "AG01_kW", "previous-column-value:1346.342003:0.0355", "0.0355")
    set_if(rows, header_index, "2019-07-01 05:38:50", "AG01_kW", "previous-column-value-1346.342003--0.0355", "0.0355")
    set_dt(rows, header_index, "2019-07-01 05:49:50", "AG21_kWh", "1849.843554")
    set_if(rows, header_index, "2019-07-01 05:49:50", "AG22_kW", "21-kwh:1849.843554-0.2717", "0.2717")
    set_if(rows, header_index, "2019-07-01 05:51:20", "AG07_kWh", "644.001391:next-three-columns-values:0.2134:0.0988:657.807324", "644.001391")
    set_dt(rows, header_index, "2019-07-01 05:51:20", "AG08_kW", "0.2134")
    set_dt(rows, header_index, "2019-07-01 05:51:20", "AG08_kVAR", "0.0988")
    set_dt(rows, header_index, "2019-07-01 05:51:20", "AG08_kWh", "657.807324")
    set_dt(rows, header_index, "2019-07-01 05:53:10", "AG19_kW", "0.2845")
    set_if(rows, header_index, "2019-07-01 05:53:10", "AG19_kVAR", "KW:0.2845:0.0346", "0.0346")
    set_if(rows, header_index, "2019-07-01 06:26:40", "AG05_kWh", "619.902136:6kw:0.1916", "619.902136")
    set_dt(rows, header_index, "2019-07-01 06:26:40", "AG06_kW", "0.1916")
    set_if(rows, header_index, "2019-07-01 05:54:20", "AG18_kW", "0.3272:kvar:-0.0154", "0.3272")
    set_dt(rows, header_index, "2019-07-01 05:54:20", "AG18_kVAR", "-0.0154")
    set_if(rows, header_index, "2019-07-01 05:58:50", "AG18_kW", "AG-17-kwh:1386.302563|0.2935", "0.2935")
    set_dt(rows, header_index, "2019-07-01 05:58:50", "AG17_kWh", "1386.302563")

    if "AG08_kWh" in header_index:
        for row in rows[1:]:
            value = row[header_index["AG08_kWh"]]
            if "," not in str(value):
                continue
            part1, part2 = str(value).split(",", 1)
            row[header_index["AG08_kWh"]] = part1
            row[header_index["AG10_kWh"]] = row[header_index["AG10_kVAR"]]
            row[header_index["AG10_kVAR"]] = row[header_index["AG10_kW"]]
            row[header_index["AG10_kW"]] = row[header_index["AG09_kWh"]]
            row[header_index["AG09_kWh"]] = row[header_index["AG09_kVAR"]]
            row[header_index["AG09_kVAR"]] = row[header_index["AG09_kW"]]
            row[header_index["AG09_kW"]] = part2


def load_clean_rows():
    rows = read_messy_csv(DATA_PATH)
    apply_targeted_repairs(rows)
    header = rows[0]
    cleaned_rows = []
    for raw_row in rows[1:]:
        row = {}
        for index, column in enumerate(header):
            value = raw_row[index] if index < len(raw_row) else ""
            row[column] = clean_datetime(value) if column == "DateTimeUTC" else clean_number(value)
        cleaned_rows.append(row)

    cleaned_rows.sort(key=lambda row: row["DateTimeUTC"])
    deduped_rows = []
    seen_datetimes = set()
    for row in cleaned_rows:
        datetime_value = row["DateTimeUTC"]
        if datetime_value in seen_datetimes:
            continue
        seen_datetimes.add(datetime_value)
        deduped_rows.append(row)
    return deduped_rows


def compute_distribution_rows(rows):
    p_columns = [f"AG{i:02d}_kW" for i in range(1, 27)]
    q_columns = [f"AG{i:02d}_kVAR" for i in range(1, 27)]
    output_rows = []

    for row in rows:
        positives = [max(row[column], 0.0) for column in p_columns]
        abs_reactive = [abs(row[column]) for column in q_columns]
        sum_pos = sum(positives)
        sum_abs = sum(abs_reactive)

        norm_pos = [0.0] * 26 if sum_pos == 0.0 else [value / sum_pos for value in positives]
        norm_abs = [0.0] * 26 if sum_abs == 0.0 else [value / sum_abs for value in abs_reactive]

        cum_pos = []
        cum_abs = []
        running_pos = 0.0
        running_abs = 0.0
        for index in range(26):
            running_pos += norm_pos[index]
            running_abs += norm_abs[index]
            cum_pos.append(running_pos)
            cum_abs.append(running_abs)

        both_zero = sum_pos == 0.0 and sum_abs == 0.0
        w1 = 0.0 if both_zero else sum(abs(cum_pos[index] - cum_abs[index]) for index in range(25))
        tv = 0.0 if both_zero else 0.5 * sum(abs(norm_pos[index] - norm_abs[index]) for index in range(26))
        hellinger = 0.0 if both_zero else math.sqrt(0.5 * sum((math.sqrt(norm_pos[index]) - math.sqrt(norm_abs[index])) ** 2 for index in range(26)))
        mix = [0.5 * (norm_pos[index] + norm_abs[index]) for index in range(26)]
        js = 0.0
        if not both_zero:
            js = 0.5 * sum((norm_pos[index] * math.log(norm_pos[index] / mix[index]) if norm_pos[index] > 0.0 else 0.0) for index in range(26))
            js += 0.5 * sum((norm_abs[index] * math.log(norm_abs[index] / mix[index]) if norm_abs[index] > 0.0 else 0.0) for index in range(26))

        norm_pos_size = math.sqrt(sum(value * value for value in norm_pos))
        norm_abs_size = math.sqrt(sum(value * value for value in norm_abs))
        dot_product = sum(left * right for left, right in zip(norm_pos, norm_abs))
        if both_zero:
            cosine_dist = 0.0
        elif norm_pos_size == 0.0 or norm_abs_size == 0.0:
            cosine_dist = 1.0
        else:
            cosine_dist = 1.0 - dot_product / (norm_pos_size * norm_abs_size)

        entropy_scale = math.log(26.0)
        entropy_pos = sum((-value * math.log(value)) if value > 0.0 else 0.0 for value in norm_pos) / entropy_scale
        entropy_abs = sum((-value * math.log(value)) if value > 0.0 else 0.0 for value in norm_abs) / entropy_scale
        hhi_gap = sum(value * value for value in norm_pos) - sum(value * value for value in norm_abs)

        enriched = dict(row)
        enriched["TV_P_vs_absQ"] = round6(tv)
        enriched["JS_P_vs_absQ"] = round6(js)
        enriched["Hellinger_P_vs_absQ"] = round6(hellinger)
        enriched["CosineDist_P_vs_absQ"] = round6(cosine_dist)
        enriched["Entropy_posP"] = round6(entropy_pos)
        enriched["Entropy_absQ"] = round6(entropy_abs)
        enriched["HHI_gap"] = round6(hhi_gap)
        enriched["sum_posP"] = round6(sum_pos)
        enriched["sum_absQ"] = round6(sum_abs)
        enriched["W1_P_vs_absQ"] = round6(w1)
        output_rows.append(enriched)

    return output_rows


def write_power_csv(rows):
    header = list(rows[0].keys())
    metric_columns = [
        "TV_P_vs_absQ",
        "JS_P_vs_absQ",
        "Hellinger_P_vs_absQ",
        "CosineDist_P_vs_absQ",
        "Entropy_posP",
        "Entropy_absQ",
        "HHI_gap",
        "sum_posP",
        "sum_absQ",
        "W1_P_vs_absQ",
    ]
    original_columns = [column for column in header if column not in metric_columns]
    ordered_columns = original_columns + metric_columns

    with OUTPUT_CSV.open("w", encoding="utf-8", newline="") as handle:
        writer = csv.writer(handle)
        writer.writerow(ordered_columns)
        for row in rows:
            values = []
            for column in ordered_columns:
                value = row[column]
                if isinstance(value, float):
                    values.append(f"{value:.6f}")
                else:
                    values.append(value)
            writer.writerow(values)


def build_extreme_rows(rows, reverse=False):
    ordered = sorted(rows, key=lambda row: ((-row["W1_P_vs_absQ"]) if reverse else row["W1_P_vs_absQ"], row["DateTimeUTC"]))[:5]
    return [
        {
            "DateTimeUTC": row["DateTimeUTC"],
            "W1_P_vs_absQ": row["W1_P_vs_absQ"],
            "sum_posP": row["sum_posP"],
            "sum_absQ": row["sum_absQ"],
            "TV_P_vs_absQ": row["TV_P_vs_absQ"],
            "JS_P_vs_absQ": row["JS_P_vs_absQ"],
        }
        for row in ordered
    ]


def build_largest_shift_rows(rows):
    shifts = []
    for index in range(1, len(rows)):
        delta = round6(rows[index]["W1_P_vs_absQ"] - rows[index - 1]["W1_P_vs_absQ"])
        shifts.append(
            {
                "DateTimeUTC": rows[index]["DateTimeUTC"],
                "delta_W1": delta,
                "abs_delta_W1": round6(abs(delta)),
            }
        )
    return sorted(shifts, key=lambda row: (-row["abs_delta_W1"], row["DateTimeUTC"]))[:5]


def build_summary(rows):
    values = [row["W1_P_vs_absQ"] for row in rows]
    tv_values = [row["TV_P_vs_absQ"] for row in rows]
    js_values = [row["JS_P_vs_absQ"] for row in rows]
    hellinger_values = [row["Hellinger_P_vs_absQ"] for row in rows]
    cosine_values = [row["CosineDist_P_vs_absQ"] for row in rows]
    entropy_gap = [row["Entropy_posP"] - row["Entropy_absQ"] for row in rows]
    sum_pos_values = [row["sum_posP"] for row in rows]
    sum_abs_values = [row["sum_absQ"] for row in rows]
    sorted_values = sorted(values)
    rows_count = len(rows)

    deltas = [values[index] - values[index - 1] for index in range(1, rows_count)]
    rolling_means = []
    rolling_stds = []
    for index in range(rows_count):
        window = values[max(0, index - 23) : index + 1]
        window_mean = sum(window) / len(window)
        rolling_means.append(window_mean)
        rolling_stds.append(math.sqrt(sum((value - window_mean) ** 2 for value in window) / len(window)))

    p33 = quantile_linear(values, 0.33)
    p66 = quantile_linear(values, 0.66)
    summary = {
        "rows": rows_count,
        "w1_stats": {
            "min": round6(min(values)),
            "max": round6(max(values)),
            "mean": round6(sum(values) / rows_count),
            "std_pop": round6(math.sqrt(sum((value - (sum(values) / rows_count)) ** 2 for value in values) / rows_count)),
            "p01": round6(quantile_linear(values, 0.01)),
            "p50": round6(quantile_linear(values, 0.50)),
            "p99": round6(quantile_linear(values, 0.99)),
        },
        "shape_diagnostics": {
            "gini": round6(gini(sorted_values)),
            "theil_T": round6(theil_t(values)),
            "lorenz_area": round6(lorenz_area(sorted_values)),
            "top10_share": round6(top10_share(sorted_values)),
        },
        "distance_diagnostics": {
            "tv_mean": round6(sum(tv_values) / rows_count),
            "js_mean": round6(sum(js_values) / rows_count),
            "hellinger_mean": round6(sum(hellinger_values) / rows_count),
            "cosine_dist_mean": round6(sum(cosine_values) / rows_count),
            "w1_js_corr": round6(safe_corr(values, js_values)),
            "w1_entropy_gap_corr": round6(safe_corr(values, entropy_gap)),
        },
        "temporal_diagnostics": {
            "mean_abs_delta_w1": round6(sum(abs(delta) for delta in deltas) / len(deltas)),
            "max_abs_delta_w1": round6(max(abs(delta) for delta in deltas)),
            "lag1_autocorr_w1": round6(safe_corr(values[1:], values[:-1])),
            "w1_trend_slope_per_row": round6(safe_slope(values)),
            "rolling24_mean_of_mean": round6(sum(rolling_means) / len(rolling_means)),
            "rolling24_mean_of_std": round6(sum(rolling_stds) / len(rolling_stds)),
        },
        "energy_balance": {
            "mean_sum_posP": round6(sum(sum_pos_values) / rows_count),
            "mean_sum_absQ": round6(sum(sum_abs_values) / rows_count),
            "mean_posP_to_absQ_ratio": round6(sum((left / right) if right != 0.0 else 0.0 for left, right in zip(sum_pos_values, sum_abs_values)) / rows_count),
            "corr_sum_posP_sum_absQ": round6(safe_corr(sum_pos_values, sum_abs_values)),
        },
        "regime_counts": {
            "low": sum(1 for value in values if value <= p33),
            "mid": sum(1 for value in values if p33 < value <= p66),
            "high": sum(1 for value in values if value > p66),
        },
        "lowest_5": build_extreme_rows(rows, reverse=False),
        "highest_5": build_extreme_rows(rows, reverse=True),
        "largest_w1_shifts": build_largest_shift_rows(rows),
    }
    return summary


def main():
    clean_rows = load_clean_rows()
    enriched_rows = compute_distribution_rows(clean_rows)
    write_power_csv(enriched_rows)
    summary = build_summary(enriched_rows)
    with OUTPUT_JSON.open("w", encoding="utf-8") as handle:
        json.dump(summary, handle, separators=(",", ":"))
    print(json.dumps(summary, separators=(",", ":")))


if __name__ == "__main__":
    main()
```

**Reasoning**

The raw file is not directly analyzable because it includes extra leading and trailing commas, trailing empty columns, HTML-like noise, OCR substitutions, spelled-out digits, embedded corrections inside numeric cells, and several row-specific packed-value errors. The solution first normalizes CSV row width, applies the required file-specific repairs, then performs deterministic datetime and numeric cleaning. After stable datetime sorting and first-duplicate removal, it computes the normalized positive-active-power and absolute-reactive-power distributions for `AG01` through `AG26`, derives `W1`, `TV`, `JS`, `Hellinger`, cosine distance, entropies, HHI gap, and the two row sums, writes `power_with_w1.csv`, and then builds the requested inequality, correlation, temporal, regime, and extreme-row summaries for `summary.json`.

```json
{"rows":1001,"w1_stats":{"min":0.469988,"max":2.580901,"mean":1.438702,"std_pop":0.334645,"p01":0.713571,"p50":1.409517,"p99":2.296121},"shape_diagnostics":{"gini":0.130025,"theil_T":0.027032,"lorenz_area":0.434987,"top10_share":0.146505},"distance_diagnostics":{"tv_mean":0.216737,"js_mean":0.043652,"hellinger_mean":0.212672,"cosine_dist_mean":0.074759,"w1_js_corr":0.440247,"w1_entropy_gap_corr":-0.108255},"temporal_diagnostics":{"mean_abs_delta_w1":0.124866,"max_abs_delta_w1":0.889744,"lag1_autocorr_w1":0.861142,"w1_trend_slope_per_row":0.000342,"rolling24_mean_of_mean":1.428553,"rolling24_mean_of_std":0.191716},"energy_balance":{"mean_sum_posP":9.572673,"mean_sum_absQ":1.551397,"mean_posP_to_absQ_ratio":6.183543,"corr_sum_posP_sum_absQ":0.703309},"regime_counts":{"low":331,"mid":330,"high":340},"lowest_5":[{"DateTimeUTC":"2019-07-01 04:39:30","W1_P_vs_absQ":0.469988,"sum_posP":10.2993,"sum_absQ":1.781,"TV_P_vs_absQ":0.191487,"JS_P_vs_absQ":0.029688},{"DateTimeUTC":"2019-07-01 04:38:50","W1_P_vs_absQ":0.620062,"sum_posP":9.9883,"sum_absQ":1.6489,"TV_P_vs_absQ":0.195235,"JS_P_vs_absQ":0.029788},{"DateTimeUTC":"2019-07-01 05:06:00","W1_P_vs_absQ":0.620783,"sum_posP":9.8849,"sum_absQ":1.7216,"TV_P_vs_absQ":0.160917,"JS_P_vs_absQ":0.026155},{"DateTimeUTC":"2019-07-01 04:40:00","W1_P_vs_absQ":0.63104,"sum_posP":9.9238,"sum_absQ":1.5897,"TV_P_vs_absQ":0.177245,"JS_P_vs_absQ":0.026379},{"DateTimeUTC":"2019-07-01 04:39:50","W1_P_vs_absQ":0.633985,"sum_posP":9.9216,"sum_absQ":1.5799,"TV_P_vs_absQ":0.194868,"JS_P_vs_absQ":0.027801}],"highest_5":[{"DateTimeUTC":"2019-07-01 06:46:30","W1_P_vs_absQ":2.580901,"sum_posP":8.103,"sum_absQ":1.2616,"TV_P_vs_absQ":0.282577,"JS_P_vs_absQ":0.062567},{"DateTimeUTC":"2019-07-01 06:46:20","W1_P_vs_absQ":2.474773,"sum_posP":8.1414,"sum_absQ":1.3205,"TV_P_vs_absQ":0.274648,"JS_P_vs_absQ":0.058715},{"DateTimeUTC":"2019-07-01 05:56:30","W1_P_vs_absQ":2.472453,"sum_posP":9.2036,"sum_absQ":1.3885,"TV_P_vs_absQ":0.254662,"JS_P_vs_absQ":0.056279},{"DateTimeUTC":"2019-07-01 06:45:40","W1_P_vs_absQ":2.453699,"sum_posP":8.424,"sum_absQ":1.5164,"TV_P_vs_absQ":0.261773,"JS_P_vs_absQ":0.05776},{"DateTimeUTC":"2019-07-01 06:42:50","W1_P_vs_absQ":2.407195,"sum_posP":8.4388,"sum_absQ":1.5776,"TV_P_vs_absQ":0.229541,"JS_P_vs_absQ":0.045078}],"largest_w1_shifts":[{"DateTimeUTC":"2019-07-01 05:49:10","delta_W1":0.889744,"abs_delta_W1":0.889744},{"DateTimeUTC":"2019-07-01 05:49:20","delta_W1":-0.792486,"abs_delta_W1":0.792486},{"DateTimeUTC":"2019-07-01 06:43:50","delta_W1":-0.786041,"abs_delta_W1":0.786041},{"DateTimeUTC":"2019-07-01 06:11:20","delta_W1":-0.714956,"abs_delta_W1":0.714956},{"DateTimeUTC":"2019-07-01 04:16:30","delta_W1":0.706312,"abs_delta_W1":0.706312}]}
```