Hotelling's T2 multivariate control chart. Charts a matrix of correlated variables per observation, not a single value series. Mean vector and covariance matrix are computed from chkItemEvent data (Phase I - the baseline is the same data being charted). UCL uses the chi-square approximation (valid once the baseline sample is reasonably large), not the exact F-distribution - simpler, no incomplete-beta-function implementation needed, and standard practice in most commercial SPC software for this case.

`variableNames` and `alpha` are required parameters with no default value in the signature - pass `None` explicitly to get the default behavior described below. This is deliberate: the module is a candidate for a future Java port, and Java has no native optional-parameter support.

---
### Parameters
**rows** `list[list[float]]`: one row per observation (e.g. one inspected unit/event), each row holding the same p variables in the same order - e.g. `[[length, width, height], [length, width, height], ...]`. All rows must be the same length; missing/None values aren't supported (filter incomplete rows out before calling).<br />
**variableNames** `List of Strings`: p names for labeling. Pass `None` to auto-label `Var1..Varp`.<br />
**alpha** `Float`: false-alarm rate for the UCL. Pass `None` for the default, 0.0027, which matches the ~3-sigma-equivalent Type I error rate used for the univariate Shewhart charts elsewhere in this module, so a T2 chart's out-of-control rate is comparable to what users already expect from individuals/xbar/etc.<br />

---
### Returns
**results** `Dictionary`<br />
**chartType** `String` i.e. 't2'<br />
**error** `String`<br />
**p** `Integer` variable count<br />
**n** `Integer` observation count<br />
**variableNames** `List of Strings`<br />
**mean** `List of Float values` the p-dim mean vector<br />
**covariance** `list[list[float]]` the p x p sample covariance matrix<br />
**points** `List of Float values` T2 value per observation<br />
**ucl** `Float` chi-square-approximation control limit<br />
**aboveUCL** `List of Integers` indices of observations exceeding ucl<br />
**outOfControl** `Boolean`<br />

---
### Examples

#### In-control baseline

```python
rows = [
	[100.4, 50.2], [98.6, 50.3], [100.2, 50.1], [96.0, 50.4], [109.8, 49.9],
	[102.1, 50.0], [103.2, 49.8], [97.5, 50.5], [101.0, 50.0], [99.8, 50.2],
	[100.5, 49.9], [98.9, 50.3], [102.4, 49.8], [99.1, 50.4],
]
variableNames = ["Length", "Width"]
result = system.kanoa.quality.spc.spcCheckT2(rows, variableNames, None)

# result["p"]          = 2
# result["n"]          = 14
# result["mean"]       = [100.679, 50.129]
# result["covariance"] = [[10.594, -0.559], [-0.559, 0.055]]
# result["ucl"]        = 11.983
# result["points"]     = [0.136, 0.56, 0.138, 2.09, 10.417, 0.305, 2.13, 2.614, 0.508, 0.098, 2.271, 0.539, 2.542, 1.652]
# result["aboveUCL"]   = []
# result["outOfControl"] = False
```

Note `mean`/`covariance` are computed from these same 14 rows (Phase I) - Length and Width happen to be fairly strongly negatively correlated here (-0.559 covariance), which is exactly what lets T2 catch a point that breaks that relationship, below.

#### Adding a correlation-breaking observation

```python
rows.append([115.0, 51.5])   # both Length AND Width move up together - breaks the negative correlation
result = system.kanoa.quality.spc.spcCheckT2(rows, variableNames, None)

# result["n"]            = 15
# result["points"][-1]   = 12.218   (exceeds ucl = 11.983)
# result["aboveUCL"]     = [14]
# result["outOfControl"] = True
```

Notice the new point isn't extreme on either variable by itself (Length=115 and Width=51.5 are each only a couple of points away from their own means) - it's flagged because the *combination* moves against the established relationship between the two, which is precisely what a pair of independent univariate charts on Length and Width individually would miss.
