Portfolio View
📋 需求 ↔ 实现说明书 · Linear AXI-69~72 → 界面功能 → 计算口径
✅ 需求 ↔ API 覆盖对照(定版需求 → 当前接口字段 → 判定)
对照定版需求逐条看数据接口是否已提供。判定分三种:「满足」= 正好给到;「超额」= 给的比要求多(v1 只要一种 occupancy,我们返回了三种;只要 S0,我们返回了 S0/S1/S2)——多给无妨,前端用不到可忽略;「网关负责」= 这条由网关层按 X-Project-Ids 控制访问范围来实现,不经过数据接口,所以后端响应里本就不该有对应字段。总结:数据覆盖 ≈ 100%,大面积超额。
Linear需求(定版)当前 API 提供判定
AXI-69跨选中项目、按日历月相加收入/成本行catalog.py 9 个源,DB 侧 GROUP BY 逐月求和满足
AXI-69只加金额,比率 / IRR / per-party 不加每个 spec 只列 $ 列,rate/ratio 根本不进聚合满足
AXI-69active 窗口 = tb_deal 收购~处置,非活跃月贡献 0deal_window(date_of_closing~sale_date) + align 补 0满足
AXI-69时间轴按日历月、不归一化持有期build_timeline:最早收购~最晚处置满足
AXI-70NOI / Revenue / OpEx 逐月METRICS.NOI / REVENUE / OPERATING_EXPENSES满足
AXI-70Occupancy v1 只要物理口径METRICS.OCCUPANCY(预测·逐月·逐场景)满足
AXI-70经济口径推 v1.5METRICS.ECONOMIC_OCCUPANCY 已经返回超额
AXI-70rent-roll 快照 occupancy(可选)METRICS.RENT_ROLL_OCCUPANCY 已经返回超额
AXI-70只取持有期内 out_of_holding_period=0spec.py _OUT0 = coalesce(out_of_holding_period,0)=0满足
AXI-70CapEx 之上的计算 / 确认 NOI 序列是 pre-CapExNOI = NET_OPERATING_INCOME(标准 pre-CapEx),外加 CASH_FLOW.CAP_EX 单列也一并返回超额
AXI-70Scenario 锁定默认 case(S0),toggle 推 v1.5收入类源 scenarios=(0,1,2),S0/S1/S2 全部返回超额
AXI-71leasable SF(快照)CONTEXT_STRIP.leasableSf(Σ rent_roll_item.sqft)满足
AXI-71active units 用 rent-roll 单元数两种口径并排返回、页面也并排显示:activeUnitsFromRentRoll(rent-roll 单元数)+ activeUnits(tb_property 登记数),前端自选超额
AXI-71收购/处置物业名(同月多个列全部)CONTEXT_STRIP.acquired[] / disposed[]满足
AXI-71active property 数CONTEXT_STRIP.activePropertyCount满足
AXI-72可见范围 = Projects 列表可见项目走网关 X-Project-Ids,不是数据接口的事网关负责
AXI-69Portfolio View: Aggregation Engine
Linear 原文(逐字):
Period-based aggregation across all active properties in a GP account. For each period, sum revenue and cost items of every property active in that period. Do not normalize holding periods across deals. Build the portfolio timeline from the earliest acquisition date to the latest disposition date across the account. A property not active in a given period contributes zero to that period. Expose per-period aggregate values so downstream metric and period-over-period logic can consume them.
➡️ 对应界面功能:下方「逐行汇总表」(Rollup)—— 所选多个项目按月加总后的收入/成本明细。
🛠️ 我们的实现 / 模糊点备注:已实现:按「日历月」把所选项目每一行收入/成本跨项目相加(DB 侧 GROUP BY),不活跃的月贡献 0,时间轴取所有项目里最早收购~最晚处置。模糊点:原文说 “a GP account”,但我们没有“GP 账户”这个东西,见下方「GP 与账号关系」。
⚠️ 看上去清楚、落地才发现没说清的点:
  • “a GP account” 到底指什么?系统里没有“GP 账户”。我们改成“用户选中的一组项目”。
  • “revenue and cost items” 具体是哪些行?我们定为:租金收入各行 + 其他收入 + 运营费用(含税/保险) + NOI,纯 $ 可加项;比率/IRR/人均等不加。
  • “active in that period” 用哪个日期判活跃?我们用 tb_deal 的收购日~处置日窗口。
  • “period” 是日历月还是各 deal 的持有期序号?各 deal 起止不一,我们统一按日历月对齐(原文说不要归一化持有期)。
