第十七章 演示日
林知行出门前检查了三遍U盘。
第一遍确认demo程序在里面,第二遍确认数据文件在里面,第三遍确认备份的安装包在里面。U盘是方小满昨天在电脑城买的,16G,银灰色外壳,花了三十五块。方小满说买个好的,别到时候读不出来。林知行觉得三十五块够了,能存东西就行。
他们约了上午十点。张副总发来消息说张老板亲自来看,让林知行把东西准备充分。
"准备充分"四个字让林知行多检查了一遍笔记本电脑的电量。充满的,百分之百。他又把电源适配器塞进背包,以防万一。
方小满换了一件干净的polo衫,头发还打了点发胶。他平时穿拖鞋上课,今天蹬了一双白球鞋,鞋面擦得锃亮。
"走吧。"方小满拍了拍他的肩。
林知行背上背包,跟在他后面出了宿舍楼。
十一月初的早上,空气里有桂花的尾调。梧桐树的叶子黄了一半,风一吹,簌簌地掉。他们走到校门口,叫了一辆网约车。方小满坐副驾驶,跟司机聊了两句天气,林知行坐在后排,膝盖上的手一直攥着。
"紧张?"方小满从后视镜看他。
"不紧张。"林知行说。
"手都攥白了。"
林知行低头看了一眼,松开手,手指有点僵。他活动了两下,把目光移向车窗外。
街上的行人不多,路边的早餐店冒着白气,蒸笼里的包子叠了三层高。一个穿校服的女孩背着大书包从人行道跑过去,书包上的卡通挂件一颠一颠的。
他忽然想起小时候,父亲带他去县城报名参加数学竞赛。那天也是一大早出门,父亲骑摩托车,他坐在后面,风灌进衣领里。父亲一句话都没说,到了学校门口才回头看了他一眼,说了三个字:好好考。
那年他拿了县二等奖。父亲在镇上的饭馆请人吃了一顿,喝了三瓶啤酒,回家路上摔了一跤,膝盖上留了一块疤。
车停了。
张氏教育的总部在一栋六层的商务楼里,外墙贴着浅灰色的瓷砖,大门口立着一块招牌,白底蓝字,写着"张氏教育集团"。门口停了两辆黑色轿车,一辆是别克,一辆是帕萨特。
林知行下车,站在楼前,仰头看了一眼。
上次来是面试的时候,那时候他紧张得手心冒汗,但那时候只是面谈,今天是真刀真枪地演示。上次张副总一个人看,今天张老板亲自来。
"走。"方小满在前面带路。
他们上了三楼,张副总已经在会议室门口等着了。今天他穿了一件深蓝色的衬衫,袖口卷到手肘,脸上带着笑,但眼神比上次严肃。
"准备好了?"张副总问。
"准备好了。"林知行说。
张副总点点头,推开会议室的门。
会议室不大,长方形,中间一张椭圆形的桌子,围着八把椅子。桌上摆着一台投影仪,旁边放着几瓶矿泉水。墙上挂着一面锦旗和几张合影照片,照片里的人穿着统一的蓝色工服,背景是某个开业典礼。
张老板坐在桌子的另一头。
他五十出头,比张副总老一些,头发花白了一半,脸上的皱纹很深,但眼睛很亮,看人的时候带着一种审视的味道。他穿了一件灰色的夹克,里面是深色的毛衣,桌上放着一部翻盖手机,不是智能机。
旁边还坐着一个女人,四十岁左右,短发,戴眼镜,面前摊着一个笔记本,手里攥着一支笔。张副总介绍说是教务主管,姓刘,管着三十七个校区的教务调度。
林知行把笔记本电脑接上投影仪,插上U盘,打开demo程序。
程序弹出一行红字:KeyError: 'teacher_id'。
他的心跳漏了一拍。
他盯着那行错误信息看了三秒,脑子飞速运转。teacher_id字段缺失——某个校区的教师数据格式和其他校区不一致,导致模型在读取数据时报错。他检查了数据文件,是城北校区的教师表,编号字段用的是"工号"而不是"teacher_id"。
差一个字段名。
他在两周的数据清洗中漏掉了这个校区。
张副总在角落里拧开了矿泉水瓶盖,喝了一口,又拧上。刘主管翻了一页笔记本,没有抬头。张老板的目光从屏幕移到他脸上,又移回屏幕。
林知行蹲到会议室角落,把笔记本电脑放在膝盖上,打开代码编辑器。他的手心开始出汗,指尖在触摸板上滑了两下才点准位置。他找到数据预处理模块,加了一行格式转换代码,把"工号"映射成"teacher_id"。
保存。运行。
错误消失了。
他看了一眼时间。八分钟。
他站起来,走回投影仪前,把笔记本电脑重新接上。额头上有一层薄汗,衬衫后背湿了一块,贴在皮肤上。
投影幕布亮了。
他深吸一口气,开始演示。
"各位好,我叫林知行,今天给大家演示的是排课冲突检测系统。"他的声音比自己预想的要稳,"这个系统分为三个模块——初排、调整、评价。我先演示初排模块……"
他点开第一个页面,屏幕上出现一张表格,左侧是教师名单,上方是时间段,表格里填满了不同颜色的色块,代表不同的课程。
"初排模块只处理硬约束——同一位老师不能在同一时间段上两节课,同一间教室不能同时安排两个班。"他指着屏幕,"我用三个校区的数据做了一次初排,结果是这样的——"
他点了"运行"按钮。
屏幕上弹出一个进度条,开始走动。
会议室里很安静。张老板靠在椅背上,两手交叉放在胸前,眼睛盯着屏幕。刘主管低头在笔记本上写着什么。张副总站在角落,手里拿着一瓶矿泉水,没有拧开。
进度条走到100%。
屏幕上的表格刷新了,所有时间段都填满了,没有空白。
"初排完成率98.7%。"林知行说,"有三个时间段因为教室数量不足没有排上,我后面会讲怎么处理。"
他切换到第二个页面。
"调整模块处理强软约束——老师的时间偏好、通勤距离、资质要求。如果有冲突,系统会生成冲突报告。"
他又点了"运行"。
这次进度条走得更快,十几秒就到了底。
屏幕上弹出一份报告,列着23条冲突,每条都标注了涉及的老师和时间段。
"23条冲突。"林知行说,"大部分是老师时间偏好的冲突。比如第七条——王老师希望周二上午没课,但周二上午的教室空着,系统建议安排,就产生了冲突。这种冲突需要人工协调。"
刘主管抬头看了他一眼,眼神里有一点意外。
"最后是评价模块。"林知行切换到第三个页面,"弱软约束——通勤距离最短、教师工作量均衡、学生选课满意度。系统会对调整后的方案打分,满分100分。"
他点了运行。
几秒钟后,屏幕上显示:综合评分87.3分。
"87分。"林知行说,"不算高,但基本可用。如果你们觉得哪个方面不满意,我可以调整权重参数,优化评分。"
他停下来,看着张老板。
张老板没有说话,眼睛还是盯着屏幕。
刘主管翻了翻笔记本,开口了:"我试一下。"
她站起来,走到电脑前,指着屏幕上的一个校区:"城南校区。初二年级。我给你加一个条件——张老师只能上上午的课,她下午要接孩子。李老师不能和赵老师排在同一节课,他们去年因为绩效的事情闹过矛盾,搭班会出事。还有,周四下午全校教研活动,所有老师都不能排课。"
林知行愣了一下。
他没有预料到刘主管会提出这种条件。前两个还行——时间偏好和时间段排除,他的模型能处理。但第三个——"李老师不能和赵老师排在同一节课"——这不是硬约束,也不是软约束。这是人际关系约束。
他的模型里没有这个变量。
他看了一眼张老板,张老板的表情没有变化。
刘主管推了推眼镜,看着他:"能做到吗?"
林知行的脑子转得飞快。
他的模型确实处理不了人际约束——在他的算法里,老师和老师之间没有关系,只有老师和时间段、老师和教室、老师和课程之间的关系。但刘主管提的这个条件本质上是一条额外的硬约束:两个变量不能出现在同一个解空间里。
他可以在白板上手动加一条约束,然后重新跑一遍。
"给我两分钟。"他说。
他走到白板前,拿起笔,在排课模型的流程图旁边画了一个方框,写上"人际约束"。然后在方框里画了两条线,一条连到"初排模块",一条连到"调整模块"。
"这条约束加在调整模块里。"他指着白板,"系统在调整阶段会检查每一对老师的排课组合,如果两个人之间存在标记为'不兼容'的关系,就把他们的课安排在不同的时间段。"
他转回电脑前,打开代码编辑器,当着所有人的面,加了一个if语句。
张副总在角落里皱起眉头。
林知行没管他,保存代码,重新运行。
进度条走了二十秒。
结果出来了。张老师只排了上午的课,李老师和赵老师没有出现在同一节课里,周四下午所有时间段都标记为"教研活动"。
刘主管走到屏幕前,把结果检查了一遍,嘴角动了一下。
"小伙子反应挺快。"她说。
张老板这时候开口了。他的声音很平,听不出高兴还是不高兴。
"这个东西,做出来多少钱?"
林知行转过头来看他。
他想过这个问题。张副总说过,做出来才有资格谈钱。他之前没报价,因为他不确定能不能做出来。现在demo跑通了,他得给一个数字。
"一万五。"他说。
方小满在旁边差点呛到。
这个数字是林知行昨晚想的。他查了同类产品的市场价——SaaS排课系统年费在十万到二十万之间,定制化开发更贵。一万五是一个极低的价格,但他没有名气,没有案例,没有团队,只有一份demo。他觉得一万五已经不低了。
张老板看了他一眼,转头对张副总说:"先付五千。做好了再付一万。"
张副总点头,从口袋里掏出一张支票本。
林知行的手又攥紧了。
出了会议室,方小满在楼道里一把抱住他,蹦了两下。
"一万五!操!一万五!"
林知行把他推开,靠在墙上,没有说话。
"你怎么不高兴?"方小满看着他的脸,"刚才多牛逼啊,当着人家面改代码,刘主管都笑了——"
"那条人际约束。"林知行说。
方小满愣了一下:"啥?"
"刘主管说的那条——李老师不能和赵老师排在同一节课。"林知行看着天花板,"我的模型里没有这个东西。案例库里没有,方法论里也没有。"
"那不是加上去了吗?"
"加是加上去了。"林知行的声音低了下去,"但那是她提前告诉我的。如果她没说呢?如果排完课之后才发现张老师和李老师闹过矛盾呢?"
方小满张了张嘴,没接上话。
林知行从裤兜里掏出手机,打开备忘录,打了一行字:
"人的问题。补。"
他盯着那四个字看了一会儿,把手机揣回兜里。
方小满看着他,忽然笑了:"你这人,刚拿了五千块,就在这儿焦虑。"
"五千是预付款。"林知行说,"后面还有一万,做不好就没了。"
"那你做呗。"方小满拍了拍他的肩,"走,请你吃碗粉。"
他们下了楼,走到街对面的一家米粉店。方小满要了两碗牛肉粉,加了辣椒和酸豆角。热气从碗里升起来,带着牛油和辣椒的香味。
林知行吃了一口粉,味道很好,但他吃得心不在焉。
他在想那条人际约束。
在他的算法里,老师是变量,时间段是约束,排课是一个求解过程。变量之间只有逻辑关系——A和B不能同时出现在同一个解里。但现实中,变量和变量之间还有感情关系——A和B闹过矛盾,C和D是亲戚,E和F去年一起带过班配合很好。这些东西不能用if语句穷举,因为它们是动态的、模糊的、随时可能变化的。
今天刘主管帮他列举了三条人际约束,但整个张氏教育有上千名老师。老师和老师之间的关系是一个完全图,边的数量是O(n²)级别的。他不可能把所有的人际关系都录入系统——先不说数据量,光是信息来源就是一个问题:谁来告诉他哪两个老师闹过矛盾?
这不是技术问题。
这是人的问题。
方小满在对面嗦粉,嗦得很响。林知行放下筷子,看着他:"你觉得,张老板会怎么看刚才的演示?"
方小满抬起头,想了想:"反正支票都签了,应该还行吧?"
"他没笑。"林知行说,"从头到尾,他没笑过。"
"有些人就是不笑。"方小满说,"我爸的战友老赵也是这种人,板着脸不代表不高兴。他要是真不满意,不会签支票的。"
林知行想了想,觉得有道理。
但他心里还是不踏实。
他掏出手机,看了看备忘录里的那行字:"人的问题。补。"
他又加了一行:"人际约束——动态、模糊、O(n²)量级——怎么采集?怎么建模?怎么更新?"
他把手机放回桌上,端起碗,把剩下的粉吃完了。
汤很烫,一直烫到胃里。
走出米粉店的时候,方小满说:"回去把支票拍个照发朋友圈?"
"不发。"林知行说。
"为啥?"
"等做完了再发。"
方小满撇了撇嘴,没再说话。
他们走到公交站台,等车。站台上贴着一张房地产广告,上面写着"首付十八万,安家在这座城"。林知行看了一眼那行字,把目光移开了。
公交车来了,他们上了车,找了两个靠窗的位置坐下。
车窗外的街景慢慢后退。梧桐树、早餐店、五金店、彩票站、一个推着三轮车卖红薯的老人。这个城市的上午很安静,安静得能听见公交车引擎的嗡嗡声。
方小满靠着椅背,打了个盹。
林知行没睡。他掏出手机,翻到上周的项目笔记。上面记着排课系统的技术方案,每个模块的功能、接口、数据格式。他翻到最后一页,在空白处写了几行字:
"待解决:人际约束的采集方式。方案1——问卷调查,让老师填写不愿意搭班的人。问题:数据可能不准确,谁愿意承认自己跟同事有矛盾?方案2——从历史排课数据中挖掘。问题:历史数据不一定反映真实关系。方案3——教务主管人工标注。问题:扩展到37个校区,工作量太大。"
他把三种方案看了两遍,把手机锁屏了。
三种方案都有问题,都不完美。
公交车到站了。他们下了车,走进校门。方小满在后面伸了个懒腰:"今天中午你请客啊,拿了预付款。"
"行。"林知行说。
他走进宿舍,把背包放下,打开电脑,没有打开demo程序,而是打开了一个新的文件夹。
他新建了一个文档,文件名叫"人际约束模块_v1"。
然后他盯着空白的文档,坐了很久。
屏幕上的光标一闪一闪,像一个等待输入的变量。
他不知道这个模块应该怎么做。
但他知道,它必须做出来。
窗外的阳光斜照进来,落在键盘上,把每个字母的边缘都镀上了一层金色。方小满已经爬上了床,翻了个身,开始打呼噜。
林知行把手放在键盘上,敲下了第一行字:
"人际约束——需求定义。"