严格意义上来说从零搭建个人网站已经完成了,后续应该是不断添加功能和维护的过程。以后的标题就改成现在这样,有样学样记录一下版本号。

因为我租的服务器规模比较小(单核小内存),带博客系统和内置AI应该很有压力,所以只使用比较简单的结构:

后端: FastAPI + Gunicorn + PostgreSQL,前端: 静态HTML/JavaScript(由Nginx直接服务),反向代理: Nginx(HTTPS终止 + 静态文件服务)

当然,是某AI模型帮我设计的(狗头)。至于模型的名字大家看页面排版就知道。

这次想加入的是一个统计方法建议的新功能。一般在分析实验数据的时候,要先看分组数和样本量,然后看分布、方差齐性,再决定使用的统计方法。看来看去有点费时间,还不一定正确。有时候判断流程和标准记不清还要检查。如果有这样一个工具可以检查数据,输出推荐的或者符合统计学规则的检验方法,就非常实用和方便了。

一、告诉大模型我的要求:

思考片刻,大模型就告诉了我推荐的方案,甚至还有确认细节的步骤:

二、开工!

上传之前保存的md运维文档,上传文件路径和主页html文档,告诉大模型我是不会敲代码的非专业人员,让他带我在Powershell中操作。大模型很贴心地帮我检查Python包、目录结构等等,完善准备工作。

后端Python分析:

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import scipy.stats as stats

router = APIRouter(prefix="/api/stats", tags=["stats"])

class StatsRequest(BaseModel):
    groups: List[List[float]]
    group_names: List[str]
    paired: bool = False

class StepResult(BaseModel):
    test_name: str
    details: str
    passed: bool

class StatsResponse(BaseModel):
    recommendation: str
    reason: str
    post_hoc: Optional[str]
    steps: List[StepResult]

