business_harness.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. from __future__ import annotations
  2. from datetime import datetime
  3. from decimal import Decimal
  4. from typing import Any
  5. from sqlalchemy import (
  6. BigInteger,
  7. Boolean,
  8. DateTime,
  9. ForeignKey,
  10. Index,
  11. Integer,
  12. JSON,
  13. Numeric,
  14. String,
  15. Text,
  16. UniqueConstraint,
  17. func,
  18. )
  19. from sqlalchemy.orm import Mapped, mapped_column
  20. from supply_infra.db.base import Base
  21. class EvidencePackage(Base):
  22. """Immutable evidence snapshot owned by one durable pipeline run."""
  23. __tablename__ = "evidence_package"
  24. __table_args__ = (
  25. UniqueConstraint("run_id", name="uk_evidence_package_run"),
  26. Index("idx_evidence_package_biz", "biz_dt", "created_at"),
  27. )
  28. package_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  29. run_id: Mapped[str] = mapped_column(
  30. String(36),
  31. ForeignKey("pipeline_run.run_id", ondelete="RESTRICT"),
  32. nullable=False,
  33. )
  34. biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  35. package_version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
  36. status: Mapped[str] = mapped_column(
  37. String(24), nullable=False, default="frozen"
  38. )
  39. source_snapshot_hash: Mapped[str] = mapped_column(String(64), nullable=False)
  40. source_versions_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  41. evidence_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
  42. created_at: Mapped[datetime] = mapped_column(
  43. DateTime, nullable=False, server_default=func.now()
  44. )
  45. class RawDemandExpression(Base):
  46. """Unmodified source expression captured inside an evidence package."""
  47. __tablename__ = "raw_demand_expression"
  48. __table_args__ = (
  49. UniqueConstraint(
  50. "package_id",
  51. "source_type",
  52. "source_record_id",
  53. name="uk_raw_expression_source",
  54. ),
  55. Index("idx_raw_expression_package", "package_id"),
  56. Index("idx_raw_expression_hash", "content_hash"),
  57. )
  58. expression_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  59. package_id: Mapped[str] = mapped_column(
  60. String(36),
  61. ForeignKey("evidence_package.package_id", ondelete="RESTRICT"),
  62. nullable=False,
  63. )
  64. source_type: Mapped[str] = mapped_column(String(64), nullable=False)
  65. source_record_id: Mapped[str] = mapped_column(String(128), nullable=False)
  66. raw_text: Mapped[str] = mapped_column(String(512), nullable=False)
  67. original_payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  68. content_hash: Mapped[str] = mapped_column(String(64), nullable=False)
  69. observed_biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  70. data_quality: Mapped[str] = mapped_column(
  71. String(24), nullable=False, default="available"
  72. )
  73. created_at: Mapped[datetime] = mapped_column(
  74. DateTime, nullable=False, server_default=func.now()
  75. )
  76. class StandardDemandTerm(Base):
  77. """Canonical term with stable identity across daily runs."""
  78. __tablename__ = "standard_demand_term"
  79. __table_args__ = (
  80. UniqueConstraint("canonical_key", name="uk_standard_demand_term_key"),
  81. Index("idx_standard_demand_term_text", "normalized_text"),
  82. )
  83. term_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  84. canonical_key: Mapped[str] = mapped_column(String(64), nullable=False)
  85. canonical_text: Mapped[str] = mapped_column(String(256), nullable=False)
  86. normalized_text: Mapped[str] = mapped_column(String(256), nullable=False)
  87. status: Mapped[str] = mapped_column(
  88. String(24), nullable=False, default="active"
  89. )
  90. created_at: Mapped[datetime] = mapped_column(
  91. DateTime, nullable=False, server_default=func.now()
  92. )
  93. updated_at: Mapped[datetime] = mapped_column(
  94. DateTime, nullable=False, server_default=func.now(), onupdate=func.now()
  95. )
  96. class StandardDemandAlias(Base):
  97. """Traceable relationship from a canonical term to a raw expression."""
  98. __tablename__ = "standard_demand_alias"
  99. __table_args__ = (
  100. UniqueConstraint(
  101. "term_id",
  102. "expression_id",
  103. name="uk_standard_demand_alias_expression",
  104. ),
  105. Index("idx_standard_demand_alias_term", "term_id"),
  106. )
  107. alias_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  108. term_id: Mapped[str] = mapped_column(
  109. String(36),
  110. ForeignKey("standard_demand_term.term_id", ondelete="RESTRICT"),
  111. nullable=False,
  112. )
  113. expression_id: Mapped[str] = mapped_column(
  114. String(36),
  115. ForeignKey("raw_demand_expression.expression_id", ondelete="RESTRICT"),
  116. nullable=False,
  117. )
  118. alias_text: Mapped[str] = mapped_column(String(512), nullable=False)
  119. relation_type: Mapped[str] = mapped_column(
  120. String(32), nullable=False, default="source_expression"
  121. )
  122. confidence: Mapped[Decimal] = mapped_column(
  123. Numeric(6, 5), nullable=False, default=1
  124. )
  125. reason: Mapped[str] = mapped_column(Text, nullable=False)
  126. created_at: Mapped[datetime] = mapped_column(
  127. DateTime, nullable=False, server_default=func.now()
  128. )
  129. class PlatformDemand(Base):
  130. """Stable business object consumed by scoring, people and agents."""
  131. __tablename__ = "platform_demand"
  132. __table_args__ = (
  133. UniqueConstraint("canonical_key", name="uk_platform_demand_key"),
  134. Index("idx_platform_demand_name", "name"),
  135. Index("idx_platform_demand_status", "status"),
  136. )
  137. platform_demand_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  138. canonical_key: Mapped[str] = mapped_column(String(64), nullable=False)
  139. name: Mapped[str] = mapped_column(String(256), nullable=False)
  140. description: Mapped[str] = mapped_column(Text, nullable=False)
  141. status: Mapped[str] = mapped_column(
  142. String(24), nullable=False, default="active"
  143. )
  144. lifecycle_state: Mapped[str] = mapped_column(
  145. String(24), nullable=False, default="new"
  146. )
  147. created_at: Mapped[datetime] = mapped_column(
  148. DateTime, nullable=False, server_default=func.now()
  149. )
  150. updated_at: Mapped[datetime] = mapped_column(
  151. DateTime, nullable=False, server_default=func.now(), onupdate=func.now()
  152. )
  153. class PlatformDemandVersion(Base):
  154. """Append-only daily cognition version for a platform demand."""
  155. __tablename__ = "platform_demand_version"
  156. __table_args__ = (
  157. UniqueConstraint(
  158. "platform_demand_id",
  159. "version_no",
  160. name="uk_platform_demand_version_no",
  161. ),
  162. UniqueConstraint(
  163. "platform_demand_id",
  164. "run_id",
  165. name="uk_platform_demand_version_run",
  166. ),
  167. Index("idx_platform_demand_version_biz", "biz_dt", "run_id"),
  168. )
  169. platform_demand_version_id: Mapped[str] = mapped_column(
  170. String(36), primary_key=True
  171. )
  172. platform_demand_id: Mapped[str] = mapped_column(
  173. String(36),
  174. ForeignKey("platform_demand.platform_demand_id", ondelete="RESTRICT"),
  175. nullable=False,
  176. )
  177. term_id: Mapped[str] = mapped_column(
  178. String(36),
  179. ForeignKey("standard_demand_term.term_id", ondelete="RESTRICT"),
  180. nullable=False,
  181. )
  182. package_id: Mapped[str] = mapped_column(
  183. String(36),
  184. ForeignKey("evidence_package.package_id", ondelete="RESTRICT"),
  185. nullable=False,
  186. )
  187. run_id: Mapped[str] = mapped_column(
  188. String(36),
  189. ForeignKey("pipeline_run.run_id", ondelete="RESTRICT"),
  190. nullable=False,
  191. )
  192. source_demand_grade_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
  193. version_no: Mapped[int] = mapped_column(Integer, nullable=False)
  194. biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  195. name: Mapped[str] = mapped_column(String(256), nullable=False)
  196. description: Mapped[str] = mapped_column(Text, nullable=False)
  197. cognition_confidence: Mapped[Decimal] = mapped_column(
  198. Numeric(6, 5), nullable=False
  199. )
  200. reason: Mapped[str] = mapped_column(Text, nullable=False)
  201. change_type: Mapped[str] = mapped_column(String(24), nullable=False)
  202. evidence_hash: Mapped[str] = mapped_column(String(64), nullable=False)
  203. created_at: Mapped[datetime] = mapped_column(
  204. DateTime, nullable=False, server_default=func.now()
  205. )
  206. class PlatformDemandCategoryRel(Base):
  207. """Versioned graph edge from one platform-demand version to the tree."""
  208. __tablename__ = "platform_demand_category_rel"
  209. __table_args__ = (
  210. UniqueConstraint(
  211. "platform_demand_version_id",
  212. "category_id",
  213. "relation_type",
  214. name="uk_platform_demand_category_rel",
  215. ),
  216. Index("idx_platform_demand_category_node", "category_id"),
  217. )
  218. rel_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  219. platform_demand_version_id: Mapped[str] = mapped_column(
  220. String(36),
  221. ForeignKey(
  222. "platform_demand_version.platform_demand_version_id",
  223. ondelete="RESTRICT",
  224. ),
  225. nullable=False,
  226. )
  227. category_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
  228. relation_type: Mapped[str] = mapped_column(String(32), nullable=False)
  229. relation_source: Mapped[str] = mapped_column(String(64), nullable=False)
  230. reason: Mapped[str] = mapped_column(Text, nullable=False)
  231. confidence: Mapped[Decimal] = mapped_column(
  232. Numeric(6, 5), nullable=False
  233. )
  234. is_inferred: Mapped[bool] = mapped_column(
  235. Boolean, nullable=False, default=False
  236. )
  237. status: Mapped[str] = mapped_column(
  238. String(24), nullable=False, default="active"
  239. )
  240. valid_from_biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  241. created_at: Mapped[datetime] = mapped_column(
  242. DateTime, nullable=False, server_default=func.now()
  243. )
  244. class StrategyVersion(Base):
  245. """Versioned scoring and allocation policy; published versions are immutable."""
  246. __tablename__ = "strategy_version"
  247. __table_args__ = (
  248. UniqueConstraint("version_key", name="uk_strategy_version_key"),
  249. Index("idx_strategy_version_status", "status", "activated_at"),
  250. )
  251. strategy_version_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  252. version_key: Mapped[str] = mapped_column(String(64), nullable=False)
  253. status: Mapped[str] = mapped_column(String(24), nullable=False)
  254. definition_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  255. change_reason: Mapped[str] = mapped_column(Text, nullable=False)
  256. created_by: Mapped[str] = mapped_column(String(128), nullable=False)
  257. activated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  258. created_at: Mapped[datetime] = mapped_column(
  259. DateTime, nullable=False, server_default=func.now()
  260. )
  261. class DemandEvaluationSnapshot(Base):
  262. """Two reproducible 0-1 decisions for one platform-demand version."""
  263. __tablename__ = "demand_evaluation_snapshot"
  264. __table_args__ = (
  265. UniqueConstraint(
  266. "platform_demand_version_id",
  267. name="uk_demand_evaluation_version",
  268. ),
  269. Index("idx_demand_evaluation_biz", "biz_dt", "action_tier"),
  270. )
  271. evaluation_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  272. platform_demand_version_id: Mapped[str] = mapped_column(
  273. String(36),
  274. ForeignKey(
  275. "platform_demand_version.platform_demand_version_id",
  276. ondelete="RESTRICT",
  277. ),
  278. nullable=False,
  279. )
  280. run_id: Mapped[str] = mapped_column(
  281. String(36),
  282. ForeignKey("pipeline_run.run_id", ondelete="RESTRICT"),
  283. nullable=False,
  284. )
  285. strategy_version_id: Mapped[str] = mapped_column(
  286. String(36),
  287. ForeignKey("strategy_version.strategy_version_id", ondelete="RESTRICT"),
  288. nullable=False,
  289. )
  290. biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  291. validity_score: Mapped[Decimal] = mapped_column(Numeric(6, 5), nullable=False)
  292. local_supply_priority: Mapped[Decimal] = mapped_column(
  293. Numeric(6, 5), nullable=False
  294. )
  295. data_confidence: Mapped[Decimal] = mapped_column(Numeric(6, 5), nullable=False)
  296. posterior_state: Mapped[str] = mapped_column(String(24), nullable=False)
  297. action_tier: Mapped[str] = mapped_column(String(32), nullable=False)
  298. metrics_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  299. missing_dimensions_json: Mapped[list[str]] = mapped_column(JSON, nullable=False)
  300. decision_reason: Mapped[str] = mapped_column(Text, nullable=False)
  301. created_at: Mapped[datetime] = mapped_column(
  302. DateTime, nullable=False, server_default=func.now()
  303. )
  304. class DemandScoreContribution(Base):
  305. """Expandable score contribution; contributions sum to the stored score."""
  306. __tablename__ = "demand_score_contribution"
  307. __table_args__ = (
  308. UniqueConstraint(
  309. "evaluation_id",
  310. "score_type",
  311. "dimension",
  312. name="uk_demand_score_contribution",
  313. ),
  314. Index("idx_demand_score_contribution_eval", "evaluation_id"),
  315. )
  316. contribution_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  317. evaluation_id: Mapped[str] = mapped_column(
  318. String(36),
  319. ForeignKey("demand_evaluation_snapshot.evaluation_id", ondelete="RESTRICT"),
  320. nullable=False,
  321. )
  322. score_type: Mapped[str] = mapped_column(String(24), nullable=False)
  323. dimension: Mapped[str] = mapped_column(String(64), nullable=False)
  324. raw_value_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  325. normalized_value: Mapped[Decimal] = mapped_column(
  326. Numeric(8, 7), nullable=False
  327. )
  328. configured_weight: Mapped[Decimal] = mapped_column(
  329. Numeric(8, 7), nullable=False
  330. )
  331. effective_weight: Mapped[Decimal] = mapped_column(
  332. Numeric(8, 7), nullable=False
  333. )
  334. contribution: Mapped[Decimal] = mapped_column(Numeric(8, 7), nullable=False)
  335. data_status: Mapped[str] = mapped_column(String(24), nullable=False)
  336. reason: Mapped[str] = mapped_column(Text, nullable=False)
  337. created_at: Mapped[datetime] = mapped_column(
  338. DateTime, nullable=False, server_default=func.now()
  339. )
  340. class DailyDemandPackage(Base):
  341. """The only published daily contract shared by people and agents."""
  342. __tablename__ = "daily_demand_package"
  343. __table_args__ = (
  344. UniqueConstraint("run_id", name="uk_daily_demand_package_run"),
  345. Index("idx_daily_demand_package_biz", "biz_dt", "status"),
  346. )
  347. demand_package_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  348. run_id: Mapped[str] = mapped_column(
  349. String(36),
  350. ForeignKey("pipeline_run.run_id", ondelete="RESTRICT"),
  351. nullable=False,
  352. )
  353. evidence_package_id: Mapped[str] = mapped_column(
  354. String(36),
  355. ForeignKey("evidence_package.package_id", ondelete="RESTRICT"),
  356. nullable=False,
  357. )
  358. strategy_version_id: Mapped[str] = mapped_column(
  359. String(36),
  360. ForeignKey("strategy_version.strategy_version_id", ondelete="RESTRICT"),
  361. nullable=False,
  362. )
  363. biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  364. package_version: Mapped[int] = mapped_column(Integer, nullable=False)
  365. status: Mapped[str] = mapped_column(String(24), nullable=False)
  366. content_hash: Mapped[str] = mapped_column(String(64), nullable=False)
  367. item_count: Mapped[int] = mapped_column(Integer, nullable=False)
  368. published_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  369. created_at: Mapped[datetime] = mapped_column(
  370. DateTime, nullable=False, server_default=func.now()
  371. )
  372. class DailyDemandTask(Base):
  373. """Immutable agent/human task view for one evaluated demand."""
  374. __tablename__ = "daily_demand_task"
  375. __table_args__ = (
  376. UniqueConstraint(
  377. "demand_package_id",
  378. "platform_demand_version_id",
  379. name="uk_daily_demand_task_version",
  380. ),
  381. Index("idx_daily_demand_task_tier", "demand_package_id", "action_tier"),
  382. )
  383. task_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  384. demand_package_id: Mapped[str] = mapped_column(
  385. String(36),
  386. ForeignKey("daily_demand_package.demand_package_id", ondelete="RESTRICT"),
  387. nullable=False,
  388. )
  389. platform_demand_version_id: Mapped[str] = mapped_column(
  390. String(36),
  391. ForeignKey(
  392. "platform_demand_version.platform_demand_version_id",
  393. ondelete="RESTRICT",
  394. ),
  395. nullable=False,
  396. )
  397. evaluation_id: Mapped[str] = mapped_column(
  398. String(36),
  399. ForeignKey("demand_evaluation_snapshot.evaluation_id", ondelete="RESTRICT"),
  400. nullable=False,
  401. )
  402. action_tier: Mapped[str] = mapped_column(String(32), nullable=False)
  403. search_terms_json: Mapped[list[str]] = mapped_column(JSON, nullable=False)
  404. exclude_terms_json: Mapped[list[str]] = mapped_column(JSON, nullable=False)
  405. hit_rules_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  406. hypotheses_json: Mapped[list[str]] = mapped_column(JSON, nullable=False)
  407. evidence_gaps_json: Mapped[list[str]] = mapped_column(JSON, nullable=False)
  408. existing_content_json: Mapped[list[str]] = mapped_column(JSON, nullable=False)
  409. allocation_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  410. task_payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  411. created_at: Mapped[datetime] = mapped_column(
  412. DateTime, nullable=False, server_default=func.now()
  413. )
  414. class ContentDemandCoverage(Base):
  415. """Many-to-many content coverage edge linked to a published task."""
  416. __tablename__ = "content_demand_coverage"
  417. __table_args__ = (
  418. UniqueConstraint(
  419. "platform_demand_version_id",
  420. "content_id",
  421. "coverage_role",
  422. name="uk_content_demand_coverage",
  423. ),
  424. Index("idx_content_demand_coverage_content", "content_id"),
  425. )
  426. coverage_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  427. platform_demand_version_id: Mapped[str] = mapped_column(
  428. String(36),
  429. ForeignKey(
  430. "platform_demand_version.platform_demand_version_id",
  431. ondelete="RESTRICT",
  432. ),
  433. nullable=False,
  434. )
  435. task_id: Mapped[str] = mapped_column(
  436. String(36),
  437. ForeignKey("daily_demand_task.task_id", ondelete="RESTRICT"),
  438. nullable=False,
  439. )
  440. content_id: Mapped[str] = mapped_column(String(128), nullable=False)
  441. content_url: Mapped[str | None] = mapped_column(String(1024), nullable=True)
  442. source_type: Mapped[str] = mapped_column(String(32), nullable=False)
  443. coverage_role: Mapped[str] = mapped_column(String(24), nullable=False)
  444. coverage_score: Mapped[Decimal] = mapped_column(Numeric(6, 5), nullable=False)
  445. evidence_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  446. status: Mapped[str] = mapped_column(String(24), nullable=False)
  447. discovered_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  448. content_published_at: Mapped[datetime | None] = mapped_column(
  449. DateTime, nullable=True
  450. )
  451. created_at: Mapped[datetime] = mapped_column(
  452. DateTime, nullable=False, server_default=func.now()
  453. )
  454. class ContentPerformanceFact(Base):
  455. """Immutable raw content performance observation."""
  456. __tablename__ = "content_performance_fact"
  457. __table_args__ = (
  458. UniqueConstraint(
  459. "content_id",
  460. "biz_dt",
  461. "source",
  462. "payload_hash",
  463. name="uk_content_performance_fact",
  464. ),
  465. Index("idx_content_performance_fact_content", "content_id", "biz_dt"),
  466. )
  467. performance_fact_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  468. content_id: Mapped[str] = mapped_column(String(128), nullable=False)
  469. biz_dt: Mapped[str] = mapped_column(String(8), nullable=False)
  470. source: Mapped[str] = mapped_column(String(64), nullable=False)
  471. payload_hash: Mapped[str] = mapped_column(String(64), nullable=False)
  472. exposure_count: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  473. sample_size: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  474. content_quality: Mapped[Decimal | None] = mapped_column(
  475. Numeric(6, 5), nullable=True
  476. )
  477. rov: Mapped[Decimal | None] = mapped_column(Numeric(16, 8), nullable=True)
  478. vov: Mapped[Decimal | None] = mapped_column(Numeric(16, 8), nullable=True)
  479. raw_payload_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  480. observed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
  481. created_at: Mapped[datetime] = mapped_column(
  482. DateTime, nullable=False, server_default=func.now()
  483. )
  484. class DemandAttributionSnapshot(Base):
  485. """Reproducible attribution from one content fact to one demand version."""
  486. __tablename__ = "demand_attribution_snapshot"
  487. __table_args__ = (
  488. UniqueConstraint(
  489. "coverage_id",
  490. "performance_fact_id",
  491. "strategy_version_id",
  492. name="uk_demand_attribution_snapshot",
  493. ),
  494. Index(
  495. "idx_demand_attribution_version",
  496. "platform_demand_version_id",
  497. "created_at",
  498. ),
  499. )
  500. attribution_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  501. platform_demand_version_id: Mapped[str] = mapped_column(
  502. String(36),
  503. ForeignKey(
  504. "platform_demand_version.platform_demand_version_id",
  505. ondelete="RESTRICT",
  506. ),
  507. nullable=False,
  508. )
  509. coverage_id: Mapped[str] = mapped_column(
  510. String(36),
  511. ForeignKey("content_demand_coverage.coverage_id", ondelete="RESTRICT"),
  512. nullable=False,
  513. )
  514. performance_fact_id: Mapped[str] = mapped_column(
  515. String(36),
  516. ForeignKey(
  517. "content_performance_fact.performance_fact_id",
  518. ondelete="RESTRICT",
  519. ),
  520. nullable=False,
  521. )
  522. strategy_version_id: Mapped[str] = mapped_column(
  523. String(36),
  524. ForeignKey("strategy_version.strategy_version_id", ondelete="RESTRICT"),
  525. nullable=False,
  526. )
  527. support_state: Mapped[str] = mapped_column(String(24), nullable=False)
  528. attributable_rov: Mapped[Decimal | None] = mapped_column(
  529. Numeric(16, 8), nullable=True
  530. )
  531. attributable_vov: Mapped[Decimal | None] = mapped_column(
  532. Numeric(16, 8), nullable=True
  533. )
  534. attribution_confidence: Mapped[Decimal] = mapped_column(
  535. Numeric(6, 5), nullable=False
  536. )
  537. validity_influence: Mapped[Decimal] = mapped_column(
  538. Numeric(8, 7), nullable=False
  539. )
  540. priority_influence: Mapped[Decimal] = mapped_column(
  541. Numeric(8, 7), nullable=False
  542. )
  543. reason: Mapped[str] = mapped_column(Text, nullable=False)
  544. created_at: Mapped[datetime] = mapped_column(
  545. DateTime, nullable=False, server_default=func.now()
  546. )
  547. class StrategyChangeProposal(Base):
  548. """Draft/preview/approval workflow for changing business strategy."""
  549. __tablename__ = "strategy_change_proposal"
  550. __table_args__ = (Index("idx_strategy_proposal_status", "status", "created_at"),)
  551. proposal_id: Mapped[str] = mapped_column(String(36), primary_key=True)
  552. base_strategy_version_id: Mapped[str] = mapped_column(
  553. String(36),
  554. ForeignKey("strategy_version.strategy_version_id", ondelete="RESTRICT"),
  555. nullable=False,
  556. )
  557. status: Mapped[str] = mapped_column(String(24), nullable=False)
  558. requested_change_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  559. proposed_definition_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  560. diff_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False)
  561. preview_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
  562. reason: Mapped[str] = mapped_column(Text, nullable=False)
  563. created_by: Mapped[str] = mapped_column(String(128), nullable=False)
  564. approved_by: Mapped[str | None] = mapped_column(String(128), nullable=True)
  565. approved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  566. created_at: Mapped[datetime] = mapped_column(
  567. DateTime, nullable=False, server_default=func.now()
  568. )