AXI-70Portfolio View: Metric Calculations
Linear 原文(逐字):
The four v1 operating metrics, each computed per period off the aggregation engine's output. NOI = Sum across active properties in period. Revenue = Sum across active properties in period. Operating Expenses = Sum across active properties in period. Occupancy = Total occupied SF ÷ total leasable SF across active properties. Occupancy note: Weight by leasable SF. Sum occupied SF and leasable SF across active properties, then divide. Do not average per-property occupancy rates.
➡️ 对应界面功能:「Metrics」区 —— NOI / Revenue / Operating Expenses / Occupancy(逐月、可切 S0/S1/S2、可切图表)。
🛠️ 我们的实现 / 模糊点备注:NOI/Revenue/OpEx 就是逐月跨项目求和,直接投影。Occupancy 原文写的是 “occupied SF ÷ leasable SF”,但那是 rent-roll 快照(一个时间点),而 Portfolio 是预测(逐月该变化),快照平铺到每期是错的。所以改成从预测租金派生逐月占用率;又因“到底算哪种”没说清,我们算了两种(物理/经济),rent-roll 快照单独保留。见下方口径表。
⚠️ 看上去清楚、落地才发现没说清的点:
  • Occupancy 的分子/分母没定义清楚:是 rent-roll 快照 SF(历史一个点)还是逐月预测?这是两个完全不同的东西。
  • 就算按“occupied SF ÷ leasable SF”,预测期根本没有逐月的 occupied SF 序列(rent-roll 只有一个快照),字面照做会让每个预测月都是同一个数。
  • 要不要把让利(concession)、低租(loss-to-lease)算进“没收满”?物理口径不算、经济口径算 —— 原文没说,我们两种都给。
  • NOI/Revenue/OpEx 用哪个场景?含不含 CapEx?暴露 S0/S1/S2 供切换(S0 含 CapEx / S1 不含)。
  • “per period” 含不含持有期之外的 forward 月?我们只取持有期内(out_of_holding_period=0)。