@router.post("/analyze", response_model=StatsResponse)
def analyze(req: StatsRequest):
    groups = req.groups
    names = req.group_names
    paired = req.paired
    n_groups = len(groups)
    steps = []

    if n_groups < 2:
        raise HTTPException(status_code=400, detail="At least 2 groups are required.")

    ns = [len(g) for g in groups]

    if paired:
        if len(set(ns)) != 1:
            raise HTTPException(
                status_code=400,
                detail=f"Paired analysis requires equal sample sizes across all groups. "
                       f"Current sizes: {', '.join(f'{names[i]}: n={ns[i]}' for i in range(n_groups))}"
            )

    normality_results = []
    all_normal = True
    for i, g in enumerate(groups):
        if len(g) < 3:
            normality_results.append((names[i], None, None, False))
            all_normal = False
            steps.append(StepResult(
                test_name=f"Normality - {names[i]}",
                details=f"n={len(g)}: too few observations to run Shapiro-Wilk (minimum n=3). Assumed non-normal.",
                passed=False
            ))
        else:
            stat, p = stats.shapiro(g)
            normal = p > 0.05
            if not normal:
                all_normal = False
            normality_results.append((names[i], stat, p, normal))
            steps.append(StepResult(
                test_name=f"Normality - {names[i]}",
                details=f"Shapiro-Wilk: W={stat:.4f}, p={p:.4f} -> {'normal distribution' if normal else 'non-normal distribution'}",
                passed=normal
            ))

    if not all_normal:
        if n_groups == 2:
            if paired:
                return StatsResponse(
                    recommendation="Wilcoxon Signed-Rank Test",
                    reason=(
                        f"Decision path: 2 groups -> paired -> normality check failed "
                        f"({'; '.join(f'{r[0]}: p={r[2]:.4f}, non-normal' if r[2] is not None else f'{r[0]}: n too small' for r in normality_results)}). "
                        f"Non-parametric alternative to the paired t-test is recommended."
                    ),
                    post_hoc=None,
                    steps=steps
                )
            else:
                return StatsResponse(
                    recommendation="Mann-Whitney U Test",
                    reason=(
                        f"Decision path: 2 groups -> unpaired -> normality check failed "
                        f"({'; '.join(f'{r[0]}: p={r[2]:.4f}, non-normal' if r[2] is not None else f'{r[0]}: n too small' for r in normality_results)}). "
                        f"Non-parametric alternative to the independent t-test is recommended."
                    ),
                    post_hoc=None,
                    steps=steps
                )
        else:
            if paired:
                return StatsResponse(
                    recommendation="Friedman Test",
                    reason=(
                        f"Decision path: {n_groups} groups -> paired -> normality check failed "
                        f"({'; '.join(f'{r[0]}: p={r[2]:.4f}, non-normal' if r[2] is not None else f'{r[0]}: n too small' for r in normality_results)}). "
                        f"Non-parametric alternative to repeated measures ANOVA is recommended."
                    ),
                    post_hoc="Post-hoc: Wilcoxon Signed-Rank Test with Bonferroni correction for pairwise comparisons.",
                    steps=steps
                )
            else:
                return StatsResponse(
                    recommendation="Kruskal-Wallis Test",
                    reason=(
                        f"Decision path: {n_groups} groups -> unpaired -> normality check failed "
                        f"({'; '.join(f'{r[0]}: p={r[2]:.4f}, non-normal' if r[2] is not None else f'{r[0]}: n too small' for r in normality_results)}). "
                        f"Non-parametric alternative to one-way ANOVA is recommended."
                    ),
                    post_hoc="Post-hoc: Dunn's test with Bonferroni correction for pairwise comparisons.",
                    steps=steps
                )

    levene_stat, levene_p = stats.levene(*groups)
    equal_var = levene_p > 0.05
    steps.append(StepResult(
        test_name="Variance Homogeneity (Levene's Test)",
        details=f"Levene's test: W={levene_stat:.4f}, p={levene_p:.4f} -> {'equal variances' if equal_var else 'unequal variances'}",
        passed=equal_var
    ))

    normality_summary = "; ".join(
        f"{r[0]}: p={r[2]:.4f}, normal" for r in normality_results
    )

    if n_groups == 2:
        if paired:
            return StatsResponse(
                recommendation="Paired t-Test",
                reason=(
                    f"Decision path: 2 groups -> paired -> all groups normally distributed ({normality_summary}). "
                    f"Paired t-test is the appropriate parametric test."
                ),
                post_hoc=None,
                steps=steps
            )
        else:
            if equal_var:
                return StatsResponse(
                    recommendation="Independent Samples t-Test",
                    reason=(
                        f"Decision path: 2 groups -> unpaired -> all groups normally distributed ({normality_summary}) "
                        f"-> equal variances (Levene's p={levene_p:.4f}). "
                        f"Standard independent samples t-test is appropriate."
                    ),
                    post_hoc=None,
                    steps=steps
                )
            else:
                return StatsResponse(
                    recommendation="Welch's t-Test",
                    reason=(
                        f"Decision path: 2 groups -> unpaired -> all groups normally distributed ({normality_summary}) "
                        f"-> unequal variances (Levene's p={levene_p:.4f}). "
                        f"Welch's t-test does not assume equal variances and is recommended."
                    ),
                    post_hoc=None,
                    steps=steps
                )
    else:
        if paired:
            return StatsResponse(
                recommendation="Repeated Measures ANOVA",
                reason=(
                    f"Decision path: {n_groups} groups -> paired -> all groups normally distributed ({normality_summary}). "
                    f"Repeated measures ANOVA accounts for within-subject correlation."
                ),
                post_hoc="Post-hoc: Bonferroni-corrected pairwise t-tests for within-subject comparisons.",
                steps=steps
            )
        else:
            if equal_var:
                return StatsResponse(
                    recommendation="One-Way ANOVA",
                    reason=(
                        f"Decision path: {n_groups} groups -> unpaired -> all groups normally distributed ({normality_summary}) "
                        f"-> equal variances (Levene's p={levene_p:.4f}). "
                        f"One-way ANOVA is the standard parametric test for multiple independent groups."
                    ),
                    post_hoc="Post-hoc: Tukey's HSD test for all pairwise comparisons.",
                    steps=steps
                )
            else:
                return StatsResponse(
                    recommendation="Welch's One-Way ANOVA",
                    reason=(
                        f"Decision path: {n_groups} groups -> unpaired -> all groups normally distributed ({normality_summary}) "
                        f"-> unequal variances (Levene's p={levene_p:.4f}). "
                        f"Welch's ANOVA does not assume variance homogeneity."
                    ),
                    post_hoc="Post-hoc: Games-Howell test for pairwise comparisons (does not assume equal variances).",
                    steps=steps
                )

自学了一点点代码的我还是知道这一段是在干什么的,总结一下就是这样的一个决策流程:

(另一个很好用的大模型生成的流程图)

接下来更新main.py注册新功能,重启测试新路由,再设计一下前端页面html即可。html的代码就不贴上来了,比较长而且都是网页设计方面的东西。接着在主页放一个新功能的入口就完成啦!

别忘了让大模型帮我更新md运维文档,下次让他打工更方便。

网页观感不错,试了试功能基本可用,输出的结果应该比较靠谱。
感叹编程大模型的发展实在是迅速,没有代码功底的非专业人员,只要会复制粘贴也能做网站实现简单的功能了;而且还像模像样的。

三、换一种复制粘贴的方法

和大模型聊天的时候,他提到可以用VScode的ssh remote插件链接VPS服务器,鼠标点击操作文件和管理代码更加方便直观,还自带Terminal功能可以取代Powershell。

赶紧加入插件,按照步骤链接上了VPS。由于我有本地私钥,不用输密码打开VScode直连,体验相当棒!

我:你咋不早说?

大模型:你也没问啊,非要我带你用Powershell

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