"""Synthetic classification workflow; assertions verify behavior, not real-world quality."""
import numpy as np
import pandas as pd
import sklearn
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, precision_recall_fscore_support
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
x = pd.DataFrame({'age':[20,25,30,np.nan,45,50,55,60,22,27,32,37,42,47,52,57],
                  'city':['Seoul','Busan']*8})
y = np.array([0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1])
train, test = train_test_split(np.arange(len(y)), test_size=0.25, random_state=7, stratify=y)
preprocess = ColumnTransformer([
    ('number', Pipeline([('impute', SimpleImputer(strategy='median')), ('scale', StandardScaler())]), ['age']),
    ('category', Pipeline([('impute', SimpleImputer(strategy='most_frequent')), ('encode', OneHotEncoder(handle_unknown='ignore'))]), ['city'])])
model = Pipeline([('prepare', preprocess), ('classify', LogisticRegression(random_state=7))])
model.fit(x.iloc[train], y[train])
actual_median = model.named_steps['prepare'].named_transformers_['number'].named_steps['impute'].statistics_[0]
assert actual_median == np.nanmedian(x.iloc[train]['age'])
prediction = model.predict(x.iloc[test])
assert prediction.shape == (4,)
assert confusion_matrix(y[test], prediction, labels=[0,1]).sum() == 4
p,r,f,_ = precision_recall_fscore_support(y[test], prediction, average='binary', zero_division=0)
unknown = pd.DataFrame({'age':[np.nan], 'city':['Daejeon']})
assert model.predict(unknown).shape == (1,)
try:
    model.predict(unknown.drop(columns='age'))
except ValueError:
    pass
else:
    raise AssertionError('missing required column was accepted')
print(f'PASS: train-only preprocessing, unseen category, missing-column rejection; sklearn {sklearn.__version__}')
print(f'Synthetic held-out n=4: precision={p:.3f}, recall={r:.3f}, F1={f:.3f}; not a deployment score')