指标分子分母数据来源业务逻辑(大白话)
NOI(净营业收入)
金额
所选各物业当月 NOI 逐一相加—(纯求和,无分母)预测报表的 NOI 序列(noi 源 / NET_OPERATING_INCOME)把所有选中项目当月的净营业收入直接加起来。不活跃的项目当月算 0。
Revenue(营业收入)
金额
各物业当月「租金收入各行」+「其他收入」相加—(纯求和,无分母)rent 源(RENTAL_INCOME)+ other_income 源(OTHER_INCOME)所有选中项目当月的收入合计(含租金与其他收入)。
Operating Expenses(运营支出)
金额
各物业当月运营费用相加(含物业税、保险)—(纯求和,无分母)expense 源(EXPENSE / PROPERTY_TAXES / INSURANCE)所有选中项目当月的运营支出合计。
Occupancy · 物理口径(预测·逐月·逐场景)
百分比
潜在租金 − 空置损失(= 扣掉空置后的租金)潜在租金(Market Rent / GPR)预测租金 tb_report_rent_item,跨物业先求和再相除;随 S0/S1/S2 逐月变化反映“楼有多满”。= 1 − 空置损失 ÷ 潜在租金。只扣纯空置,不含让利/低租。多物业按潜在租金加权(先加金额、再相除,不是把各物业占用率求平均)。
Occupancy · 经济口径(预测·逐月·逐场景)
百分比
潜在租金 − 空置 − 低租(Loss-to-Lease) − 让利(Concessions)潜在租金(Market Rent / GPR)预测租金 tb_report_rent_item,跨物业先求和再相除;随 S0/S1/S2 逐月变化反映“实际收到的有效租金占潜在租金的比例”。= 1 − (空置+低租+让利) ÷ 潜在租金。比物理口径更低,因为把让利和低租也算作“没收满”。
Occupancy · Rent-Roll 初期快照(非预测,单值)
百分比
已占用单元的 SF(占用状态单元面积之和)可租 SF(rent-roll 全部单元面积之和)rent-roll 上传快照,多物业叠加;只有一个时间点的值这才是 Linear 原文说的“occupied SF ÷ leasable SF”。但 rent-roll 是一个时间点的快照,只能当“初期占用率”单独看一眼,不能摊到每个预测月(否则每个预测月都会是同一个数)。
AXI-71Portfolio View: Context Strip
Linear 原文(逐字):
A persistent strip displayed alongside the metrics at all times, showing, for the selected period: Active property count; Active units and leasable SF; Properties acquired or sold during the period, if any. Why it's mandatory: without it, a jump in NOI can't be distinguished between real performance improvement and simply more properties entering the active set. The strip must remain visible for every period.
➡️ 对应界面功能:顶部「Context」常驻带 —— 活跃物业数 / 活跃单元 / 可租 SF + 当期收购/处置。
🛠️ 我们的实现 / 模糊点备注:已实现,常驻显示(无需切换)。活跃/收购/处置口径来自 tb_deal 的收购~处置窗口。作用:NOI 涨了,先看这条带 —— 是真的经营变好,还是只是多了几个物业进来。
⚠️ 看上去清楚、落地才发现没说清的点:
  • “Active units”从哪来、怎么定义?是 rent-roll 的单元条数,还是 unit config 的配置数?我们用当前 rent-roll 的单元数。
  • “leasable SF”同样是 rent-roll 快照口径 —— 和 Occupancy 一样存在“快照 vs 逐月”的问题。
  • “acquired or sold during the period”里的“during”按哪个日期?我们用 tb_deal 的收购/处置日落在该月即算。
  • 同一个月多个物业进/出怎么呈现?原文没说,我们把名字都列出来。
AXI-72Portfolio View: GP-view Routing and Access
Linear 原文(逐字):
Make the portfolio view reachable and access-controlled for GP-role users. Route a logged-in GP-role user to the Portfolio View. Scope the view to GP role for v1. No LP view in this release. Acceptance: a logged-in GP-role user can reach the Portfolio View; the view renders the metrics and context strip for that account's active properties.
➡️ 对应界面功能:侧栏「Portfolio View」入口 + 访问范围(谁能看到哪些项目)。
🛠️ 我们的实现 / 模糊点备注:原文假设有一种“GP-role user”(账户级角色),但系统里没有这个属性 —— GP/LP 是「逐项目的资本结构」(某公司在这个项目当 GP、在另一个项目当 LP),不是一个人固定的身份。故未按“GP 角色”做门,改为:可见项目 = 用户在「Projects 列表」里本就能看到的。详见下方「GP 与账号关系」。
⚠️ 看上去清楚、落地才发现没说清的点:
  • “GP-role user”在系统里根本不存在这个属性 —— 一个人不是天生 GP。GP/LP 是逐项目、挂在公司上的。这是最核心的说不清。
  • “Route a logged-in GP-role user”是登录后自动跳转到 Portfolio,还是只提供一个入口点?我们做成侧栏入口,不自动跳。
  • “Scope the view to GP role”具体规则是什么?没给。改为“用户可见的项目”口径。
  • “No LP view in this release”:那 LP 登录进来是隐藏入口、还是允许看但只显示他能看的?我们不隐藏入口,按可见项目放行。
GP / LP 与账号的关系(为什么 AXI-72 那样写行不通)
1. Linear 假设有一种「GP-role user」—— 好像一个人天生就是 GP,登录后就看“他的 GP 账户”下所有楼。但我们系统里没有“账户级的 GP/LP 身份”这个东西。
2. 在我们的数据里,GP/LP 是「逐项目的资本结构」:每个项目里可以配置一堆公司,某家公司在 A 项目充当 GP、在 B 项目可以充当 LP。也就是说“是 GP 还是 LP”是跟着“项目 + 公司”走的,不是跟着“人/账号”走的。
3. 所以同一个人完全可能在这个项目是 GP、在那个项目是 LP。用一个账户级的“他是 GP 还是 LP”标志去判断,本身就是错的。
4. 处理方式:Portfolio View 的可访问范围 = 用户在「Projects 列表」里本就能看到的项目(本人拥有的 + 其作为 manager 管理的下属的)。既贴合“看自己名下这一篮子 deal”的意图,又不依赖一个并不存在的“GP 角色”。
5. 如果产品上确实要“GP 视角”,需要先在账号上定义一个明确的 GP/LP 属性(并回答“同一个人不同项目不同角色”怎么办)—— 这是需求要先想清楚的地方。
附:AXI-69~72 定版决策(产品经理确认)
AXI-69(Aggregation Engine)
  • “GP account”定为“用户选中的一组项目”。默认全选用户可见的所有项目,可再取消勾选,不采用初始空状态(空状态等于把 property 筛选变成 v1 必选,该功能排入 v1.5)。
  • Revenue/cost items 口径:租金收入各行 + 其他收入;运营费用含税/保险;纯金额可加,比率不加。
  • active 窗口取 tb_deal 收购日~处置日。
  • 按日历月对齐,不归一化持有期。
AXI-70(Metrics / Occupancy)—— 原 spec 的 occupancy 公式有缺陷(rent-roll 是快照,摊到每个预测月会成一条平线),故重新定版:
  • v1 采用物理口径 occupancy(预测·逐月),为 v1 唯一显示的 occupancy。
  • 经济口径 v1 不显示,列为 v1.5 候选(两个 occupancy 并列会引发客户对不上的疑问)。
  • Rent-roll 初期快照若已完成,可保留为单独的时点静态值并标注;若增加 UI 复杂度则暂不显示。
  • 只取持有期内(out_of_holding_period = 0)。
  • NOI 在 CapEx 之上计算;NOI 定义本身不含 CapEx,与场景无关。数据取自 NET_OPERATING_INCOME 序列(需确认该序列为 CapEx 之前口径)。
  • Scenario:v1 锁定场景、不放开 toggle。锁定每个 deal 创建时自动生成的默认 case(需确认:100% 的 deal 都有默认 case、且 S0 即指该默认 case;若某 deal 的 S0 非默认 case,按默认 case 取、不按标签取)。无默认 case 的 deal 不进汇总,且界面明确标出被排除,不得静默丢弃(静默丢弃会使汇总失真)。Toggle 代码保留,v1 禁用或以 feature flag 关闭,缺场景规则确定后于 v1.5 放开。MoM % 切换与场景无关,保留在 v1。
AXI-71(Context Strip)
  • active units 取 rent-roll 单元数;收购/处置以 tb_deal 日期落在当月为准;同月多个物业进出,名称全部列出。
AXI-72(GP routing)
  • 系统无账户级 GP/LP 身份(spec 未写清)。可见范围 = Projects 列表本就可见的项目;侧栏入口;不自动跳转;不隐藏 LP 入口。账户级 GP/LP 属性问题延至 LP view 一并解决。
MoM %
No data
Select projects and click Generate